rugnux: report nine resolution shells, as XDS does
Build Packages / build:windows:nocuda (push) Successful in 15m45s
Build Packages / build:windows:cuda (push) Successful in 20m58s
Build Packages / build:rugnux:windows (push) Successful in 15m29s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m17s
Build Packages / build:viewer-tgz:cpu (push) Successful in 15m41s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 15m33s
Build Packages / build:viewer-tgz:cuda (push) Successful in 18m0s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 21m9s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m12s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 23m35s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 24m30s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 22m16s
Build Packages / build:rpm (rocky9) (push) Successful in 20m29s
Build Packages / build:rpm (rocky8) (push) Successful in 23m1s
Build Packages / XDS test (durin plugin) (push) Successful in 10m2s
Build Packages / Generate python client (push) Successful in 38s
Build Packages / Build documentation (push) Successful in 1m8s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m51s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m0s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m52s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m7s
Build Packages / DIALS test (push) Successful in 20m57s
Build Packages / Unit tests (push) Successful in 2h36m9s

The binning rule was already XDS's: equal steps in 1/d^2 between the
lowest- and the highest-resolution reflection the merge kept, anchored on
the data rather than on the nominal low-resolution cut. Only the count
differed - ten shells against XDS's nine - so at the same resolution
limits neither the boundaries nor the reflection populations matched, and
a per-shell number could not be read across. Forced to nine on a
tetragonal rotation dataset, rugnux now gives 4.27 3.03 2.47 2.14 1.92
1.75 1.62 1.52 1.43 against CORRECT.LP's 4.26 3.02 2.47 2.14 1.92 1.75
1.62 1.52 1.43; the hundredth of an angstrom in the first two shells is
each program anchoring on its own lowest-resolution reflection, the two
sets of survivors not being identical.

This matters most to the comparisons that are made shell by shell.
rugnux_vs_xds.py hands XDS's resolution range to rugnux but not its shell
count, so its low-resolution R_meas column compared a shell running to
4.49 A against one running to 4.26 A - 814 unique reflections against
943. The outer shell was nearly immune, its inner edge being
d_min*sqrt(n/(n-1)), which is 1.51 at ten shells and 1.52 at nine.

