rugnux: fix de-novo rotation indexing adopting a spurious axis-multiple supercell
Two-pass rotation indexing scored each first-pass scheme by how many validation frames it indexes, but a spurious axis multiple (2x/3x...) indexes every frame its true sub-cell does, so the count saturates - both schemes reach the same frame total - and the tie fell to whichever scheme ran first. When that first scheme's full-rotation FFT resolves a true axis only as a 2x/3x harmonic of its length (the fundamental can sit far below the harmonic in the full-360 cloud), it commits a multiplied cell and the dataset collapses to P1, even though the other scheme already found the true cell. Add an integer-supercell cross-scheme tie-break: when two schemes tie on frames but their cell volumes differ by a near-integer factor >=2, the larger is the spurious supercell and the smaller true cell is adopted, independent of scheme order. The near-integer test distinguishes a real axis multiplication from a centering coincidence (a rhombohedral H cell vs its C2 sub-cell is 1.5x and is left alone). Validated de-novo across the rotation regression battery: the affected dataset now indexes its correct space group, the override triggers only where an axis was multiplied, and every other dataset is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-3
@@ -466,6 +466,7 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
f.get();
|
||||
|
||||
int best_score = -1;
|
||||
double best_vol = 0.0;
|
||||
std::string best_name;
|
||||
std::optional<RotationIndexerResult> best_result;
|
||||
for (size_t i = 0; i < ris.size(); i++) {
|
||||
@@ -475,13 +476,40 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
if (!result.has_value())
|
||||
continue;
|
||||
const int score = count_indexed(*result);
|
||||
const double vol = std::abs(result->lattice.CalcVolume());
|
||||
const std::string &name = schemes[i].first;
|
||||
logger.Info("First-pass scheme '{}': indexes {}/{} validation frames", name, score,
|
||||
static_cast<int>(validation.size()));
|
||||
// The first scheme sets the baseline; a later one wins only if it indexes clearly more
|
||||
// frames (>10%), so a scheme that merely ties the default never displaces it.
|
||||
if (!best_result.has_value() || static_cast<float>(score) > best_score * 1.1f + 0.5f) {
|
||||
|
||||
// A later scheme wins if it indexes clearly more frames (>10%).
|
||||
const bool clearly_more = static_cast<float>(score) > best_score * 1.1f + 0.5f;
|
||||
|
||||
// Integer-supercell tie-break. The validation-frame count saturates - a spurious axis
|
||||
// multiple (2x/3x...) indexes every frame its true cell does, so both schemes reach the same
|
||||
// frame total and the count alone cannot tell them apart; the default then keeps whichever
|
||||
// ran first. When the two schemes tie on frames but their cell volumes are related by an
|
||||
// integer factor >=2, the larger cell is that spurious supercell (one FFT resolved a true
|
||||
// axis only as a 2x/3x harmonic of its length - the full-rotation 'spread' cloud is prone to
|
||||
// this) and the smaller is the true reduced cell: take it, regardless of scheme order.
|
||||
// Requiring a near-integer ratio is what separates a real axis multiplication from a mere
|
||||
// centering coincidence: a rhombohedral H cell vs its C2 sub-cell is 1.5x (non-integer) and
|
||||
// is correctly left alone.
|
||||
bool integer_subcell = false;
|
||||
if (best_result.has_value() && !clearly_more && vol > 1.0 && best_vol > 1.0) {
|
||||
const bool tied = static_cast<float>(score) >= best_score * 0.9f - 0.5f;
|
||||
const double ratio = (vol < best_vol) ? best_vol / vol : vol / best_vol;
|
||||
const double nearest = std::round(ratio);
|
||||
const bool integer_multiple = nearest >= 2.0 && std::abs(ratio - nearest) < 0.15;
|
||||
integer_subcell = tied && integer_multiple && vol < best_vol;
|
||||
}
|
||||
|
||||
if (!best_result.has_value() || clearly_more || integer_subcell) {
|
||||
if (integer_subcell)
|
||||
logger.Info("Scheme '{}' cell (vol {:.0f}) is a {:.0f}x sub-cell of '{}' (vol {:.0f}) at "
|
||||
"equal frame count - adopting the smaller true cell", name, vol,
|
||||
std::round(best_vol / vol), best_name, best_vol);
|
||||
best_score = score;
|
||||
best_vol = vol;
|
||||
best_name = name;
|
||||
best_result = result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user