File indexing completed on 2026-08-30 08:14:26
0001 #include "GlobalFieldFitter.h"
0002
0003 #include "helpers.h"
0004 #include "parameters.h"
0005
0006 #include <algorithm>
0007 #include <cmath>
0008 #include <utility>
0009 #include <array>
0010 #include <vector>
0011
0012 bool nearly_same_control_r(double lhs, double rhs)
0013 {
0014 return std::abs(lhs - rhs) < 1e-3;
0015 }
0016
0017 bool smaller_neighbor_distance(const std::pair<double, double> &lhs, const std::pair<double, double> &rhs)
0018 {
0019 return lhs.first < rhs.first;
0020 }
0021
0022
0023
0024
0025
0026 GlobalFieldFitter::GlobalFieldFitter(const std::vector<std::array<double, 6>> &observations, const std::vector<double> &control_r_positions) : m_observations(observations), m_requestedControlRPositions(control_r_positions) {}
0027
0028
0029
0030
0031
0032
0033
0034 bool GlobalFieldFitter::fit()
0035 {
0036
0037 m_isValid = false;
0038 m_coefficients_delta_r.clear();
0039 m_coefficients_r_delta_phi.clear();
0040 m_evaluation_coefficients_delta_r.clear();
0041 m_evaluation_coefficients_r_delta_phi.clear();
0042
0043 if (m_observations.empty())
0044 {
0045 return false;
0046 }
0047
0048 initialize_grid();
0049 if (m_nControlPhi < 2 || m_nControlR < 2)
0050 {
0051 return false;
0052 }
0053
0054 compute_control_support();
0055 std::vector<int> full_to_fit_index;
0056 select_active_controls(full_to_fit_index);
0057
0058
0059
0060
0061 int nFit = 0;
0062 for (int fit_index : full_to_fit_index)
0063 {
0064 if (fit_index >= 0)
0065 {
0066 nFit = std::max(nFit, fit_index + 1);
0067 }
0068 }
0069 if (nFit < global_field_min_active_controls)
0070 {
0071 return false;
0072 }
0073
0074 std::vector<std::vector<double>> normal_delta_r(nFit, std::vector<double>(nFit, 0.0));
0075 std::vector<std::vector<double>> normal_r_delta_phi(nFit, std::vector<double>(nFit, 0.0));
0076 std::vector<double> rhs_delta_r(nFit, 0.0);
0077 std::vector<double> rhs_r_delta_phi(nFit, 0.0);
0078
0079
0080
0081 for (const auto &observation : m_observations)
0082 {
0083 std::vector<int> indices;
0084 std::vector<double> weights;
0085 accumulate_bilinear_weights(observation[observation_phi], observation[observation_r], indices, weights);
0086 if (indices.empty())
0087 {
0088 continue;
0089 }
0090
0091 const double sigma_delta_r = std::max(observation[observation_sigma_delta_r], 1e-6);
0092 const double sigma_r_delta_phi = std::max(observation[observation_sigma_r_delta_phi], 1e-6);
0093 const double inv_var_delta_r = 1.0 / (sigma_delta_r * sigma_delta_r);
0094 const double inv_var_r_delta_phi = 1.0 / (sigma_r_delta_phi * sigma_r_delta_phi);
0095
0096
0097
0098 double active_weight_sum = 0.0;
0099 for (size_t a = 0; a < indices.size(); a++)
0100 {
0101 if (full_to_fit_index[indices[a]] >= 0)
0102 {
0103 active_weight_sum += weights[a];
0104 }
0105 }
0106 if (active_weight_sum <= 1e-6)
0107 {
0108 continue;
0109 }
0110
0111 for (size_t a = 0; a < indices.size(); a++)
0112 {
0113 const int ia = full_to_fit_index[indices[a]];
0114 if (ia < 0)
0115 {
0116 continue;
0117 }
0118
0119 const double wa = weights[a] / active_weight_sum;
0120 rhs_delta_r[ia] += wa * inv_var_delta_r * observation[observation_delta_r];
0121 rhs_r_delta_phi[ia] += wa * inv_var_r_delta_phi * observation[observation_r_delta_phi];
0122
0123 for (size_t b = 0; b < indices.size(); b++)
0124 {
0125 const int ib = full_to_fit_index[indices[b]];
0126 if (ib < 0)
0127 {
0128 continue;
0129 }
0130
0131 const double wb = weights[b] / active_weight_sum;
0132 normal_delta_r[ia][ib] += wa * inv_var_delta_r * wb;
0133 normal_r_delta_phi[ia][ib] += wa * inv_var_r_delta_phi * wb;
0134 }
0135 }
0136 }
0137
0138 add_smoothness_penalty(normal_delta_r, full_to_fit_index);
0139 add_smoothness_penalty(normal_r_delta_phi, full_to_fit_index);
0140
0141
0142
0143 for (int i = 0; i < nFit; i++)
0144 {
0145 normal_delta_r[i][i] += global_field_kernel_nugget;
0146 normal_r_delta_phi[i][i] += global_field_kernel_nugget;
0147 }
0148
0149 if (!solve_linear_system(normal_delta_r, rhs_delta_r, m_coefficients_delta_r))
0150 {
0151 return false;
0152 }
0153 if (!solve_linear_system(normal_r_delta_phi, rhs_r_delta_phi, m_coefficients_r_delta_phi))
0154 {
0155 return false;
0156 }
0157
0158 build_dense_evaluation_grid();
0159
0160
0161
0162 m_isValid = true;
0163 return true;
0164 }
0165
0166 bool GlobalFieldFitter::is_valid() const
0167 {
0168 return m_isValid;
0169 }
0170
0171 double GlobalFieldFitter::evaluate_delta_r(double phi, double r) const
0172 {
0173
0174
0175 if (!m_evaluation_coefficients_delta_r.empty())
0176 {
0177 return evaluate_component(m_evaluation_coefficients_delta_r, phi, r);
0178 }
0179 return evaluate_component(m_coefficients_delta_r, phi, r);
0180 }
0181
0182 double GlobalFieldFitter::evaluate_r_delta_phi(double phi, double r) const
0183 {
0184 if (!m_evaluation_coefficients_r_delta_phi.empty())
0185 {
0186 return evaluate_component(m_evaluation_coefficients_r_delta_phi, phi, r);
0187 }
0188 return evaluate_component(m_coefficients_r_delta_phi, phi, r);
0189 }
0190
0191 double GlobalFieldFitter::evaluate_delta_phi(double phi, double r) const
0192 {
0193
0194
0195 if (std::abs(r) < 1e-6)
0196 {
0197 return 0.0;
0198 }
0199 return evaluate_r_delta_phi(phi, r) / r;
0200 }
0201
0202 double GlobalFieldFitter::predictive_sigma_delta_r(double , double ) const
0203 {
0204
0205
0206 return estimate_global_residual_sigma(true);
0207 }
0208
0209 double GlobalFieldFitter::predictive_sigma_r_delta_phi(double , double ) const
0210 {
0211 return estimate_global_residual_sigma(false);
0212 }
0213
0214 std::vector<std::array<double, 7>> GlobalFieldFitter::control_points() const
0215 {
0216
0217
0218 std::vector<std::array<double, 7>> points;
0219 points.reserve(m_nControlPhi * m_nControlR);
0220 for (int rIndex = 0; rIndex < m_nControlR; ++rIndex)
0221 {
0222 const double r = control_r(rIndex);
0223 for (int phiIndex = 0; phiIndex < m_nControlPhi; ++phiIndex)
0224 {
0225 const int index = control_index(phiIndex, rIndex);
0226 const double phi = control_phi(phiIndex);
0227 std::array<double, 7> point{};
0228 point[control_point_phi] = phi;
0229 point[control_point_r] = r;
0230 point[control_point_delta_r] = evaluate_delta_r(phi, r);
0231 point[control_point_r_delta_phi] = evaluate_r_delta_phi(phi, r);
0232 point[control_point_delta_phi] = std::abs(r) > 1e-6 ? point[control_point_r_delta_phi] / r : 0.0;
0233 if (index >= 0 && index < static_cast<int>(m_controlSupport.size()))
0234 {
0235 point[control_point_support] = m_controlSupport[index];
0236 }
0237 if (index >= 0 && index < static_cast<int>(m_activeControl.size()))
0238 {
0239 point[control_point_active] = m_activeControl[index];
0240 }
0241 points.push_back(point);
0242 }
0243 }
0244 return points;
0245 }
0246
0247 void GlobalFieldFitter::initialize_grid()
0248 {
0249
0250
0251
0252 const double phi_spacing = std::max(0.05, global_field_control_phi_spacing_rad);
0253 m_nControlPhi = std::max(3, static_cast<int>(std::round((2.0 * M_PI) / phi_spacing)));
0254 m_phiMin = 0.0;
0255 m_phiMax = 2.0 * M_PI;
0256 m_phiStep = (m_phiMax - m_phiMin) / static_cast<double>(m_nControlPhi);
0257
0258 m_rMin = fit_r_min_cm;
0259 m_rMax = fit_r_max_cm;
0260 if (m_rMax <= m_rMin)
0261 {
0262 m_rMax = m_rMin + 1.0;
0263 }
0264
0265 m_controlRPositions.clear();
0266 m_controlRPositions.reserve(m_requestedControlRPositions.size());
0267 for (double r : m_requestedControlRPositions)
0268 {
0269 if (r < m_rMin || r > m_rMax)
0270 {
0271 continue;
0272 }
0273 m_controlRPositions.push_back(r);
0274 }
0275 std::sort(m_controlRPositions.begin(), m_controlRPositions.end());
0276
0277
0278 m_controlRPositions.erase(std::unique(m_controlRPositions.begin(), m_controlRPositions.end(), nearly_same_control_r), m_controlRPositions.end());
0279
0280 if (m_controlRPositions.size() < 2)
0281 {
0282
0283 m_nControlR = std::max(2, global_field_control_r_bins);
0284 m_rStep = (m_rMax - m_rMin) / static_cast<double>(m_nControlR - 1);
0285 m_controlRPositions.resize(m_nControlR);
0286 for (int i = 0; i < m_nControlR; i++)
0287 {
0288 m_controlRPositions[i] = m_rMin + static_cast<double>(i) * m_rStep;
0289 }
0290 }
0291 else
0292 {
0293 m_nControlR = static_cast<int>(m_controlRPositions.size());
0294 m_rStep = 0.0;
0295 }
0296 }
0297
0298 int GlobalFieldFitter::control_index(int phi_index, int r_index) const
0299 {
0300
0301
0302 int wrapped_phi = phi_index % m_nControlPhi;
0303 if (wrapped_phi < 0)
0304 {
0305 wrapped_phi += m_nControlPhi;
0306 }
0307 const int clamped_r = std::clamp(r_index, 0, m_nControlR - 1);
0308 return clamped_r * m_nControlPhi + wrapped_phi;
0309 }
0310
0311 void GlobalFieldFitter::accumulate_bilinear_weights(double phi, double r, std::vector<int> &indices, std::vector<double> &weights) const
0312 {
0313 indices.clear();
0314 weights.clear();
0315
0316
0317 const double phi_width = m_phiMax - m_phiMin;
0318 while (phi < m_phiMin)
0319 {
0320 phi += phi_width;
0321 }
0322 while (phi >= m_phiMax)
0323 {
0324 phi -= phi_width;
0325 }
0326
0327 const double phi_u = (phi - m_phiMin) / m_phiStep;
0328 const int phi0 = static_cast<int>(std::floor(phi_u));
0329 const int phi1 = phi0 + 1;
0330 const double phi_t = phi_u - std::floor(phi_u);
0331
0332
0333
0334 const double r_clamped = std::clamp(r, control_r(0), control_r(m_nControlR - 1));
0335 auto upper = std::upper_bound(m_controlRPositions.begin(), m_controlRPositions.end(), r_clamped);
0336 int r1 = static_cast<int>(upper - m_controlRPositions.begin());
0337 if (r1 <= 0)
0338 {
0339 r1 = 1;
0340 }
0341 if (r1 >= m_nControlR)
0342 {
0343 r1 = m_nControlR - 1;
0344 }
0345 const int r0 = r1 - 1;
0346 const double r_span = std::max(control_r(r1) - control_r(r0), 1e-6);
0347 const double r_t = std::clamp((r_clamped - control_r(r0)) / r_span, 0.0, 1.0);
0348
0349
0350
0351 const double w00 = (1.0 - phi_t) * (1.0 - r_t);
0352 const double w10 = phi_t * (1.0 - r_t);
0353 const double w01 = (1.0 - phi_t) * r_t;
0354 const double w11 = phi_t * r_t;
0355
0356 indices = {control_index(phi0, r0), control_index(phi1, r0), control_index(phi0, r1), control_index(phi1, r1)};
0357 weights = {w00, w10, w01, w11};
0358 }
0359
0360 void GlobalFieldFitter::add_smoothness_penalty(std::vector<std::vector<double>> &normal_matrix, const std::vector<int> &full_to_fit_index) const
0361 {
0362 const double lambda = global_field_regularization_lambda;
0363
0364
0365
0366
0367 for (int r = 0; r < m_nControlR; r++)
0368 {
0369 for (int p = 0; p < m_nControlPhi; p++)
0370 {
0371 const int im1 = control_index(p - 1, r);
0372 const int i0 = control_index(p, r);
0373 const int ip1 = control_index(p + 1, r);
0374 const double phi_coeffs[3] = {1.0, -2.0, 1.0};
0375 const int phi_fit_indices[3] = {full_to_fit_index[im1], full_to_fit_index[i0], full_to_fit_index[ip1]};
0376 if (phi_fit_indices[0] < 0 || phi_fit_indices[1] < 0 || phi_fit_indices[2] < 0)
0377 {
0378 continue;
0379 }
0380
0381 for (int a = 0; a < 3; a++)
0382 {
0383 for (int b = 0; b < 3; b++)
0384 {
0385 normal_matrix[phi_fit_indices[a]][phi_fit_indices[b]] += lambda * phi_coeffs[a] * phi_coeffs[b];
0386 }
0387 }
0388 }
0389 }
0390
0391
0392
0393 for (int r = 1; r < m_nControlR - 1; r++)
0394 {
0395 for (int p = 0; p < m_nControlPhi; p++)
0396 {
0397 const int jm1 = control_index(p, r - 1);
0398 const int j0 = control_index(p, r);
0399 const int jp1 = control_index(p, r + 1);
0400 const double r_coeffs[3] = {1.0, -2.0, 1.0};
0401 const int r_fit_indices[3] = {full_to_fit_index[jm1], full_to_fit_index[j0], full_to_fit_index[jp1]};
0402 if (r_fit_indices[0] < 0 || r_fit_indices[1] < 0 || r_fit_indices[2] < 0)
0403 {
0404 continue;
0405 }
0406
0407 for (int a = 0; a < 3; a++)
0408 {
0409 for (int b = 0; b < 3; b++)
0410 {
0411 normal_matrix[r_fit_indices[a]][r_fit_indices[b]] += lambda * r_coeffs[a] * r_coeffs[b];
0412 }
0413 }
0414 }
0415 }
0416 }
0417
0418 bool GlobalFieldFitter::solve_linear_system(std::vector<std::vector<double>> matrix, std::vector<double> rhs, std::vector<double> &solution) const
0419 {
0420
0421
0422
0423 const size_t n = matrix.size();
0424 solution.assign(n, 0.0);
0425 if (rhs.size() != n)
0426 {
0427 return false;
0428 }
0429
0430 for (size_t i = 0; i < n; i++)
0431 {
0432 matrix[i].push_back(rhs[i]);
0433 }
0434
0435 for (size_t pivot_col = 0; pivot_col < n; pivot_col++)
0436 {
0437
0438 size_t pivot_row = pivot_col;
0439 double pivot_abs = std::abs(matrix[pivot_row][pivot_col]);
0440
0441 for (size_t row = pivot_col + 1; row < n; row++)
0442 {
0443 const double candidate_abs = std::abs(matrix[row][pivot_col]);
0444 if (candidate_abs > pivot_abs)
0445 {
0446 pivot_abs = candidate_abs;
0447 pivot_row = row;
0448 }
0449 }
0450
0451 if (pivot_abs < 1e-12)
0452 {
0453 return false;
0454 }
0455
0456 if (pivot_row != pivot_col)
0457 {
0458 std::swap(matrix[pivot_row], matrix[pivot_col]);
0459 }
0460
0461
0462 const double pivot = matrix[pivot_col][pivot_col];
0463 for (size_t col = pivot_col; col <= n; col++)
0464 {
0465 matrix[pivot_col][col] /= pivot;
0466 }
0467
0468 for (size_t row = 0; row < n; row++)
0469 {
0470 if (row == pivot_col)
0471 {
0472 continue;
0473 }
0474
0475 const double factor = matrix[row][pivot_col];
0476 for (size_t col = pivot_col; col <= n; col++)
0477 {
0478 matrix[row][col] -= factor * matrix[pivot_col][col];
0479 }
0480 }
0481 }
0482
0483 for (size_t row = 0; row < n; row++)
0484 {
0485 solution[row] = matrix[row][n];
0486 }
0487
0488 return true;
0489 }
0490
0491 double GlobalFieldFitter::evaluate_component(const std::vector<double> &coefficients, double phi, double r) const
0492 {
0493 if (!m_isValid || coefficients.empty())
0494 {
0495 return 0.0;
0496 }
0497
0498 if (static_cast<int>(coefficients.size()) != m_nControlPhi * m_nControlR)
0499 {
0500
0501
0502 return interpolate_active_value(coefficients, phi, r);
0503 }
0504
0505 std::vector<int> indices;
0506 std::vector<double> weights;
0507 accumulate_bilinear_weights(phi, r, indices, weights);
0508
0509 double value = 0.0;
0510 for (size_t i = 0; i < indices.size(); i++)
0511 {
0512 value += weights[i] * coefficients[indices[i]];
0513 }
0514
0515 return value;
0516 }
0517
0518 void GlobalFieldFitter::compute_control_support()
0519 {
0520
0521
0522 const int nCtrl = m_nControlPhi * m_nControlR;
0523 m_controlSupport.assign(nCtrl, 0.0);
0524
0525 for (const auto &observation : m_observations)
0526 {
0527 std::vector<int> indices;
0528 std::vector<double> weights;
0529 accumulate_bilinear_weights(observation[observation_phi], observation[observation_r], indices, weights);
0530 for (size_t i = 0; i < indices.size(); i++)
0531 {
0532 m_controlSupport[indices[i]] += weights[i];
0533 }
0534 }
0535 }
0536
0537 void GlobalFieldFitter::select_active_controls(std::vector<int> &full_to_fit_index)
0538 {
0539 const int nCtrl = m_nControlPhi * m_nControlR;
0540 full_to_fit_index.assign(nCtrl, -1);
0541 m_activeControl.assign(nCtrl, false);
0542
0543 if (!use_active_control_interpolation)
0544 {
0545
0546 for (int i = 0; i < nCtrl; i++)
0547 {
0548 m_activeControl[i] = true;
0549 full_to_fit_index[i] = i;
0550 }
0551 return;
0552 }
0553
0554
0555 int nActive = 0;
0556 for (int i = 0; i < nCtrl; i++)
0557 {
0558 if (m_controlSupport[i] < global_field_min_control_support)
0559 {
0560 continue;
0561 }
0562
0563 m_activeControl[i] = true;
0564 full_to_fit_index[i] = nActive++;
0565 }
0566
0567 if (nActive >= global_field_min_active_controls)
0568 {
0569 return;
0570 }
0571
0572
0573
0574 for (int i = 0; i < nCtrl; i++)
0575 {
0576 m_activeControl[i] = true;
0577 full_to_fit_index[i] = i;
0578 }
0579 }
0580
0581 void GlobalFieldFitter::build_dense_evaluation_grid()
0582 {
0583
0584
0585 const int nCtrl = m_nControlPhi * m_nControlR;
0586 m_evaluation_coefficients_delta_r.assign(nCtrl, 0.0);
0587 m_evaluation_coefficients_r_delta_phi.assign(nCtrl, 0.0);
0588
0589 if (!use_active_control_interpolation || static_cast<int>(m_coefficients_delta_r.size()) == nCtrl || static_cast<int>(m_coefficients_r_delta_phi.size()) == nCtrl)
0590 {
0591 m_evaluation_coefficients_delta_r = m_coefficients_delta_r;
0592 m_evaluation_coefficients_r_delta_phi = m_coefficients_r_delta_phi;
0593 return;
0594 }
0595
0596
0597 int coeff_index = 0;
0598
0599
0600 for (int ir = 0; ir < m_nControlR; ir++)
0601 {
0602 for (int iphi = 0; iphi < m_nControlPhi; iphi++)
0603 {
0604 const int full_index = control_index(iphi, ir);
0605 if (full_index >= static_cast<int>(m_activeControl.size()) || !m_activeControl[full_index])
0606 {
0607 continue;
0608 }
0609 if (coeff_index >= static_cast<int>(m_coefficients_delta_r.size()) || coeff_index >= static_cast<int>(m_coefficients_r_delta_phi.size()))
0610 {
0611 break;
0612 }
0613
0614 m_evaluation_coefficients_delta_r[full_index] = m_coefficients_delta_r[coeff_index];
0615 m_evaluation_coefficients_r_delta_phi[full_index] = m_coefficients_r_delta_phi[coeff_index];
0616 coeff_index++;
0617 }
0618 }
0619
0620 for (int ir = 0; ir < m_nControlR; ir++)
0621 {
0622 const double r = control_r(ir);
0623 for (int iphi = 0; iphi < m_nControlPhi; iphi++)
0624 {
0625 const int full_index = control_index(iphi, ir);
0626 if (full_index < static_cast<int>(m_activeControl.size()) && m_activeControl[full_index])
0627 {
0628 continue;
0629 }
0630
0631 const double phi = control_phi(iphi);
0632 m_evaluation_coefficients_delta_r[full_index] = interpolate_active_value(m_coefficients_delta_r, phi, r);
0633 m_evaluation_coefficients_r_delta_phi[full_index] = interpolate_active_value(m_coefficients_r_delta_phi, phi, r);
0634 }
0635 }
0636 }
0637
0638 double GlobalFieldFitter::control_phi(int phi_index) const
0639 {
0640 int wrapped_phi = phi_index % m_nControlPhi;
0641 if (wrapped_phi < 0)
0642 {
0643 wrapped_phi += m_nControlPhi;
0644 }
0645 return m_phiMin + static_cast<double>(wrapped_phi) * m_phiStep;
0646 }
0647
0648 double GlobalFieldFitter::control_r(int r_index) const
0649 {
0650 const int clamped_r = std::clamp(r_index, 0, m_nControlR - 1);
0651 if (clamped_r < static_cast<int>(m_controlRPositions.size()))
0652 {
0653 return m_controlRPositions[clamped_r];
0654 }
0655 return m_rMin;
0656 }
0657
0658 double GlobalFieldFitter::interpolate_active_value(const std::vector<double> &coefficients, double phi, double r) const
0659 {
0660 if (coefficients.empty())
0661 {
0662 return 0.0;
0663 }
0664
0665
0666
0667 std::vector<std::pair<double, double>> neighbors;
0668 neighbors.reserve(std::max(1, global_field_active_interpolation_neighbors));
0669 int coeff_index = 0;
0670 const int max_neighbors = std::max(1, global_field_active_interpolation_neighbors);
0671 const double r_scale = std::max(1e-6, global_field_active_interpolation_r_scale_cm);
0672 const double rphi_scale = std::max(1e-6, global_field_active_interpolation_rphi_scale_cm);
0673
0674 for (int ir = 0; ir < m_nControlR; ir++)
0675 {
0676 const double controlR = control_r(ir);
0677 for (int iphi = 0; iphi < m_nControlPhi; iphi++)
0678 {
0679 const int full_index = control_index(iphi, ir);
0680 if (full_index >= static_cast<int>(m_activeControl.size()) || !m_activeControl[full_index])
0681 {
0682 continue;
0683 }
0684 if (coeff_index >= static_cast<int>(coefficients.size()))
0685 {
0686 break;
0687 }
0688
0689
0690 const double dphi = wrap_delta_phi(phi - control_phi(iphi));
0691 const double dr = r - controlR;
0692 const double rmean = 0.5 * (r + controlR);
0693 const double radial_distance = dr / r_scale;
0694 const double phi_distance = (rmean * dphi) / rphi_scale;
0695 const double dist2 = radial_distance * radial_distance + phi_distance * phi_distance;
0696 if (dist2 <= 1e-12)
0697 {
0698 return coefficients[coeff_index];
0699 }
0700
0701 if (static_cast<int>(neighbors.size()) < max_neighbors)
0702 {
0703 neighbors.emplace_back(dist2, coefficients[coeff_index]);
0704 }
0705 else
0706 {
0707 auto farthest = std::max_element(neighbors.begin(), neighbors.end(), smaller_neighbor_distance);
0708 if (farthest != neighbors.end() && dist2 < farthest->first)
0709 {
0710 *farthest = std::make_pair(dist2, coefficients[coeff_index]);
0711 }
0712 }
0713 coeff_index++;
0714 }
0715 }
0716
0717 if (neighbors.empty())
0718 {
0719 return 0.0;
0720 }
0721
0722
0723
0724 const double power = std::max(0.1, global_field_active_interpolation_power);
0725 double weighted_sum = 0.0;
0726 double weight_sum = 0.0;
0727 for (const auto &neighbor : neighbors)
0728 {
0729 const double dist2 = neighbor.first;
0730 const double value = neighbor.second;
0731 const double dist = std::max(std::sqrt(dist2), 1e-6);
0732 const double weight = 1.0 / std::pow(dist, power);
0733 weighted_sum += weight * value;
0734 weight_sum += weight;
0735 }
0736
0737 if (weight_sum <= 0.0)
0738 {
0739 return 0.0;
0740 }
0741 return weighted_sum / weight_sum;
0742 }
0743
0744 double GlobalFieldFitter::estimate_global_residual_sigma(bool fit_delta_r) const
0745 {
0746 if (!m_isValid || m_observations.empty())
0747 {
0748 return 999.0;
0749 }
0750
0751
0752
0753 std::vector<double> residuals;
0754 residuals.reserve(m_observations.size());
0755 for (const auto &observation : m_observations)
0756 {
0757 const double model = fit_delta_r ? evaluate_delta_r(observation[observation_phi], observation[observation_r]) : evaluate_r_delta_phi(observation[observation_phi], observation[observation_r]);
0758 const double value = fit_delta_r ? observation[observation_delta_r] : observation[observation_r_delta_phi];
0759 residuals.push_back(value - model);
0760 }
0761
0762 return robust_mad_sigma(residuals, fit_delta_r ? fallback_sigma_prior_delta_r_cm : fallback_sigma_prior_r_delta_phi_cm);
0763 }