--resolution-shells still sets any count. rugnux_stills_ab.py pins the
count for both its arms and now pins nine, so its tables read the same
way as everything else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vi1gV6Z45aZL5wLwe85Ksn
This commit is contained in:
2026-08-26 20:24:34 +02:00
co-authored by Claude Opus 5
parent e576184e2f
commit daac3c2da1
7 changed files with 12 additions and 5 deletions
+2 -1
View File
@@ -104,7 +104,8 @@ class ScalingSettings {
// high_resolution_limit_A is set - that manual limit always wins and disables the auto-cut.
ResolutionCutoffMethod resolution_cutoff = ResolutionCutoffMethod::CCHalfLogistic;
double resolution_cc_target = 0.30; // CC1/2 value defining the fall-off limit before the +1 shell
int report_shell_count = 10; // number of resolution shells in the reported statistics table
int report_shell_count = 9; // resolution shells in the reported statistics table (XDS's count,
// over the same equal-1/d^2 bins, so the two tables read row for row)
public:
ScalingSettings& RefineRotationWedge(bool input);
+1
View File
@@ -21,6 +21,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as
* A failed `/initialize` is reported to `/wait_until_running` and `/wait_till_done` as soon as it happens, instead of when their timeout expires.
* `space_group_number` accepts space groups up to 230 in the API schema, so cubic space groups can be recorded. The broker always accepted them; the generated clients rejected them before the request was sent.
* The results report's `REPORT_VERSION` is 3, two sections having been added. Existing key names and table columns are unchanged.
* The merged statistics table has **9** resolution shells instead of 10, which is what XDS reports. The bins were already XDS's - equal steps in 1/d^2 between the lowest- and the highest-resolution reflection the merge kept - so at the same resolution limits the two tables now have the same shell boundaries and can be read row for row. `--resolution-shells` sets a different count.
* `rugnux --model` now settles the frame the merged reflections are written in, not only the frame the R-factors and the maps are computed in: the `.mtz`/`.cif`/`.hkl` come out in the model's indexing, and where the data were merged in the model's enantiomorph they take the model's hand and space group - which on anomalous data puts I(+) and I(-) the right way round. The indexing choice is logged with the winning R-free and the runner-up, so a decision made within noise is visible.
* `rugnux --model` can resolve the indexing ambiguity of a **serial stills** run, which a model could not do before: structure factors computed from the model become the per-image reference, the same role a reference MTZ plays. It needs the cell and space group up front (`-C` / `-S`). Without one or the other, a merohedral serial run still merges both hands together and says so.
* The rugnux documentation opens with a quick start - the default run, and runs with a reference MTZ, with a model, or with the space group and cell pinned - and explains the indexing ambiguity: what it costs on rotation and on serial data, and which of `-z` / `--model` resolves it in each case. The long reference pages now carry a table of contents.
+5
View File
@@ -834,6 +834,11 @@ An internal-consistency term can inflate uncertainties when multiple observation
### 10.5 Merging statistics
The shells are **nine bins of equal width in $1/d^2$**, laid between the lowest- and the
highest-resolution reflection the merge actually kept — XDS's rule and XDS's count, so at the same
resolution limits the two programs' tables have the same shell boundaries and can be read row for
row. `--resolution-shells` changes the count; the binning rule does not change with it.
Per-shell and overall merging statistics are computed on corrected intensities, including:
- number of observations and of unique reflections, and multiplicity,
- mean $I/\sigma(I)$,
+1 -1
View File
@@ -951,7 +951,7 @@ Scaling and merging:
| `--scaling-low-resolution <num>` | Low-resolution limit for scaling and merging, Å (default: 50, the value XDS configurations use; 0 removes the limit). Reflections coarser than this sit behind or beside the beam stop and are measured on a background it has eaten into |
| `--resolution-cutoff <txt>` | Automatic high-resolution cutoff for the written reflections and reported shells: `cc-logistic` \| `off` (default: `cc-logistic`; ignored when `--scaling-high-resolution` is set) |
| `--resolution-cc-target <num>` | CC1/2 target defining the `cc-logistic` fall-off (default: 0.30) |
| `--resolution-shells <num>` | Number of resolution shells in the reported statistics table (default: 10) |
| `--resolution-shells <num>` | Number of resolution shells in the reported statistics table (default: 9). The bins are equal steps in 1/d² between the lowest- and highest-resolution reflection merged, which is XDS's rule, and 9 is XDS's count — so at the same resolution limits the two tables have the same shells and can be read row for row |
| `--min-partiality <num>` | Minimum partiality to accept a reflection (default: 0.02) |
| `--ice-min-score <num>` | Ice-presence gate: the measured per-run ice score (1 = no ice) a dataset must reach before **any** ice handling is applied — the flagging and the exclusion from scaling (default: 1.5; 0 = no gate). The eleven fixed hexagonal bands cover 1626 % of the unique reflections whether or not the crystal has ice, so handling ice on a clean crystal only costs completeness |
| `--ice-min-spot-ratio <num>` | The second ice-presence channel: found **spots** on the hexagonal rings over the same q width of ice-free flanks beside them (1 = spots spread evenly). Ice in large crystallites diffracts as discrete spots and leaves the radial profile flat, so `--ice-min-score` alone is blind to it (default: 2.0; 0 disables this channel) |
@@ -181,7 +181,7 @@ private:
// scaling, combine and error model always run over the full range). Manual d_min_limit wins.
ResolutionCutoffMethod resolution_cutoff_method = ResolutionCutoffMethod::Off;
double resolution_cc_target = 0.30;
int report_shell_count = 10;
int report_shell_count = 9;
// Flat buffers, allocated once by Ingest() and reused across Run() calls.
std::vector<Obs> partials; // all per-frame partials, grouped by frame
+1 -1
View File
@@ -149,7 +149,7 @@ void print_usage() {
std::cout << " --scaling-low-resolution <num> Low resolution limit for scaling/merging, in A (default: 50, the value XDS configurations use; 0 = no limit). Reflections coarser than this sit behind or beside the beam stop and are measured on a background it has eaten into" << std::endl;
std::cout << " --resolution-cutoff <txt> Automatic high-resolution cutoff for the written reflections + reported shells: cc-logistic|off (default: cc-logistic; ignored when --scaling-high-resolution is set)" << std::endl;
std::cout << " --resolution-cc-target <num> CC1/2 target defining the cc-logistic fall-off (default: 0.30)" << std::endl;
std::cout << " --resolution-shells <num> Number of resolution shells in the reported statistics table (default: 10)" << std::endl;
std::cout << " --resolution-shells <num> Number of resolution shells in the reported statistics table (default: 9, as XDS)" << std::endl;
std::cout << " --ice-min-score <num> Ice-presence gate: measured ice score (1 = no ice) a run must reach before ANY ice handling is applied - the flagging and the exclusion from scaling (default: 1.5). The eleven hexagonal bands cover 16-26% of the unique reflections whether or not the crystal has ice, so handling ice on a clean crystal is a pure loss. 0 = no gate (always handle ice)" << std::endl;
std::cout << " --ice-min-spot-ratio <num> Second ice-presence channel: found spots on the hexagonal rings over the same q width of ice-free flanks beside them (1 = spots spread evenly). Ice in large crystallites diffracts as discrete spots and leaves the radial profile flat, so --ice-min-score alone is blind to it. Default 2.0; 0 disables this channel" << std::endl;
std::cout << " --min-partiality <num> Minimum partiality to accept reflection (default: 0.02)" << std::endl;
+1 -1
View File
@@ -648,7 +648,7 @@ def main():
help="unit-cell file for compare_hkl -p, per dataset. Required by "
"--crystfel-merge; the indexamajig line at the top of the stream names "
"the file that produced it")
ap.add_argument("--shells", type=int, default=10, help="resolution shells (default 10)")
ap.add_argument("--shells", type=int, default=9, help="resolution shells (default 9)")
ap.add_argument("--progress", action="store_true")
args = ap.parse_args()