rugnux: report anomalous signal-to-noise SigAno (stats table + mmCIF)
Compute SigAno = <|I(+)-I(-)|>/<sigma(I(+)-I(-))> per report shell and overall from the full-multiplicity I(+)/I(-) Bijvoet split, store it on MergeStatisticsShell, and surface it in the console merge-statistics table (new SigAno column) and the mmCIF via the standard wwPDB PDBx items _reflns.pdbx_absDiff_over_sigma_anomalous (overall) and _reflns_shell.pdbx_absDiff_over_sigma_anomalous (per shell) - the dictionary's home for this quantity, so no jfjoch_ local item is needed (unlike ISa). SigAno approaches ~0.8 for pure noise and rises above 1 with real anomalous signal. A half-set anomalous CC is deliberately not used: its two half estimates are complementary partitions of one observation pool (dI0 + dI1 = 2*dI_full), so differencing the two Bijvoet hands cancels the large common intensity that keeps CC1/2 non-negative and, once the anomalous SNR per half drops below 1, drives the correlation towards -1 rather than 0 - misrepresenting a weak-but-real signal. Emitted only when an anomalous split was made (has_anom guard), so a non-anomalous merge keeps its stats block and shell-loop columns byte-for-byte. Export-neutral: the I(+)/I(-)/IMEAN accumulation is unchanged. On low-energy S-SAD test data SigAno tracks XDS (13 keV null ~0.89, 6 keV ~1.44, 5 keV ~1.5-1.7) and never goes spuriously negative. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,9 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
|
||||
? 100.0 * static_cast<double>(s.unique_reflections) / s.possible_unique_reflections : 0.0; };
|
||||
if (!statistics.shells.empty()) {
|
||||
const auto &ov = statistics.overall;
|
||||
// Anomalous signal-to-noise (SigAno) is written only when an anomalous split was made, so a
|
||||
// non-anomalous merge keeps its previous stats block / shell-loop columns unchanged.
|
||||
const bool has_anom = std::isfinite(ov.abs_diff_over_sigma_anomalous);
|
||||
out << "_reflns.d_resolution_high " << Fmt(ov.d_min, 2) << "\n";
|
||||
out << "_reflns.d_resolution_low " << Fmt(ov.d_max, 2) << "\n";
|
||||
out << "_reflns.number_obs " << ov.unique_reflections << "\n";
|
||||
@@ -144,6 +147,9 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
|
||||
out << "_reflns.pdbx_netI_over_sigmaI " << Fmt(ov.mean_i_over_sigma, 2) << "\n";
|
||||
out << "_reflns.pdbx_Rrim_I_all " << Fmt(ov.r_meas, 4) << "\n";
|
||||
out << "_reflns.pdbx_CC_half " << Fmt(ov.cc_half, 4) << "\n";
|
||||
if (has_anom)
|
||||
out << "_reflns.pdbx_absDiff_over_sigma_anomalous " << Fmt(ov.abs_diff_over_sigma_anomalous, 3)
|
||||
<< " # SigAno = <|dano|>/<sigma(dano)>\n";
|
||||
out << "_reflns.jfjoch_diffrn_ISa " << CifStr(isa) << " # asymptotic I/sigma (Diederichs)\n";
|
||||
// Dataset-wide isotropic Wilson B-factor estimate (standard PDBx item), analogous to XDS's
|
||||
// "WILSON LINE ... B=". Emitted only when the log-linear fit succeeded.
|
||||
@@ -171,13 +177,18 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
|
||||
out << "_reflns_shell.meanI_over_sigI_obs\n";
|
||||
out << "_reflns_shell.pdbx_Rrim_I_all\n";
|
||||
out << "_reflns_shell.pdbx_CC_half\n";
|
||||
if (has_anom)
|
||||
out << "_reflns_shell.pdbx_absDiff_over_sigma_anomalous\n";
|
||||
for (const auto &s : statistics.shells) {
|
||||
if (s.unique_reflections == 0)
|
||||
continue;
|
||||
out << Fmt(s.d_min, 2) << " " << Fmt(s.d_max, 2) << " "
|
||||
<< s.total_observations << " " << s.unique_reflections << " "
|
||||
<< Fmt(mult(s), 2) << " " << Fmt(compl_pct(s), 1) << " "
|
||||
<< Fmt(s.mean_i_over_sigma, 2) << " " << Fmt(s.r_meas, 4) << " " << Fmt(s.cc_half, 4) << "\n";
|
||||
<< Fmt(s.mean_i_over_sigma, 2) << " " << Fmt(s.r_meas, 4) << " " << Fmt(s.cc_half, 4);
|
||||
if (has_anom)
|
||||
out << " " << Fmt(s.abs_diff_over_sigma_anomalous, 3);
|
||||
out << "\n";
|
||||
}
|
||||
out << "#\n";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user