Space-group search: log the refusal the two-merge rule overrides

When the all-observation and Lorentz-filtered search merges disagree, the
higher point group is adopted. The losing arm's answer was dropped, so a
promotion one arm refused on its twin gates (H ratio, added-operator R)
went through with nothing in the output saying a gate had fired - and the
report printed the adopted group's failing H ratio ("2.02 against a bound
of 1.85") as if it were a margin. Seen on an H3 partial twin (5j23),
refused on both twin gates by the all-observation merge and promoted to
R32 by the filtered one.

Now the losing arm's refusal (group and reason), or the fact that it never
confirmed the operators, is logged as a warning and written to the report
(SPACE_GROUP_ARM_OVERRIDE). A bound the adopted group exceeds is printed as
"EXCEEDS its bound - that gate fired and was overridden", for the H ratio
and for the best-operator R. The comment that claimed both arms keep their
vetoes now describes the rule as implemented. The rule itself is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
leonarski_fandClaude Opus 5 committed 2026-09-19 11:29:57 +02:00
1 parent e715d3999e
commit 7ae1da277a
3 files changed
+50 -6

No files matched your search

+19 -2
View File
@@ -528,9 +528,21 @@ ReportDocument BuildReportDocument(const std::string &output_prefix,
// operator and candidate tables that justify it.
std::string line = fmt::format(" Point group {} from the intensity correlations",
search.point_group_hm);
// A bound the adopted group EXCEEDS is a gate that fired and was overridden - the point group
// was taken from the other search arm - and must not read as the margin of a pass.
if (std::isfinite(search.h_ratio))
line += fmt::format(";\n twin-law H ratio {:.2f} against a bound of {:.2f}",
search.h_ratio, search.h_ratio_bound);
line += search.h_ratio > search.h_ratio_bound
? fmt::format(";\n twin-law H ratio {:.2f} EXCEEDS its bound of {:.2f} - that gate fired "
"and was overridden", search.h_ratio, search.h_ratio_bound)
: fmt::format(";\n twin-law H ratio {:.2f} against a bound of {:.2f}",
search.h_ratio, search.h_ratio_bound);
// The best-operator R gate is the one that applies once two operators are confirmed, i.e.
// above order 2 (the first step out of P1 is judged on the noise floor instead).
if (search.point_group_order > 2 && std::isfinite(search.r_over_best)
&& search.r_over_best_bound > 0.0 && search.r_over_best > search.r_over_best_bound)
line += fmt::format(";\n added-operator R {:.2f}x the best operator EXCEEDS its bound of {:.2f}x - "
"that gate fired and was overridden", search.r_over_best,
search.r_over_best_bound);
line += ".\n The space group follows from the systematic absences.";
if (!alts.empty())
line += fmt::format("\n These data cannot separate it from {} - both are consistent with"
@@ -541,6 +553,11 @@ ReportDocument BuildReportDocument(const std::string &output_prefix,
" cannot name. Molecular replacement settles it: one of the two refines and the\n"
" other does not - the normal outcome for a chiral group, not a failure of the\n"
" run."));
if (!result.space_group_arm_override.empty()) {
Add(s, KeyText("SPACE_GROUP_ARM_OVERRIDE", result.space_group_arm_override));
Add(s, Prose(fmt::format(" The two search merges disagreed and the higher point group was taken:\n {}.",
result.space_group_arm_override)));
}
if (refused)
Add(s, Prose(fmt::format(
" A promotion to {} was refused: {}.\n"
+27 -4
View File
@@ -5280,9 +5280,12 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
// rotations are real, because a promotion is refused by the most damning statistic it can
// be shown and only one arm has to be starved for that to happen, while the ABSENCES are
// always judged on the all-observation merge, which is the only one holding the weak
// reflections a systematic absence is made of. Both searches keep their vetoes, so a merohedral twin
// stays refused in each (verified on a real twin at the three frame ranges where it
// over-promotes: refused in both arms every time).
// reflections a systematic absence is made of. The vetoes are NOT kept per arm: a promotion
// one arm refused on its twin gates (H ratio, added-operator R) is adopted when the other arm
// confirms it, because the higher order wins. That held on one real twin at three frame
// ranges, where both arms refused, but an H3 partial twin has been seen refused by the
// all-observation arm on both twin gates and promoted to R32 by the filtered one. So the
// losing arm's refusal is logged and carried to the report below; the rule is unchanged.
if (rsm && rsm->GetSearchMinZeta() > 0.0 && !sg_search.point_group_hm.empty()) {
const double zeta = rsm->GetSearchMinZeta();
rsm->SetSearchMinZeta(0.0);
@@ -5341,10 +5344,29 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
stats_text << " !! " << msg << "\n\n";
result.warnings.push_back({PathologyCode::SYMMETRY_AMBIGUITY, msg});
}
if (order_of(alt) > order_of(sg_search))
// The arm whose lower answer is about to be overridden, and why it stopped there. A
// refusal by a consistency gate is the case that matters: the higher group is adopted
// anyway, and without this nothing in the output says a gate ever fired.
const auto overridden = [&](const SearchSpaceGroupResult &lower, const char *lower_name,
const char *higher_name) {
std::string msg;
if (!lower.refused_point_group_hm.empty())
msg = fmt::format("the {} merge REFUSED {} ({}); overridden, because the {} merge "
"confirmed the higher point group", lower_name,
lower.refused_point_group_hm, lower.refused_reason, higher_name);
else
msg = fmt::format("the {} merge did not confirm the operators of the higher point "
"group (no gate refused it); the {} merge did", lower_name,
higher_name);
logger.Warning("Space-group search: {}", msg);
result.space_group_arm_override = msg;
};
if (order_of(alt) > order_of(sg_search)) {
logger.Info("Space-group search: all-observation merge supports {} where the "
"Lorentz-filtered one supports {} - taking the higher symmetry",
alt.point_group_hm, sg_search.point_group_hm);
overridden(sg_search, "Lorentz-filtered", "all-observation");
}
if (order_of(alt) < order_of(sg_search)) {
// The filtered arm found symmetry the all-observation arm did not. Discarding it
// was costing real point groups: over the corpus the two arms disagree on eight
@@ -5359,6 +5381,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
// that found it and the absences are re-judged here, on every observation. That
// is what fixed_point_group is for: Stage A still runs and still reports what it
// would have refused, only the choice is overridden.
overridden(alt, "all-observation", "Lorentz-filtered");
sg_opts.fixed_point_group = sg_search.point_group_representative;
auto split = SearchSpaceGroup(sm_all.merged, sg_opts);
sg_opts.fixed_point_group.reset();
+4
View File
@@ -334,6 +334,10 @@ struct ProcessResult {
// populated only when a search ran (no space group was fixed). The library no longer renders it to
// text - the CLI formats it for stdout, the viewer draws it as a table.
std::optional<SearchSpaceGroupResult> space_group_search;
// When the two search arms (all observations / Lorentz-filtered) disagreed and the higher point
// group was taken from one of them: what the other arm said - which gate refused the promotion,
// or that it never confirmed the operators. Empty when the arms agreed.
std::string space_group_arm_override;
// ---- Material for the results report (<prefix>_report.txt, see rugnux/ResultReport.h) ----