rugnux: --model names the strongest anomalous scatterers, not just R-free

Where the merge kept the Bijvoet split - which the default Friedel-averaged
merge does, so this needs no -A - --model now computes an anomalous difference
Fourier, coefficients F(+)-F(-) on the model phase turned back by 90 degrees
over the acentric reflections, writes it as <prefix>_anom.ccp4, and reads it at
the model's own atom centres. The ten highest are logged and land in the results
report as ANOMALOUS_SITE_01..10, each named by the atom, residue and chain it
sits on, with its height in map sigma. Reading at the sites rather than
searching the map for blobs is what ANODE does, and it says what carries the
signal instead of leaving coordinates to look up; a scatterer the model does not
contain is by construction absent from the list, which is what the map file is
for.

The list is always ten long, so its information is in the heights. On an
S-SAD dataset at 5 keV the six cysteine sulfurs take ranks 1-6 (10.1 down to
4.2 sigma) and rank 7 falls to 2.2; the same crystal at 13 keV, where sulfur has
no anomalous signal to speak of, tops out at 2.7 sigma with a water and a main
chain carbon inside the top five. So the same list reads as a signal / no-signal
gate, not only as a ranking.

The reading is cubic. Grid::interpolate_value defaults to trilinear, and on the
d_min/3 map grid that under-reads a peak this sharp by up to a quarter, unevenly
enough to reorder the sites; cubic on the same grid is within about 2% of a
sample-rate-10 one. The map r.m.s. is unchanged by the sample rate, so the
normalisation was never at fault. The 2Fo-Fc reading behind
MEAN_ATOM_DENSITY_SIGMA is deliberately left on the default so a number already
in existing reports does not move.

An anomalous merge keeps each Bijvoet mate as a row of its own and both rows
carry the same F(+)/F(-) pair, attached to the + index of the Friedel ASU of the
frame the merge was made in, so the differences are read from the merged
reflections as they came in - carried into the model's frame here, on the +
rows only. Taking the - rows as well would give one reflection both signs of its
difference and let the last row written decide.

write_ccp4 splits into an FFT and a write so the anomalous map can be both
written and sampled from one transform; the 2Fo-Fc atom-centre score, which used
to repeat that FFT, now reuses the grid.

