Space-group search: report the order of the point group it chose

The two-arm search compared its arms by the order of the space group each
had picked, but Stage B leaves best_space_group unset whenever no
candidate is eligible - no candidate had enough observed absences to
trust. That is not rare on the Lorentz-filtered arm, and for a systematic
reason: the filter removes the badly-measured observations, which is
where the weak systematically-absent reflections are.

An arm that confirmed 422 but stopped short of naming a space group
therefore scored order 0 and lost to an arm supporting P2, and the
demotion was logged as "taking the higher symmetry" - the comparison and
the message both wrong, in the one direction the design says cannot
happen.

Carry the point-group order in the result, set from the order Stage A
actually adopted, and compare on that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 09:33:29 +02:00
co-authored by Claude Opus 5
parent 0162afd324
commit 157698d31e
3 changed files with 13 additions and 7 deletions
@@ -640,6 +640,7 @@ SearchSpaceGroupResult SearchSpaceGroup(
if (best_pg->representative)
result.point_group_hm = best_pg->representative->point_group_hm();
result.point_group_order = best_pg_order;
// Only report a refusal that is actually ABOVE what was adopted.
if (refused_order > best_pg_order) {
@@ -219,6 +219,11 @@ struct SearchSpaceGroupResult {
std::vector<gemmi::SpaceGroup> alternatives;
std::string point_group_hm; // chosen point group, e.g. "422"
// Order of that point group (its number of rotations). Reported separately because Stage B can
// leave best_space_group unset - no candidate had enough absences to be eligible - while Stage A
// has confirmed the point group perfectly well, and a caller comparing two searches has to see
// the symmetry that was found either way. 0 only when no point group was chosen at all.
int point_group_order = 0;
std::vector<SpaceGroupOperatorScore> operator_scores; // Stage A, all distinct operators tested
std::vector<SpaceGroupCandidateScore> candidates; // Stage B, ranked
+7 -7
View File
@@ -1223,13 +1223,13 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b
auto sm_all = scale_and_merge("P1, all observations", true);
rsm->SetSearchMinZeta(zeta);
const auto alt = SearchSpaceGroup(sm_all.merged, sg_opts);
// Point-group order = the number of rotations, which is what sym_ops holds (centering
// lives in cen_ops). Taken from the space group the search chose - a point-group symbol
// like "422" is not a space-group name and cannot be looked up.
const auto order_of = [](const SearchSpaceGroupResult &r) -> size_t {
return r.best_space_group.has_value()
? r.best_space_group->operations().sym_ops.size() : 0;
};
// The order of the point group each arm confirmed. Read from the search, not from the
// space group it went on to pick: Stage B leaves best_space_group unset when no
// candidate has enough absences to be eligible, which is common on the filtered arm
// precisely because the filter removes the weak reflections the absences live in. An
// arm that confirmed 422 would then have scored 0 and lost to a P2 arm - a demotion,
// reported as "taking the higher symmetry".
const auto order_of = [](const SearchSpaceGroupResult &r) { return r.point_group_order; };
logger.Info("Space-group search: all-observation merge -> {} (order {}), "
"Lorentz-filtered -> {} (order {})",
alt.point_group_hm.empty() ? "?" : alt.point_group_hm, order_of(alt),