docs: rugnux automatic resolution-cutoff design (CC1/2 logistic, one shell too far)
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m21s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 12m45s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m17s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 13m22s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m27s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m40s
Build Packages / build:windows:nocuda (push) Successful in 15m57s
Build Packages / build:windows:cuda (push) Successful in 18m7s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m8s
Build Packages / build:rpm (rocky8) (push) Successful in 11m36s
Build Packages / XDS test (durin plugin) (push) Successful in 9m24s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 59s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 11m35s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m38s
Build Packages / build:rpm (rocky9) (push) Successful in 13m8s
Build Packages / DIALS test (push) Successful in 14m7s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m43s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m29s
Build Packages / Unit tests (push) Successful in 1h16m21s
Build Packages / Unit tests (pull_request) Successful in 58m25s
Build Packages / build:windows:cuda (pull_request) Successful in 12m6s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 6m59s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 7m11s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 9m51s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 11m39s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 11m10s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 10m10s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 10m27s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 11m7s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 10m44s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 11m54s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 11m12s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 10m32s
Build Packages / DIALS test (pull_request) Successful in 12m33s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 8m44s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 7m27s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 6m14s
Build Packages / Generate python client (pull_request) Successful in 24s
Build Packages / Build documentation (pull_request) Successful in 50s
Build Packages / Create release (pull_request) Skipped
Build Packages / build:windows:nocuda (pull_request) Successful in 9m49s

CC1/2 fall-off fit as a logistic in 1/d^2, cut one shell past CC1/2~0.3 - generous
for ML, robust to the broken error model (works on 4 A Benas where I/sigma cannot),
and avoids defining 'the' resolution. Includes experiments vs XDS and an
implementation plan. Not yet implemented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 15:44:40 +02:00
co-authored by Claude Fable 5
parent c0f6522778
commit be937d0163
+88
View File
@@ -0,0 +1,88 @@
<!--
SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
SPDX-License-Identifier: GPL-3.0-only
-->
# Rugnux automatic resolution cutoff — design + experiments (WIP)
Design notes and experiments for an automatic high-resolution cutoff in rugnux scaling/merging
(2026-07-09). **Not implemented** — this is the design to build from. Prototype and experiment data
live in the working scratchpad (`rescut_logistic.py`, `rescut.py`, run logs).
## Problem
Rugnux merges, reports, and writes to the **most optimistic resolution**. There is no
resolution-quality cutoff in the merge — the only limit is a manual `--scaling-high-resolution`
(default off). The reported high-res edge is set by the prediction/integration d_min, which is
**hardcoded to 1.0 Å** (`common/BraggIntegrationSettings.h`, no CLI setter), bounded only by the
detector edge. So a crystal that diffracts to 4 Å still gets shells and an MTZ/CIF written to
~11.5 Å, full of noise. (XDS's `CORRECT.LP` has the same raw behaviour; the difference is that XDS
users apply a cutoff downstream.)
## Design brief
- Do **not** try to answer "what *is* the resolution" (the ccp4bb debate). Pick a cutoff from the
**CC1/2 fall-off**, fit as a **logistic** curve (DIALS-style).
- Be **generous, not pessimistic** (XDS-style cuts are too tight): keep **one resolution shell too
far**, so maximum-likelihood refinement has a little weak data to work with.
- Leave the error model alone.
## Why CC1/2, not ⟨I/σ⟩
⟨I/σ⟩ ≥ 1 is a fine limit *when the error model is healthy*, and on the test crystals it reproduces
XDS's chosen resolution well (lysoC 1.41 vs 1.43, Ins_I 1.46 vs 1.30, EP_cs_02-10 1.29 vs 1.31). But
it fails exactly where a cutoff is most needed: on **Benas** the weak-low-res error model blows up so
⟨I/σ⟩ < 1 in **every** shell — an I/σ rule would cut to nothing. **CC1/2 still shows the physical
fall-off** there (→ ~44.3 Å). CC1/2 is what the brief calls for and is robust to the (untouched)
error model.
## Method
1. Compute CC1/2 per shell vs `s = 1/d²`. Use **finer bins** than the 10 reported shells for a stable
fit (~2030).
2. Fit a logistic `CC½(s) = 1 / (1 + exp(k·(s s0)))` (low-res plateau ~1, high-res → 0; `s0` =
inflection, `k` = steepness). Bounded least-squares (`k > 0`), restricted to the
contiguous-from-low-res region so a high-res upward blip can't distort it. (Ceres is already linked;
the scratchpad prototype uses a grid+refine fit with no extra dependency.)
3. Fall-off limit = `s` where fitted CC½ = **cc_target (default 0.30)**:
`s_cut = s0 + ln(1/cc_target 1) / k`.
4. **"One shell too far":** extend by one mean shell width in `s`: `s_final = s_cut + Δs`,
`d_final = 1/√s_final`. Clamp to `[d of highest-res reflection, d of lowest-res shell]`.
## Experiment (logistic + 1 shell, on real rugnux merge tables)
| crystal | XDS real res | ⟨I/σ⟩≥1 | **logistic + 1 shell** |
|----------------|--------------|---------|------------------------|
| EcwtCQ244-E3 | ~1.9 | 1.68 | 1.49 |
| EcwtCQ244-B1-1 | ~2.4 | 2.36 | 2.10 |
| cytC_2 | | 1.88 | 1.63 |
| lysoC_14 | 1.43 | 1.41 | 1.23 |
| Ins_I_2 | 1.30 | 1.46 | 1.38 |
| EP_cs_02-10 | 1.31 | 1.29 | 1.29 |
| **Benas_3** | ~4.5 | none (I/σ<1 all) | **4.30** |
| **Benas_7** | ~4.1 | none | **4.10** |
The logistic sits ~0.10.2 Å beyond the strict I/σ limit (the intended generosity), lands at the real
limit on the 4 Å Benas where I/σ can't, and never runs to 1.5 Å on a low-res crystal.
## Implementation plan (in `image_analysis/scale_merge/`)
- After the final merge computes per-shell CC1/2 (`MergeStatistics`), run the logistic fit + "one
shell" extension → `d_cut`. Apply `d_cut` as the high-resolution limit for the **written** MTZ/CIF
and the **reported** shell table (filter merged reflections by `d`). Do **not** trim the per-image
`_process.h5` (keep full data for re-scaling).
- CLI: `--resolution-cutoff cc-logistic|off` (default **cc-logistic**), `--resolution-cc-target <f>`
(default 0.30). `--scaling-high-resolution <d>` stays the explicit manual override (wins).
- Report the chosen limit + method, e.g. `Auto resolution cutoff: 4.3 A (CC1/2 logistic fall-off,
+1 shell; override with --scaling-high-resolution)`.
## Robustness / edge cases
- Degenerate fit (too few shells, flat or non-monotone CC1/2): fall back to the full range + warn.
- Enforce a low-res floor (never cut into good low-res data).
- The fit uses the merge CC1/2 consistently; a v2 could iterate (cut, re-fit), but one pass is enough.
## Related
- The broken weak-data error model that makes ⟨I/σ⟩ useless on Benas is also why Benas's cubic point
group (F432) is under-determined — see `docs/rugnux_denovo_symmetry_investigation.md`.