diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 656e0d38..0639162f 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -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 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(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(score) > best_score * 1.1f + 0.5f) { + + // A later scheme wins if it indexes clearly more frames (>10%). + const bool clearly_more = static_cast(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(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; }