From 5c7cd2510f391c9b7cb36fa16cfa5134359a7ea4 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 5 Nov 2026 15:21:05 +0100 Subject: [PATCH] Refinement: write the anomalous difference map's coefficients to the refined maps The refinement made the anomalous difference map on the refined model's phases (for the blob finder and the solvent audit) but did not write it: _refined_maps.mtz carried FP..FREE only, while the validation's _maps.mtz (and _mr_maps.mtz) carried ANOM/PHANOM too. Where the merge kept the Bijvoet pairs, the refined maps now carry ANOM and PHANOM as well, the same coefficients the refinement reads (F(+) - F(-) on the model phase turned back by 90 degrees), missing (NaN) on reflections without a pair - the layout of the validation's file. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UqXYdL4qBGYRiSXSGXfCLu --- docs/CPU_DATA_ANALYSIS_REFINEMENT.md | 3 ++- docs/RUGNUX_MODEL_AND_REFERENCE.md | 2 +- .../structure_refinement/Refinement.cpp | 18 ++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/CPU_DATA_ANALYSIS_REFINEMENT.md b/docs/CPU_DATA_ANALYSIS_REFINEMENT.md index 830f3aec5..b136b6ed0 100644 --- a/docs/CPU_DATA_ANALYSIS_REFINEMENT.md +++ b/docs/CPU_DATA_ANALYSIS_REFINEMENT.md @@ -356,7 +356,8 @@ The refined model's map coefficients, `_refined_maps.mtz`, are $2mF_o - $\sigma_A$ of §14.4, so the maps carry the weights the target was minimised with; $m$ is $I_1(X)/I_0(X)$, $X = 2F_oD|F_\mathrm{model}|/\Sigma$, for an acentric reflection and $\tanh(X/2)$ for a centric one. Where the merge kept the Bijvoet pairs, the anomalous difference map is made on the refined -model's phases too, and read by the blob finder below. +model's phases too, written in the same file as `ANOM`/`PHANOM` (as in `_maps.mtz`; missing where a +reflection has no Bijvoet pair), and read by the blob finder below. ### 15.9 Unexplained density: blobs diff --git a/docs/RUGNUX_MODEL_AND_REFERENCE.md b/docs/RUGNUX_MODEL_AND_REFERENCE.md index cde751bfe..c67389d9b 100644 --- a/docs/RUGNUX_MODEL_AND_REFERENCE.md +++ b/docs/RUGNUX_MODEL_AND_REFERENCE.md @@ -440,7 +440,7 @@ the data, a weak shell says nothing about them, and nothing acts on it. |---|---|---| | `.mtz` / `.cif` / `.hkl` | The answer: the data's space group, with only the class-(b) labels from the model | Refinement, deposition, everything by default | | `_model.cif` / `.pdb`, `_maps.mtz` | The model as placed, in the frame of `.mtz`, and its map coefficients (Coot/PyMOL open them as maps) | Starting refinement (REFMAC5, phenix.refine), PanDDA, Coot | -| `_refined_model.cif`/`.pdb`, `_refined_maps.mtz`, `_blobs.json` | The placed model refined by rugnux, its map coefficients, and the unexplained blobs of its difference map | A first look at the refined density and at what the model leaves unexplained; continuing refinement | +| `_refined_model.cif`/`.pdb`, `_refined_maps.mtz`, `_blobs.json` | The placed model refined by rugnux, its map coefficients (with the anomalous difference map's, `ANOM`/`PHANOM`, where the merge kept the Bijvoet pairs, as in `_maps.mtz`), and the unexplained blobs of its difference map | A first look at the refined density and at what the model leaves unexplained; continuing refinement | | `_twin_.mtz` / `.cif` | The same data merged in a subgroup with a twin law — written from the data, model or not | Twin refinement, where you suspect the higher group absorbed a twin law | | `_mr.mtz` / `.cif`, `_mr_model.pdb` / `.cif`, `_mr_*` maps | The merge in the space group the model chose (`MODEL_CHOICE_SPACE_GROUP=`), and the model placed there | Refinement *if* you accept the model's choice; compare R-free against the answer's | diff --git a/image_analysis/structure_refinement/Refinement.cpp b/image_analysis/structure_refinement/Refinement.cpp index 4c31dc9b4..e2449d33a 100644 --- a/image_analysis/structure_refinement/Refinement.cpp +++ b/image_analysis/structure_refinement/Refinement.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -1252,12 +1253,25 @@ void MakeMaps(Context &ctx, const std::vector &merged, double gemmi::AsuData> anom; anom.unit_cell_ = ctx.cell; anom.spacegroup_ = ctx.sg; + // Its coefficients in the maps file too (ANOM/PHANOM, as model validation writes them), missing (NaN) where + // the reflection has no Bijvoet pair. + const float nan = std::numeric_limits::quiet_NaN(); + MtzTable::Column anom_col{"ANOM", 'F', 1, {}}, phanom_col{"PHANOM", 'P', 1, {}}; for (size_t m = 0; m < ctx.refl.Size(); m++) { const auto it = danom.find(ctx.refl.hkl[m]); - if (it != danom.end()) - anom.v.push_back({ctx.refl.hkl[m], it->second * std::polar(1.0f, static_cast(std::arg(fmodel[m]) - PI / 2))}); + if (it == danom.end()) { + anom_col.values.push_back(nan); + phanom_col.values.push_back(nan); + continue; + } + const std::complex c = it->second * std::polar(1.0f, static_cast(std::arg(fmodel[m]) - PI / 2)); + anom.v.push_back({ctx.refl.hkl[m], c}); + anom_col.values.push_back(std::abs(c)); + phanom_col.values.push_back(MtzPhase(std::arg(c) * 180.0 / PI)); } if (!anom.v.empty()) { + mtz.columns.push_back(std::move(anom_col)); + mtz.columns.push_back(std::move(phanom_col)); result.anomalous_map = std::make_shared>( MapFromFPhi(gemmi::get_f_phi_on_grid(anom, gemmi::get_size_for_hkl(anom, {{0, 0, 0}}, 3.0), true))); result.anomalous_map_rms = gemmi::calculate_data_statistics(result.anomalous_map->data).rms;