rugnux: a first pass the schemes cannot index also tries a wedge from the end of the first part
Since the decisions before pass 2 read only the first part of a rotation sweep
(5b9144410), the first pass samples [start, PrepassEnd) with its two schemes:
'spread' (100 frames over it) and 'wedge' (consecutive frames from the start up to
the accumulation cap). Which orientations a sweep passes through first is chance,
and a sparse pattern can open on frames that do not show the long axis: on 5nw5
(P212121, 92 x 170 x 390 A, 10-25 spots a frame, 0.1 deg frames) the spread over
the first 120 deg returned a C-centred 955 A cell at every density tried (60-200
frames: 0-10/60 validation frames) and the wedge a wrong one (0/60); the FFT never
offered the true cell. The run kept the 5/60 cell as a sparse lattice and merged
P2_1 at 2.43x the deposited primitive volume, ISa 2.7. The spread over 0-90, 30-150,
60-180 or 0-180 deg indexes 57-59/60, and consecutive frames from 60 deg on 60/60:
the second half of the first part carries the lattice.
Where the two schemes index no majority of the validation frames, pick_best now
also indexes 'end wedge' - the first part read back from its end up to the
accumulation cap - and judges it against them as any later scheme is (clearly more
frames, or the axis-harmonic arbiter). It stays inside [start, PrepassEnd), so
nothing before pass 2 waits for the rest of the sweep. It is not asked on a run the
schemes index: offered there unconditionally it only added a lattice for the
axis-harmonic arbiter, which took a 3x hexagonal supercell over an I-cubic crystal's
cell on a spot count. The measured-centre arm of the beam-centre check takes it the
same way, whether its schemes were indexed speculatively or not.
Battery subset vs rc175 7c9a6c (GPU): 5nw5 fail -> pass (P212121, vol 0.996, d 7.04,
ISa 8.42, R-free(model) 0.462; baseline 7.06 / 7.76 / 0.464); myob_x06da_powder_2
d 2.26 -> 1.47 A (baseline 1.47), ISa 2.11 -> 2.34; p.mtz md5-identical on 8v4o,
9rci, 5ky6, insu_I_x06da_low_isa, nothing_1, nothing_2, 7atg, 9qw8, 5reo.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
@@ -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<std::pair<std::string, std::vector<int>>> 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<RotationIndexerResult> result; int score = -1; std::string name; double vol = 0.0;
|
||||
std::optional<std::string> 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<std::unique_ptr<RotationIndexer>>;
|
||||
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<std::pair<std::string, std::vector<int>>> *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<int>(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<int>(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<void()> &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();
|
||||
|
||||
Reference in New Issue
Block a user