rugnux: name the beam centre when two candidate cells are axis harmonics

The first pass already has the evidence and says nothing about what it means. When two schemes come
back with primitive volumes related by a small integer - or by sqrt(3), which is the hexagonal
harmonic and is not a whole number - one of them is the other's axis harmonic, and what decides
between them is the beam centre to a fraction of a pixel.

The mechanism is that the J0 law is about the FFT AMPLITUDE, which is translation-invariant. A
centre error along the spindle translates the derotated cloud rigidly, so the transform cannot see
it, the peaks stay sharp, and the lattice fit that follows - whose origin is the beam, and which is
not translation-invariant - commits with confidence to a sub-multiple. Measured on a deposited
dataset: 0.12 px of centre, 0.09 px of it across the spindle against a 2.78 px need, 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 message is deliberately not conditioned on any indexing-quality signal, and it is taken over
the whole pair rather than only over the tie: on this failure the frame count points the wrong way,
which is exactly why it cannot arbitrate. Nothing is decided differently; the integer-subcell
tie-break below is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3yNBXk4wKdMZy1ak2NY7f
This commit is contained in:
2026-08-30 08:37:55 +02:00
co-authored by Claude Opus 5
parent 52e9e9da2b
commit 4d2bb98a13
+27 -2
View File
@@ -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<float>(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 "