diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index cd54fe056..e35246179 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -2017,14 +2017,39 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // ratio separates a real axis multiplication from a centering coincidence. Volumes are // primitive (see above), so a pure setting difference is a ratio of 1 and never fires. bool integer_subcell = false; - if (bp.result.has_value() && !clearly_more && vol > 1.0 && bp.vol > 1.0) { + double harmonic_ratio = 0.0; + if (bp.result.has_value() && vol > 1.0 && bp.vol > 1.0) { const bool tied = static_cast(score) >= bp.score * 0.9f - 0.5f; const double ratio = (vol < bp.vol) ? bp.vol / vol : vol / bp.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 < bp.vol; + integer_subcell = tied && !clearly_more && integer_multiple && vol < bp.vol; + // sqrt(3) as well as an integer: a hexagonal supercell that lengthens one axis by + // sqrt(3) is the other harmonic seen on real data and its volume ratio is not a + // whole number. This is a REPORT and not a decision - it is deliberately taken + // over the whole pair, `clearly_more` included, because the harmonic is often the + // one that indexes more frames (see below). + if (integer_multiple || std::abs(ratio - std::sqrt(3.0)) < 0.15) + harmonic_ratio = ratio; } + // Two candidates whose primitive volumes differ by a small integer or by sqrt(3) are + // one lattice and its axis harmonic, and what decides between them is the BEAM CENTRE. + // A centre error along the spindle translates the derotated cloud rigidly, which the + // FFT amplitude cannot see at all, so the peaks stay sharp and the indexer commits - + // with confidence - to a sub-multiple. Measured on real data: a 0.12 px change of + // centre, 0.03x of what the J0 law asks across the spindle, is the whole difference + // between the deposited cell and a halved axis, and the halved one is the answer that + // indexed MORE frames (100.00 % against 99.23 %). So the frame count cannot arbitrate + // this pair, and until this line nothing in the run said what the pair meant. + if (harmonic_ratio > 0.0) + logger.Warning("Scheme '{}' (primitive cell volume {:.0f} A^3) and '{}' ({:.0f} A^3) " + "differ by {:.2f}x - one is an axis harmonic of the other. Which of " + "the two is right is settled by the beam centre to a fraction of a " + "pixel, and the harmonic is often the one that indexes MORE frames, " + "so the frame counts above cannot decide it. Check the beam centre " + "(--estimate-beam-center)", name, vol, bp.name, bp.vol, harmonic_ratio); + if (!bp.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 "