rugnux: the two-pass guard says when it has no completeness, instead of printing 0.0%

The guard that judges the post-refined pass against the header-geometry pass reads completeness off
each pass's space-group search merge. That merge does not count possible reflections, so the quotient
is 0.0 on both sides and every de-novo rotation report has been printing

    completeness 0.0% vs 0.0%, CC1/2 before corrections 0.994 vs 0.993

as though a comparison had happened. On the 28-dataset in-house battery that is all 28 runs. The
number is not just uninformative, it is a test that did not run: "completeness above 100% means the
cell is wrong" is the guard's wrong-cell arm, and it cannot fire against a constant zero, so only the
CC1/2 arm decides - which is computed on whatever data the pass kept, so a pass that discards a large
part of the sweep can post an equal CC1/2 and win.

Carry a measured flag beside the number. The arm is skipped and the log and PASS_DECISION say
"completeness not measured" where there is nothing to read. No behaviour changes: the arm could not
fire before either. The next commit gives the merge the count so it can.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
2026-09-01 09:48:25 +02:00
co-authored by Claude Opus 5
parent 5ce7eb3014
commit 8935a59721
3 changed files with 22 additions and 11 deletions
+1
View File
@@ -14,6 +14,7 @@
* The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after.
* The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution.
* `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have.
* The `rugnux` two-pass decision no longer reports a completeness it did not measure.
* Twinning is no longer reported when the L-test contradicts it.
* The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's.
* `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots.
+18 -11
View File
@@ -1612,17 +1612,24 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) {
if (!cancelled_ && pass1.has_merge_statistics && pass2.has_merge_statistics) {
const double compl1 = pass1.search_merge_completeness;
const double compl2 = pass2.search_merge_completeness;
const bool compl_measured = pass1.search_merge_completeness_measured
&& pass2.search_merge_completeness_measured;
// Say so rather than printing a zero that reads as a measurement.
const auto compl_text = [&] {
return compl_measured ? fmt::format("completeness {:.1f}% vs {:.1f}%", compl2, compl1)
: std::string("completeness not measured");
};
const double cc1 = pass1.search_merge_cc_half;
const double cc2 = pass2.search_merge_cc_half;
constexpr double MAX_CREDIBLE_COMPLETENESS = 100.5; // rounding headroom, nothing more
constexpr double MAX_CC_HALF_LOSS = 0.05;
if (pass2.lattice_conflicts_with_fixed_sg
|| compl2 > MAX_CREDIBLE_COMPLETENESS || cc2 < cc1 - MAX_CC_HALF_LOSS) {
|| (compl_measured && compl2 > MAX_CREDIBLE_COMPLETENESS)
|| cc2 < cc1 - MAX_CC_HALF_LOSS) {
logger.Warning("Two-pass: the refined pass is worse than the header-geometry pass "
"(completeness {:.1f}% vs {:.1f}%, CC1/2 before corrections {:.3f} vs "
"{:.3f}) - going back to the header geometry. The refined geometry did "
"not help this crystal.",
compl2, compl1, cc2, cc1);
"({}, CC1/2 before corrections {:.3f} vs {:.3f}) - going back to the "
"header geometry. The refined geometry did not help this crystal.",
compl_text(), cc2, cc1);
// The refined pass has already written the canonical files, so re-run at the header
// geometry to replace them - the same remedy the supercell collapse above uses, and it
// only costs a pass on a crystal that was going to be wrong otherwise.
@@ -1644,15 +1651,14 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) {
redo.pass_count = pass2.pass_count + 1;
redo.pass_decision = fmt::format(
"header geometry re-adopted: the post-refined pass was worse "
"(completeness {:.1f}% vs {:.1f}%, CC1/2 before corrections {:.3f} vs {:.3f})",
compl2, compl1, cc2, cc1);
"({}, CC1/2 before corrections {:.3f} vs {:.3f})",
compl_text(), cc2, cc1);
return redo;
}
if (pass2.pass_decision.empty())
pass2.pass_decision = fmt::format(
"post-refined geometry adopted (completeness {:.1f}% vs {:.1f}%, "
"CC1/2 before corrections {:.3f} vs {:.3f})",
compl2, compl1, cc2, cc1);
"post-refined geometry adopted ({}, CC1/2 before corrections {:.3f} vs {:.3f})",
compl_text(), cc2, cc1);
}
if (pass2.pass_decision.empty())
pass2.pass_decision = (prepass_detector_geometry_ || prepass_rotation_scale_)
@@ -3490,7 +3496,8 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
// surfaces - where their final merges need not even be in the same group. See RunAllPasses.
{
const auto &o = sm.statistics.overall;
result.search_merge_completeness = o.possible_unique_reflections > 0
result.search_merge_completeness_measured = o.possible_unique_reflections > 0;
result.search_merge_completeness = result.search_merge_completeness_measured
? 100.0 * o.unique_reflections / o.possible_unique_reflections : 0.0;
result.search_merge_cc_half = sm.cc_half_before_corrections;
}
+3
View File
@@ -244,6 +244,9 @@ struct ProcessResult {
// The final merges of the two passes can be in different groups, where completeness and CC1/2 are
// no longer the same measurement; this merge is in the same terms on both sides.
double search_merge_completeness = 0.0;
// False when the merge had no cell to enumerate possible reflections against, so the guard is told
// not to test a number it does not have and the log says so instead of printing a zero.
bool search_merge_completeness_measured = false;
double search_merge_cc_half = NAN;
// Per-reflection (I, sigma) of the final merged reflections, for the ISa diagnostic: I/sigma