From 47ec939c39d1320501d2f6a319de6908bc0c215f Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 2 Sep 2026 09:03:26 +0200 Subject: [PATCH] merged MTZ: FreeR_flag in the convention its own column name carries The column was written with 1 for the test set and 0 for the working set - the phenix/CNS numbering - under FreeR_flag, which is CCP4's column name. CCP4's own freerflag writes 0..19 with 0 as the test bin, and REFMAC5's default FREE 0 reads it that way, so REFMAC5 stopped on every file we wrote: "more than half of reflections are in free R set", then "Cannot switch free R flag", exit 1. phenix auto-detects either numbering with equal confidence (measured on both, score 3 each), and rugnux's own reference-MTZ reader already takes flag 0 as the test set, so 0 = test is the numbering that works everywhere and the one the rest of the code assumes. This changes every merged .mtz we write: a script that reads FreeR_flag == 1 as the test set has to be inverted. The mmCIF's _refln.status_free is a separate item with its own convention and is unchanged. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N --- docs/CHANGELOG.md | 1 + image_analysis/WriteReflections.cpp | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1a295feda..19cb14139 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ * `rugnux` writes the unmerged MTZ by default, with a P1 merge beside it, so a wrong space group can be re-merged without reprocessing. * `rugnux` writes a batch header in the unmerged MTZ for every image the observations span, not only for the images that produced one, so a scaling program reads one run per sweep. * `rugnux` leaves an event out of the unmerged MTZ when less of its rocking curve was captured than `--min-captured-fraction`, as the merge does, since the exported rows are declared full. +* `rugnux` writes `FreeR_flag` with 0 for the test set and 1 for the working set, the CCP4 convention REFMAC5 defaults to; it was the other way round. * `rugnux` handles symmetry better: the lattice, the point group, the setting and the systematic absences. * `rugnux` fits the direction of the goniometer axis and not its length, so the cell chosen by the first pass is the one its own refinement scored. * `rugnux` reports the detector geometry it measured - the direct beam, the tilt and the beam centre - and what a single sweep can and cannot determine. diff --git a/image_analysis/WriteReflections.cpp b/image_analysis/WriteReflections.cpp index f908d59f2..35ecc1678 100644 --- a/image_analysis/WriteReflections.cpp +++ b/image_analysis/WriteReflections.cpp @@ -74,7 +74,14 @@ struct MergedOutRow { int h = 0, k = 0, l = 0; float Imean = NAN, sImean = NAN, Ip = NAN, sIp = NAN, Im = NAN, sIm = NAN; float Fmean = NAN, sFmean = NAN, Fp = NAN, sFp = NAN, Fm = NAN, sFm = NAN; - int rfree = 0; + // The value written into FreeR_flag: 0 for the TEST set, 1 for the working set. That is the CCP4 + // convention the column NAME belongs to - CCP4's own freerflag writes 0..19 with 0 as the ~5% + // test bin, and REFMAC's default FREE 0 reads it that way, so the opposite (phenix/CNS) numbering + // under this name stops REFMAC dead: "more than half of reflections are in free R set", then + // "Cannot switch free R flag", exit 1. phenix auto-detects either numbering with equal confidence + // (measured on both), and rugnux's own reference-MTZ reader already takes 0 as the test set, so + // this is the numbering that works everywhere and the one the rest of the code assumes. + int rfree = 1; }; std::vector BuildMergedRows(const std::vector &reflections, @@ -95,7 +102,7 @@ std::vector BuildMergedRows(const std::vector &r for (const auto& r : reflections) out_rows.push_back({r.h, r.k, r.l, r.I, r.sigma, r.I_plus, r.sigma_plus, r.I_minus, r.sigma_minus, r.F, r.sigmaF, r.F_plus, r.sigmaF_plus, r.F_minus, - r.sigmaF_minus, r.rfree_flag ? 1 : 0}); + r.sigmaF_minus, r.rfree_flag ? 0 : 1}); } else { // Anomalous: group the two mates by their (shared) Friedel-merged ASU representative, then form // IMEAN / F as their inverse-variance Friedel mean. A single generator gives both the group key @@ -105,14 +112,14 @@ std::vector BuildMergedRows(const std::vector &r int h = 0, k = 0, l = 0; float Ip = NAN, sIp = NAN, Im = NAN, sIm = NAN; float Fp = NAN, sFp = NAN, Fm = NAN, sFm = NAN; - int rfree = 0; + int rfree = 1; }; std::map, AnomRow> rows; for (const auto& r : reflections) { const HKLKey key = key_gen(r); AnomRow& row = rows[{key.h, key.k, key.l}]; row.h = key.h; row.k = key.k; row.l = key.l; - row.rfree = r.rfree_flag ? 1 : 0; + row.rfree = r.rfree_flag ? 0 : 1; if (key.plus) { row.Ip = r.I; row.sIp = r.sigma; row.Fp = r.F; row.sFp = r.sigmaF; } else { row.Im = r.I; row.sIm = r.sigma; row.Fm = r.F; row.sFm = r.sigmaF; } }