Cross-checked against ANODE run on this output: identical identification - its
three peaks are the three disulfides, each maximum 0.64-0.70 A off a sulfur we
name - with our heights about 1.35x lower. Sharpening, grid and sigma
normalisation are excluded; the residual is SHELXC's FA preparation, which fed
ANODE 2166 reflections against our 2534.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zTy4Bpi4pPHw4bybf7q2R
This commit is contained in:
2026-08-27 14:51:06 +02:00
co-authored by Claude Opus 5
parent bc500d1578
commit b0234a1b4b
7 changed files with 149 additions and 19 deletions
+7
View File
@@ -119,6 +119,13 @@ Cryst. D59, 1145-1153 [doi:10.1107/S0907444903008163](https://doi.org/10.1107/S0
P. R. Evans and G. N. Murshudov, "How good are my data and what is the resolution?" (2013), Acta
Cryst. D69, 1204-1214 [doi:10.1107/S0907444913000061](https://doi.org/10.1107/S0907444913000061).
**[ANODE](https://doi.org/10.1107/S0021889811041768)** — reading the anomalous difference map at the
atoms of a supplied model and reporting the strongest sites by name, instead of searching the map for
blobs. The map itself is the textbook anomalous difference Fourier; what is taken from ANODE is that
reading: A. Thorn and G. M. Sheldrick, "ANODE: anomalous and heavy-atom density calculation" (2011),
J. Appl. Cryst. 44, 1285-1287
[doi:10.1107/S0021889811041768](https://doi.org/10.1107/S0021889811041768).
**Data-quality statistics** follow the established conventions rather than any one program: R_meas
and R_pim, CC1/2 and CC\*, and the reporting of I/sigma(I). K. Diederichs and P. A. Karplus, "Improved
R-factors for diffraction data analysis in macromolecular crystallography" (1997), Nat. Struct. Biol.
+1
View File
@@ -3,6 +3,7 @@
### 1.0.0-rc.165
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.
* `rugnux --model` writes an anomalous difference map (`<prefix>_anom.ccp4`) when the merge kept the Bijvoet split, and names the ten model atoms it peaks highest on - `ANOMALOUS_SITE_01`..`_10` in the results report, and one line in the log - so a run says which atoms carry the anomalous signal and how many sigma each one is.
* The rugnux results report records how the run was invoked, what it cost and what it ran on: `COMMAND_LINE=` is the command line as one shell-ready line, `WALL_TIME=` the whole invocation in seconds, and `GPU_COUNT=` / `GPU=` how many GPUs were visible and which ones. The total wall time is also printed on stdout, next to the processing time it is slightly larger than.
* The rugnux results report has a `10. MODEL VALIDATION` section when `--model` was given: R-work / R-free with their reflection counts, the bulk-solvent and scale parameters, the mean 2Fo-Fc density at the atom centres, the reindexing operators and where the maps were written - or `MODEL_VALIDATION= NOT_PERFORMED` and the reason, when the model could not be used. `REPORT_VERSION` is 4; `WARNINGS` moves from section 10 to 11 and no existing key changed.
* `rugnux --model` reads an mmCIF coordinate file as well as a PDB one, gzipped or not, taking the format from the file's content rather than its name. A `.cif` model was rejected before, and the run then finished successfully with no R-free and no maps.
+16 -1
View File
@@ -17,7 +17,7 @@ This document describes the crystallographic algorithms implemented in Jungfrauj
11. space-group determination from the merged intensities (Laue group, screw axes, centering) and the twinning check,
12. auxiliary statistics (Wilson plot, ⟨I/σ(I)⟩, CC1/2, CCref),
13. amplitude estimation (FrenchWilson) and R-free test-set flagging,
14. optional model-based validation: R-free against a supplied model and 2FoFc / FoFc electron-density maps.
14. optional model-based validation: R-free against a supplied model, 2FoFc / FoFc electron-density maps, and an anomalous difference map with the strongest anomalous sites named.
```{contents} On this page
:local:
@@ -37,6 +37,7 @@ The methods draw on, and in places reimplement, solutions from:
- A. T. Brünger, "Free R value: a novel statistical quantity for assessing the accuracy of crystal structures", *Nature* **355** (1992), 472-475 (R-free cross-validation).
- M. Wojdyr, "GEMMI: A library for structural biology", *J. Open Source Softw.* **7** (2022), 4200 (model / structure-factor / map machinery used in §14).
- J. P. Wright, "Experiences with GPU decompression for bitshuffle + LZ4 data", HDF5 User Group meeting (2021), and [github.com/jonwright/bslz4decoders](https://github.com/jonwright/bslz4decoders) (device-side decoding of bitshuffle+LZ4 images, §0).
- A. Thorn & G. M. Sheldrick, "ANODE: anomalous and heavy-atom density calculation", *J. Appl. Cryst.* **44** (2011), 1285-1287 (anomalous difference density read at the model's sites).
- Z. Otwinowski & W. Minor, "Processing of X-ray diffraction data collected in oscillation mode", *Methods Enzymol.* **276** (1997), 307-326 (reweighted, de-biased profile-fit variances).
- G. Winter et al., "DIALS: implementation and evaluation of a new integration package", *Acta Cryst.* **D74** (2018), 85-97, and J. Beilsten-Edmands et al., *Acta Cryst.* **D76** (2020), 385-399 (CC1/2 resolution cutoff, merge outlier rejection, scaling error model).
- P. Evans, "Scaling and assessment of data quality", *Acta Cryst.* **D62** (2006), 72-82, and P. R. Evans, *Acta Cryst.* **D67** (2011), 282-292 (POINTLESS: operator-by-operator point-group scoring, and the axial-zone screw-absence test).
@@ -1080,3 +1081,17 @@ The model fixes a definite hand and indexing, but the merged data need not share
- **Enantiomorph / screw.** When the data space group is the enantiomorph of the model's (e.g. data $P4_12_12$, model $P4_32_12$; or $P3_1/P3_2$), the two are **indistinguishable from merged intensities** — $|F_\mathrm{calc}|$ is invariant under the change of hand, so R-free cannot choose between them and probing would be meaningless. The hand is therefore taken from the model: the observed reflections are reindexed by the change-of-hand operator into the model's enantiomorph. Only the map phases (the density's hand) depend on this choice.
- **Indexing (merohedral) ambiguity.** When the crystal has a merohedral ambiguity (§10.9), the observed intensities *do* differ between indexings, and the right one is chosen against the best available reference. **If a reference MTZ was supplied, the data were already reindexed to agree with it** (§10.9 — by the reference-intensity correlation, at the merge stage for rotation data or per image in stills scaling), and model validation keeps that authoritative choice. **Only with a model and no reference** does validation resolve the ambiguity itself, as a fallback: the scaled model is fit to each reindexing of the data (identity plus the twin-law cosets) and the one giving the **lowest R-free** is kept. This matters for a multi-dataset campaign — a single shared reference fixes one indexing convention for every dataset, whereas an independent per-dataset lowest-R-free choice could send borderline datasets to different conventions. A no-op either way for a holohedral crystal (no twin laws) Both reindexings — the change of hand and the ambiguity choice — are then applied to the merged reflections themselves, which are written after this step, so the reflection file, the R-factors and the maps describe one indexing. The change of hand also changes the space group the file is written in (it is the model's enantiomorph), and since it is the inversion it exchanges the Bijvoet mates: on anomalous data adopting a model's hand is what puts the anomalous differences the right way round. The ambiguity choice is reported with the R-free of the winner and of the runner-up, since the margin between them is what says whether the data decided or the two came out within noise of each other.
### 14.6 Anomalous difference map and the sites it names
Where the merge kept the Bijvoet split (§10.5) — which a rotation merge does by default, whether or not the mates were averaged — an **anomalous difference map** is computed as well, with coefficients
$
\big(|F(+)| - |F(-)|\big)\, e^{i(\varphi_\mathrm{model} - \pi/2)},
$
over the acentric reflections that have both hands (a centric reflection has no anomalous difference, only noise). Turning the model phase back by 90° is what makes the anomalous scattering, which is 90° out of phase with the normal scattering, add up in the real part: the map's peaks then sit on the anomalous scatterers. It is written as `<prefix>_anom.ccp4`. Its hand is the one §14.5 settled: with the mates the wrong way round every peak becomes a trough, so a map of clean peaks is itself a check that the frame is right.
Rather than searching the map for blobs and leaving a list of coordinates, the map is read **at the model's own atom centres** (hydrogens excluded — they scatter no anomalous signal), and the ten highest, in units of the map's r.m.s., are reported in the log and as `ANOMALOUS_SITE_01`…`ANOMALOUS_SITE_10` in the results report. Each site is therefore named — the atom, residue and chain it belongs to — which is what says *what* carries the signal, not just where it is. The reading is cubic, not linear: the map is sampled every $d_\mathrm{min}/3$, and a peak that sharp read by trilinear interpolation comes out up to a quarter low — unevenly enough to reorder the sites. This reading is the one ANODE reports.
The list is always ten entries long, so it is their height that carries the information: on a sulfur-SAD dataset the sulfurs fill the top of the list and are followed by a clear drop to the couple of sigma that is the map's noise, while a dataset with no anomalous signal has no such separation and lists ten unrelated atoms at noise level. A scatterer the **model does not contain** — a bound ion, a soaked heavy atom — is by construction invisible in the list, and is what the map file is for.
+99 -17
View File
@@ -33,12 +33,15 @@ long hkl_key(const gemmi::Miller &h) {
return (h[0] + 512L) * 1048576 + (h[1] + 512L) * 1024 + (h[2] + 512L);
}
// Write an FFT of ASU map coefficients as a CCP4 map; return its RMS (for reporting / sigma units).
double write_ccp4(gemmi::AsuData<std::complex<float>> &coef, const std::string &path) {
// FFT ASU map coefficients into a real-space map.
gemmi::Grid<float> map_from_coefficients(gemmi::AsuData<std::complex<float>> &coef) {
coef.ensure_sorted();
std::array<int, 3> size = gemmi::get_size_for_hkl(coef, {{0, 0, 0}}, 3.0);
gemmi::Grid<float> map =
gemmi::transform_f_phi_grid_to_map(gemmi::get_f_phi_on_grid<float>(coef, size, true));
return gemmi::transform_f_phi_grid_to_map(gemmi::get_f_phi_on_grid<float>(coef, size, true));
}
// Write a map as CCP4; return its RMS (the sigma the map is read in).
double write_ccp4(const gemmi::Grid<float> &map, const std::string &path) {
gemmi::Ccp4<float> ccp4;
ccp4.grid = map;
ccp4.update_ccp4_header(2);
@@ -46,6 +49,10 @@ double write_ccp4(gemmi::AsuData<std::complex<float>> &coef, const std::string &
return ccp4.hstats.rms;
}
// How many anomalous sites the report names. The strongest few are what says whether the anomalous
// signal is there and what carries it; a full site list is what the map file is for.
constexpr size_t MAX_ANOMALOUS_SITES = 10;
} // namespace
ModelValidationResult ValidateAgainstModel(const std::vector<MergedReflection> &merged,
@@ -270,25 +277,91 @@ ModelValidationResult ValidateAgainstModel(const std::vector<MergedReflection> &
result.k_overall = best.k_overall;
// --- write the maps and score the 2Fo-Fc map at atom centres (a real map peaks there) ---
const std::string p2 = output_prefix + "_2fofc.ccp4";
const std::string pd = output_prefix + "_fofc.ccp4";
double rms2 = write_ccp4(map2fofc, p2);
write_ccp4(mapfofc, pd);
// Recompute the 2Fo-Fc real-space grid once more to sample it at atom positions.
const gemmi::Grid<float> grid2fofc = map_from_coefficients(map2fofc);
const double rms2 = write_ccp4(grid2fofc, output_prefix + "_2fofc.ccp4");
write_ccp4(map_from_coefficients(mapfofc), output_prefix + "_fofc.ccp4");
{
map2fofc.ensure_sorted();
std::array<int, 3> size = gemmi::get_size_for_hkl(map2fofc, {{0, 0, 0}}, 3.0);
gemmi::Grid<float> grid =
gemmi::transform_f_phi_grid_to_map(gemmi::get_f_phi_on_grid<float>(map2fofc, size, true));
double s = 0; int n = 0;
for (gemmi::Model &m : st.models)
for (gemmi::Chain &ch : m.chains)
for (gemmi::Residue &r : ch.residues)
for (gemmi::Atom &a : r.atoms) { s += grid.interpolate_value(a.pos); ++n; }
for (gemmi::Atom &a : r.atoms) { s += grid2fofc.interpolate_value(a.pos); ++n; }
result.mean_atom_density_sigma = (n > 0 && rms2 > 0) ? (s / n) / rms2 : 0;
}
// --- anomalous difference map, where the merge kept the Bijvoet split ---
// Coefficients F(+) - F(-) carried on the model phase turned back by 90 degrees. Its peaks sit on
// the anomalous scatterers, so reading the map at each of the model's own atoms names them,
// rather than leaving a list of coordinates for someone to look up.
// Following ANODE, Thorn & Sheldrick (2011) J. Appl. Cryst. 44, 1285-1287
{
// Read from the merged reflections as they came in, and carry each one into the model's frame
// here: the hand each Bijvoet difference belongs to is a property of the frame the merge was
// made in, and both operators can change it. F(+) and F(-) are attached to the + index of the
// Friedel ASU of that frame, so an anomalous merge - which keeps each mate as a row of its own,
// both carrying the same pair - is read on its + rows only. Taking the - rows as well would
// give one reflection both signs of its difference, and the last row written would decide.
const gemmi::SpaceGroup *data_sg = data_space_group_number
? gemmi::find_spacegroup_by_number(*data_space_group_number) : nullptr;
if (data_sg == nullptr)
data_sg = sg;
const gemmi::ReciprocalAsu data_asu(data_sg);
const gemmi::GroupOps data_gops = data_sg->operations();
std::unordered_map<long, float> danom_by_hkl;
for (const MergedReflection &r : merged) {
if (!std::isfinite(r.F_plus) || !std::isfinite(r.F_minus))
continue;
gemmi::Op::Miller h{{r.h, r.k, r.l}};
if (data_gops.is_reflection_centric(h)) // a centric reflection has no anomalous difference
continue;
if (!data_asu.to_asu_sign(h, data_gops).second)
continue;
if (!(result.hand_op == gemmi::Op::identity()))
h = result.hand_op.apply_to_hkl(h);
if (!(result.indexing_op == gemmi::Op::identity()))
h = result.indexing_op.apply_to_hkl(h);
const auto [hasu, plus] = asu.to_asu_sign(h, gops);
danom_by_hkl[hkl_key(hasu)] = plus ? r.F_plus - r.F_minus : r.F_minus - r.F_plus;
}
gemmi::AsuData<std::complex<float>> mapanom;
mapanom.unit_cell_ = ucell;
mapanom.spacegroup_ = sg;
for (const auto &hv : fmodel.v) {
const auto it = danom_by_hkl.find(hkl_key(hv.hkl));
if (it == danom_by_hkl.end())
continue;
const auto phi = static_cast<float>(std::arg(hv.value) - M_PI / 2);
mapanom.v.push_back({hv.hkl, it->second * std::polar(1.0f, phi)});
}
result.anomalous_pairs = static_cast<int>(mapanom.v.size());
if (!mapanom.v.empty()) {
const gemmi::Grid<float> grid = map_from_coefficients(mapanom);
const double rms = write_ccp4(grid, output_prefix + "_anom.ccp4");
std::vector<ModelValidationResult::AnomalousSite> sites;
for (gemmi::Model &m : st.models)
for (gemmi::Chain &ch : m.chains)
for (gemmi::Residue &r : ch.residues)
for (gemmi::Atom &a : r.atoms) {
if (a.is_hydrogen()) // hydrogen scatters no anomalous signal
continue;
// Cubic, not the default linear: the map is sampled every d_min/3, and a
// peak that sharp read by trilinear interpolation comes out up to a quarter
// low - unevenly enough to reorder the sites.
sites.push_back({fmt::format("{} {} {}{}", a.name, r.name, ch.name,
r.seqid.str()),
rms > 0 ? grid.interpolate_value(a.pos, 3) / rms : 0.0});
}
std::sort(sites.begin(), sites.end(),
[](const auto &x, const auto &y) { return x.sigma > y.sigma; });
if (sites.size() > MAX_ANOMALOUS_SITES)
sites.resize(MAX_ANOMALOUS_SITES);
result.anomalous_sites = std::move(sites);
}
}
// --- MTZ of map coefficients so the maps can be re-opened / rebuilt in Coot etc. ---
try {
gemmi::Mtz mtz(true);
@@ -335,8 +408,17 @@ ModelValidationResult ValidateAgainstModel(const std::vector<MergedReflection> &
logger.Info("Model validation: bulk solvent k_sol={:.3f} b_sol={:.1f}, k_overall={:.3f}",
result.k_sol, result.b_sol, result.k_overall);
logger.Info("Model validation: mean 2Fo-Fc density at atom centres = {:.2f} sigma", result.mean_atom_density_sigma);
logger.Info("Model validation: wrote {}_2fofc.ccp4, {}_fofc.ccp4, {}_maps.mtz",
output_prefix, output_prefix, output_prefix);
if (!result.anomalous_sites.empty()) {
std::string sites;
for (const auto &s : result.anomalous_sites)
sites += fmt::format("{}{} {:.1f}", sites.empty() ? "" : ", ", s.label, s.sigma);
logger.Info("Model validation: anomalous difference map from {} Bijvoet pairs; strongest "
"density at the model's atoms (sigma): {}", result.anomalous_pairs, sites);
}
logger.Info("Model validation: wrote {}_2fofc.ccp4, {}_fofc.ccp4{}, {}_maps.mtz",
output_prefix, output_prefix,
result.anomalous_sites.empty() ? "" : ", " + output_prefix + "_anom.ccp4",
output_prefix);
return result;
}
+9
View File
@@ -32,6 +32,15 @@ struct ModelValidationResult {
double mean_atom_density_sigma = 0.0; // mean 2Fo-Fc value at atom centres, in sigma
std::string maps_prefix; // where the .ccp4 / _maps.mtz were written
// The anomalous difference map read at the model's own atoms: the strongest sites, highest
// first. Empty when the merge kept no Bijvoet split, and so had nothing to make the map from.
struct AnomalousSite {
std::string label; // atom, residue and chain, e.g. "SG CYS A7"
double sigma = 0.0; // height of the anomalous map at that atom, in map sigma
};
std::vector<AnomalousSite> anomalous_sites;
int anomalous_pairs = 0; // Bijvoet pairs the anomalous map was computed from
// What had to be done to the observed reflections to bring them into the model's frame, in this
// order: the change of hand, where the data were merged in the model's enantiomorph, and then the
// alternative-indexing operator picked by R-free. Identity where nothing was needed. AdoptModelFrame
+16
View File
@@ -364,6 +364,15 @@ std::string RenderResultReport(const std::string &output_prefix,
Key(os, "BULK_SOLVENT_B_SOL", fmt::format("{:.1f}", mv.b_sol));
Key(os, "SCALE_OVERALL", fmt::format("{:.4f}", mv.k_overall));
Key(os, "MEAN_ATOM_DENSITY_SIGMA", fmt::format("{:.2f}", mv.mean_atom_density_sigma));
// The anomalous scatterers the data themselves found, named by the model's atoms.
if (!mv.anomalous_sites.empty()) {
Key(os, "ANOMALOUS_BIJVOET_PAIRS", mv.anomalous_pairs);
for (size_t i = 0; i < mv.anomalous_sites.size(); i++)
// Two digits so the ten keys are the same width and the values line up.
Key(os, fmt::format("ANOMALOUS_SITE_{:02}", i + 1).c_str(),
fmt::format("{:<18} {:6.2f} sigma", mv.anomalous_sites[i].label,
mv.anomalous_sites[i].sigma));
}
// The relabelings that were applied to the written reflections, so a reader can tell
// whether the file is in the indexing it was merged in or in the model's.
Key(os, "MODEL_HAND_OPERATOR", mv.hand_op.triplet());
@@ -375,6 +384,13 @@ std::string RenderResultReport(const std::string &output_prefix,
<< " model is not refined: it is scaled to the data with a flat bulk solvent and an overall\n"
<< " anisotropic B, so these R-factors are higher than a refined structure's and are a\n"
<< " data-quality reading, not a refinement result.\n";
if (!mv.anomalous_sites.empty())
os << "\n The anomalous sites are the highest peaks of the anomalous difference map -\n"
<< " F(+)-F(-) on the model phase turned back by 90 degrees - read at the model's own\n"
<< " atom centres, so each one is named rather than left as a coordinate. A dataset with\n"
<< " no anomalous signal still lists ten sites: it is their height, a few sigma at most,\n"
<< " that says so. The map itself is written as _anom.ccp4, where a scatterer the model\n"
<< " does not contain would show up as a peak on nothing.\n";
if (!(mv.hand_op == gemmi::Op::identity()) || !(mv.indexing_op == gemmi::Op::identity()))
os << "\n The written reflections were reindexed into the model's frame - the operators above\n"
<< " say how - so the reflection files, the R-factors and the maps all describe one\n"
+1 -1
View File
@@ -164,7 +164,7 @@ void print_usage() {
std::cout << " --scaling-iterations <num> Number of scaling iterations with no reference data (default: 3)" << std::endl;
std::cout << " -z, --reference-mtz <file> Reference MTZ file" << std::endl;
std::cout << " --reference-column <label> Reference MTZ column to use (default: auto - F-model, else IMEAN/I, else FP/FOBS/F)" << std::endl;
std::cout << " --model <file> After merging, validate vs this atomic model: R-free + 2Fo-Fc/Fo-Fc maps. PDB or mmCIF, gzipped or not, recognised by content rather than by extension" << std::endl;
std::cout << " --model <file> After merging, validate vs this atomic model: R-free, 2Fo-Fc/Fo-Fc maps, and - where the merge kept the Bijvoet split - an anomalous difference map with the strongest anomalous scatterers named by the atom they sit on. PDB or mmCIF, gzipped or not, recognised by content rather than by extension" << std::endl;
std::cout << " --no-export-unmerged Do not write <prefix>_unmerged.mtz. It is written by default: an unmerged MTZ (POINTLESS column layout) of the integrated observations, for aimless / pointless / careless. On a rotation run the partials of each reflection are summed into one full, written at the batch its rocking curve is centred on, with the summed rocking-curve fraction in FRACTIONCALC; an event that caught less of its rocking curve than --min-partiality is not written, as in the merge. Intensities carry the Lorentz-polarization factor and nothing else: the partiality is not divided out and the per-image scale is not applied at all, since those programs scale the data themselves. Written in --mode mx and --mode scale, and with --no-merge" << std::endl;
std::cout << " --export-unmerged-partials Also write <prefix>_unmerged_partials.mtz, the same observations with each partial as its own row (one batch per image) flagged for the reading program to sum, instead of summed here" << std::endl;
std::cout << std::endl;