rugnux: resolve the indexing ambiguity against a reference MTZ (rotation)

A reference MTZ is now allowed for rotation data: it fixes the space group
and cell (on the CLI) and resolves the indexing (merohedral) ambiguity, but
is NOT used to scale - the rotation merge stays self-consistent. Previously
rotation + reference was a hard error.

After the space group is determined, ChooseReindex/ReferenceIntensityCC pick
the reindexing whose merged intensities best correlate with the reference and,
if it is not the identity, the twin-law reindex is baked into the integration
outcome (hkl labels only; the cell is unchanged, the reindex is metric-
preserving), the rotation scale-merge is re-ingested and the data re-merged in
that indexing. No-op for a holohedral crystal (no twin laws), e.g. lysozyme.

Stills resolve the per-image ambiguity in ScaleOnTheFly, not here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 08:20:19 +02:00
co-authored by Claude Opus 4.8
parent e91b520863
commit 465cf1aa5e
+37 -2
View File
@@ -30,6 +30,7 @@
#include "../image_analysis/scale_merge/Merge.h"
#include "../image_analysis/scale_merge/RotationScaleMerge.h"
#include "../image_analysis/scale_merge/ResolutionCutoff.h"
#include "../image_analysis/scale_merge/ReindexAmbiguity.h"
#include "../image_analysis/scale_merge/ScalingResult.h"
#include "../image_analysis/scale_merge/SearchSpaceGroup.h"
#include "../image_analysis/lattice_search/LatticeSearch.h"
@@ -565,12 +566,15 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
const bool is_rotation = experiment_.IsRotationIndexing(); // rotation indexing -> rotation scaling/merge
std::optional<RotationScaleMerge> rsm;
if (is_rotation) {
if (!config_.reference_data.empty() || rot_ss.GetRefineB()
if (rot_ss.GetRefineB()
|| experiment_.GetRefineRotationWedgeInScaling()
|| rot_ss.GetRotationWedgeForScaling().has_value())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Rotation scaling/merging (RotationScaleMerge) does not support "
"reference scaling, B-factor refinement or wedge refinement");
"B-factor refinement or wedge refinement");
// A reference MTZ is allowed for rotation: it fixes the space group / cell (on the CLI) and
// resolves the indexing ambiguity (below), but is NOT used to scale - the rotation merge stays
// self-consistent.
rsm.emplace(experiment_, indexer->GetIntegrationOutcome(), result.consensus_cell,
static_cast<int>(config_.scaling_iter),
config_.spot_finding.ice_ring_width_Q_recipA,
@@ -814,6 +818,37 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
result.space_group_number = experiment_.GetSpaceGroupNumber();
}
// Reference-based indexing-ambiguity resolution (rotation). When a reference MTZ is supplied and
// the crystal has an indexing ambiguity (merohedral: lattice symmetry higher than the Laue group),
// pick the reindexing that best correlates with the reference intensities and re-merge in it. The
// twin-law reindex is metric-preserving, so only the hkl labels change (the cell is unchanged).
// Stills resolve the ambiguity per image in ScaleOnTheFly, not here.
if (rsm && !config_.reference_data.empty() && result.consensus_cell
&& experiment_.GetSpaceGroupNumber().has_value()) {
const int sg_num = static_cast<int>(*experiment_.GetSpaceGroupNumber());
const auto choice = ChooseReindex(
sm.merged, *result.consensus_cell, sg_num,
[&](const std::vector<MergedReflection> &m) {
return ReferenceIntensityCC(m, config_.reference_data, sg_num);
});
if (!choice.is_identity) {
logger.Info("Reference: resolved indexing ambiguity by reindexing to match the reference "
"(CC {:.3f}, vs {:.3f} unchanged)", choice.score, choice.identity_score);
for (auto &io : indexer->GetIntegrationOutcome())
for (auto &r : io.reflections) {
const gemmi::Op::Miller h = choice.op.apply_to_hkl({{r.h, r.k, r.l}});
r.h = h[0]; r.k = h[1]; r.l = h[2];
}
rsm.emplace(experiment_, indexer->GetIntegrationOutcome(), result.consensus_cell,
static_cast<int>(config_.scaling_iter),
config_.spot_finding.ice_ring_width_Q_recipA,
config_.nthreads, logger, config_.observation_dump_path);
rsm->Ingest();
phase("Re-merging in the reference indexing");
sm = scale_and_merge(experiment_.GetGemmiSpaceGroup()->short_name(), false);
}
}
// Ice-ring CC1/2 mask: a hexagonal-ice ring whose merged half-set CC1/2 collapses well below its
// resolution shoulders has been decorrelated by ice (its Bragg is unrecoverable) - drop it and
// re-merge. Weak/absent rings track their neighbours and stay, so completeness on clean crystals