diff --git a/rugnux/RugnuxFirstPass.cpp b/rugnux/RugnuxFirstPass.cpp index 599a59494..7747fab12 100644 --- a/rugnux/RugnuxFirstPass.cpp +++ b/rugnux/RugnuxFirstPass.cpp @@ -707,13 +707,23 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { {"spread", spread_ordinals}, {"wedge", all_consecutive}, }; + // A third sample of the same frames, asked only when the two above index no majority (see + // pick_best): the wedge read back from the END of the first part. Which orientations a sweep + // passes through first is chance, and the first part can open on frames that see the long axis + // badly - measured on a sparse crystal with a ~390 A axis, the spread and the wedge over its + // first 120 deg returned wrong cells (5/60 and 0/60 validation frames) at every spread density + // tried, while consecutive frames from 60 deg on indexed 60/60. Still inside [start, + // PrepassEnd), so nothing before pass 2 waits for the rest of the sweep. + const std::vector>> end_wedge = { + {"end wedge", {all_consecutive.rbegin(), all_consecutive.rend()}}, + }; // indexer_error: what the indexer threw, if it threw. An empty result with no error is a // verdict on the crystal; an empty result with one is a verdict on the machine. struct FirstPass { std::optional result; int score = -1; std::string name; double vol = 0.0; std::optional indexer_error; }; - // Feed both schemes (single-threaded spot cache), ready to be indexed. + // Feed every scheme (single-threaded spot cache), ready to be indexed. using SchemeIndexers = std::vector>; auto feed_schemes = [&](IndexerThreadPool &pool, const DiffractionExperiment *x = nullptr) { SchemeIndexers ris; @@ -740,8 +750,13 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { // Keep the lattice of the indexed schemes that indexes the most validation frames on the real // per-image path. Scored against the spot lists and settings the run stands on now, which must // be those the schemes were fed under. - auto score_schemes = [&](const SchemeIndexers &ris, IndexAndRefine &idx) -> FirstPass { - FirstPass bp; + // `named` names the indexers (`schemes` unless given) and `bp` is the standing pass they are + // judged against (none unless given). + auto score_schemes = [&](const SchemeIndexers &ris, IndexAndRefine &idx, + const std::vector>> *named = nullptr, + FirstPass bp = {}) -> FirstPass { + if (!named) + named = &schemes; for (size_t i = 0; i < ris.size(); i++) { if (cancelled_) break; @@ -752,7 +767,7 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { continue; RotationIndexerResult result = std::move(*found); int score = count_indexed(idx, result); - const std::string &name = schemes[i].first; + const std::string &name = (*named)[i].first; logger.Info("First-pass scheme '{}': indexes {}/{} validation frames", name, score, static_cast(validation.size())); @@ -919,7 +934,19 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { return bp; }; - // Feed both schemes, index them concurrently, and keep the best (score_schemes). + // Where the schemes index no majority of the validation frames, the end wedge is asked too and + // judged against them as any later scheme is. Only then: on a run the schemes index it would be + // one more lattice for the axis-harmonic arbiter, which is not decided on frame counts. + auto add_end_wedge = [&](FirstPass bp, IndexerThreadPool &pool, IndexAndRefine &idx) -> FirstPass { + if (cancelled_ || bp.score > static_cast(validation.size()) / 2) + return bp; + SchemeIndexers ris; + ris.push_back(feed_scheme(pool, end_wedge[0].second)); + run_indexing({ris[0].get()}); + return score_schemes(ris, idx, &end_wedge, std::move(bp)); + }; + + // Feed the schemes, index them concurrently, and keep the best (score_schemes, add_end_wedge). // alongside: started once the schemes are fed and before they are indexed, to run beside them. auto pick_best = [&](IndexerThreadPool &pool, IndexAndRefine &idx, const std::function &alongside = {}) -> FirstPass { @@ -930,7 +957,7 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { for (const auto &ri : ris) rp.push_back(ri.get()); run_indexing(rp); - return score_schemes(ris, idx); + return add_end_wedge(score_schemes(ris, idx), pool, idx); }; // Powder-ring rescue - and, with it, the lean seed the rotation first pass never had. @@ -1245,7 +1272,7 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { return std::nullopt; }; - // Find the spots of everything the first pass is certain to want in one go - both schemes' + // Find the spots of everything the first pass is certain to want in one go - the schemes' // opening frames and the validation set. A batch of workers costs an analysis engine each, // which is far more than a frame is to analyse, so one batch for the lot beats one per scheme. { @@ -1512,7 +1539,7 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) { entries.emplace_back(centre_spec->key, centre_spec->spots); } } - alt = score_schemes(centre_spec->ris, *indexer); + alt = add_end_wedge(score_schemes(centre_spec->ris, *indexer), *indexer_pool, *indexer); } else alt = pick_best(*indexer_pool, *indexer); centre_spec.reset();