rugnux: the external reference MTZ is never a scale anchor (stills + rotation)

An external reference MTZ is a poor per-image scale reference: it is a different crystal/dataset,
so scaling against it injects cross-dataset systematics rather than removing per-image scale error
(measured: reference-scaled R-free 0.410 vs self-scaled 0.388 on OCP; 0.378 vs 0.350 on LOV). Its
real value is fixing the cell/space group, breaking the merohedral indexing ambiguity, reporting
CCref and providing the R-free test set - none of which need it to be a scale anchor.

Make that consistent across both workflows:
- Stills: the per-image pass (ScaleImage) now uses the reference ONLY to break the indexing ambiguity
  (once, for good), not to fit G. Scaling self-references at the post-measurement merge for every run,
  whether or not a reference is given (Rugnux + rugnux_cli --scale).
- Rotation: ScaleImage no longer scales against the reference either (it was dead code - RotationScaleMerge
  recomputes the per-frame scale, so the reference never actually moved the result). Rotation resolves
  the ambiguity globally (ChooseReindex / ReferenceIntensityCC) and self-scales in RotationScaleMerge.
- A reference-as-scale mode, if ever wanted, would be a dedicated later step, not this per-image pass.

Validated: OCP CCref 56.5%->64.6%, R-free 0.400->0.393 (honest 2.36 A cutoff instead of a reference-
propped 1.5 A); LOV CCref 88.3%->90.1%, R-free 0.336->0.331 with more reflections; rotation lyso_ref
with the reference gives ISa 17.8 == de-novo 15.6 (same 15975 reflections, CC1/2 99.8%) - the reference
demonstrably does not touch rotation scaling, only cell/SG + ambiguity + CCref.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 13:01:56 +02:00
co-authored by Claude Opus 4.8
parent 9527fa6e2c
commit 95af137146
4 changed files with 30 additions and 27 deletions
+12 -8
View File
@@ -495,15 +495,19 @@ void IndexAndRefine::ScaleImage(DataMessage &msg, IntegrationOutcome& outcome) {
if (!scaling_engine)
return;
auto scaling_start_time = std::chrono::steady_clock::now();
scaling_engine->Scale(outcome);
auto scaling_end_time = std::chrono::steady_clock::now();
// The external reference fixes the cell/space group, breaks the indexing ambiguity and reports CCref,
// but is NEVER a scale anchor: scaling an image against a foreign dataset injects cross-dataset
// systematics and is a worse reference than the data's own merge, so scaling self-references at the
// post-measurement merge for both workflows. Rotation resolves the ambiguity globally and self-scales
// in RotationScaleMerge (ChooseReindex / ReferenceIntensityCC), so there is nothing to do per image.
// Stills resolve the merohedral ambiguity per image here (each crystal indexes in a random hand; pick
// the hand best-correlated with the reference, once and for good).
if (experiment.IsRotationIndexing())
return;
scale_cc[msg.number] = outcome.image_scale_cc.value_or(NAN);
msg.image_scale_b_factor = outcome.image_scale_b_factor_Ang2;
msg.image_scale_factor = outcome.image_scale_g;
msg.image_scale_mosaicity = outcome.mosaicity_deg;
msg.image_scale_cc = outcome.image_scale_cc;
auto scaling_start_time = std::chrono::steady_clock::now();
scaling_engine->ResolveIndexingAmbiguity(outcome.reflections);
auto scaling_end_time = std::chrono::steady_clock::now();
msg.image_scale_time_s = std::chrono::duration<float>(scaling_end_time - scaling_start_time).count();
}