Space-group search: take the operator disagreement as a median, not a mean
A merohedral twin mixes EVERY reflection with its twin mate, so it shifts the whole distribution of |I1-I2|/(I1+I2). A minority of badly measured reflections shifts only the tail. The mean cannot tell those apart; the median is blind to the second and just as sensitive to the first. Measured on real crystals, moving the statistic from the mean to the median leaves genuine promotions where they are and pushes every twin up: genuine tetragonal 1.016 -> 1.013 genuine lysozyme 1.051 -> 1.067 genuine tetragonal 1.238 -> 1.231 twin (-e 1050) 1.272 -> 1.447 twin (-e 450) 1.280 -> 1.622 twin (full) 1.441 -> 1.522 twin (-e 600) 1.427 -> 2.010 The margin around the 1.25 bound widens from 2.7% (genuine 1.238 against twin 1.272 - uncomfortably tight for a decision that cannot be undone downstream) to 17.5% (1.231 against 1.447). The bound itself does not move. Rotation battery, 33 crystals in both detection modes: no point group changed in either (30/33 and 29/33, as before), and only one crystal's numbers move at all - the one already documented as nondeterministic between repeat runs of the same binary. The synthetic twin-fraction x multiplicity grid passes unchanged. So this buys margin, not outcomes. Found while testing a different hypothesis, which the same measurement refuted: a tetragonal crystal whose 422 promotion is wrongly refused reads 1.484 by the mean and 1.472 by the median, i.e. its disagreement is distribution-wide and is NOT a badly-integrated minority. That crystal's cause is elsewhere and is not addressed here - see the note below. Its indexing-ambiguity operator (-k,-h,-l) lies INSIDE 422 but OUTSIDE 222, so the subgroup merge the search is given mixes lattices indexed in the two alternative hands. That corrupts exactly the 4-fold relationships and leaves the 2-fold ones intact - measured, the 222 step reads 0.917 and the 422 step 1.484 - and the corruption is indistinguishable from a twin law. Forcing the tetragonal group merges the two hands as equivalent and the same data give CC1/2 99.2% at multiplicity 10.7, matching XDS. The failure is worse the BETTER the frames index (99.9% vs 63.3% for the run that gets it right), because indexing more frames picks up more of both hands. So no statistic computed on a subgroup merge can arbitrate a promotion whose added operators include an indexing-ambiguity operator. Fixing that means resolving the ambiguity before the search, or detecting the coincidence and deciding another way; the operators needed to detect it are already computed (the run warns about them). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -273,16 +273,18 @@ SearchSpaceGroupResult SearchSpaceGroup(
|
||||
s.n_pairs = static_cast<int>(x.size());
|
||||
s.cc = PearsonCC(x, y);
|
||||
// Sigma-free disagreement over the same pairs (see SpaceGroupOptions::max_operator_h_ratio).
|
||||
double h_sum = 0.0;
|
||||
int h_n = 0;
|
||||
std::vector<double> hv;
|
||||
hv.reserve(x.size());
|
||||
for (size_t p = 0; p < x.size(); ++p) {
|
||||
const double denom = x[p] + y[p];
|
||||
if (denom > 0.0) {
|
||||
h_sum += std::fabs(x[p] - y[p]) / denom;
|
||||
++h_n;
|
||||
}
|
||||
if (denom > 0.0)
|
||||
hv.push_back(std::fabs(x[p] - y[p]) / denom);
|
||||
}
|
||||
if (!hv.empty()) {
|
||||
const size_t mid = hv.size() / 2;
|
||||
std::nth_element(hv.begin(), hv.begin() + mid, hv.end());
|
||||
s.h_stat = hv[mid];
|
||||
}
|
||||
s.h_stat = h_n > 0 ? h_sum / h_n : 0.0;
|
||||
s.present = s.n_pairs >= opt.min_pairs_per_operator && std::isfinite(s.cc) &&
|
||||
s.cc >= opt.min_operator_cc;
|
||||
return s;
|
||||
@@ -536,7 +538,7 @@ SearchSpaceGroupResult SearchSpaceGroup(
|
||||
const bool in_parent = std::binary_search(parent->rotation_set.begin(),
|
||||
parent->rotation_set.end(), RotKey(rot));
|
||||
if (in_parent) { h_par += os.h_stat; ++n_par; pairs_par += os.n_pairs; }
|
||||
else { h_new += os.h_stat; ++n_new; pairs_new += os.n_pairs; }
|
||||
else { h_new += os.h_stat; ++n_new; pairs_new += os.n_pairs; }
|
||||
}
|
||||
if (n_new > 0 && n_par > 0 && pairs_new >= opt.min_pairs_for_h
|
||||
&& pairs_par >= opt.min_pairs_for_h && h_par > 0.0) {
|
||||
|
||||
Reference in New Issue
Block a user