ScaleAndMerge: Add AI generated sigmas

This commit is contained in:
2026-02-12 17:56:32 +01:00
parent fd8e263934
commit 1dedd3de33
@@ -442,5 +442,79 @@ ScaleMergeResult ScaleAndMergeReflectionsCeres(const std::vector<Reflection>& ob
std::cout << summary.FullReport() << std::endl;
// --- Compute sigma(I_true) from scatter of corrected observations ---
{
// Accumulate per-HKL weighted residuals
// I_corrected_i = I_obs_i / (G_i * partiality_i * lp_i)
// sigma(I_true) = 1 / sqrt(sum_i w_i) where w_i = 1/sigma_corrected_i^2
// OR from sample variance of I_corrected around I_true
struct HKLAccum {
double sum_w = 0.0; // sum of weights
double sum_w_delta2 = 0.0; // sum of w * (I_corr - I_true)^2
int count = 0;
};
std::vector<HKLAccum> accum(nhkl);
const double half_wedge = opt.wedge_deg / 2.0;
for (const auto& o : obs) {
const Reflection& r = *o.r;
const double lp = SafeInv(static_cast<double>(r.rlp), 1.0);
const double G_i = g[o.img_slot];
// Compute partiality with refined mosaicity
double partiality;
size_t mos_slot = opt.per_image_mosaicity ? o.img_slot : 0;
if (refine_partiality && mosaicity[mos_slot] > 0.0) {
double c1 = r.zeta / std::sqrt(2.0);
double arg_plus = (r.delta_phi_deg + half_wedge) * c1 / mosaicity[mos_slot];
double arg_minus = (r.delta_phi_deg - half_wedge) * c1 / mosaicity[mos_slot];
partiality = (std::erf(arg_plus) - std::erf(arg_minus)) / 2.0;
} else {
partiality = r.partiality;
}
if (partiality < 1e-6) partiality = 1e-6;
const double correction = G_i * partiality * lp;
if (correction <= 0.0) continue;
const double I_corr = static_cast<double>(r.I) / correction;
const double sigma_corr = o.sigma / correction;
const double w = 1.0 / (sigma_corr * sigma_corr);
const double delta = I_corr - Itrue[o.hkl_slot];
accum[o.hkl_slot].sum_w += w;
accum[o.hkl_slot].sum_w_delta2 += w * delta * delta;
accum[o.hkl_slot].count++;
}
// GoF^2 (global reduced chi-squared) for rescaling
double global_sum_w_delta2 = 0.0;
double global_sum_w = 0.0;
int global_n = 0;
for (int h = 0; h < nhkl; ++h) {
global_sum_w_delta2 += accum[h].sum_w_delta2;
global_sum_w += accum[h].sum_w;
global_n += accum[h].count;
}
out.gof2 = (global_n > nhkl)
? global_sum_w_delta2 / static_cast<double>(global_n - nhkl)
: 1.0;
for (int h = 0; h < nhkl; ++h) {
if (accum[h].count >= 2 && accum[h].sum_w > 0.0) {
// Internal consistency: sigma^2 = (1/sum_w) * chi^2_h
// where chi^2_h = sum_w_delta2 / (n-1)
double chi2_h = accum[h].sum_w_delta2 / static_cast<double>(accum[h].count - 1);
out.merged[h].sigma = std::sqrt(chi2_h / accum[h].sum_w);
} else if (accum[h].sum_w > 0.0) {
// Single observation: use propagated sigma
out.merged[h].sigma = std::sqrt(1.0 / accum[h].sum_w);
}
// else: sigma stays 0.0 (no usable data)
}
}
return out;
}