rugnux: the first pass takes the larger of two domains of one lattice
Two twin domains, or two crystals of one cell, each index every validation frame, so the frame
count the first pass picks its lattice by cannot tell them apart, and the FFT returns whichever
its sample favours. Since the decisions before pass 2 read only the first part of the sweep
(5b9144410), that sample is smaller and it can be the domain there is less of. On an I-centred
cubic crystal with a second domain at 60 deg about [111] pass 1 took the smaller domain; pass 2's
re-index then found the hexagonal P cell both domains share (3x the primitive volume), the
supercell guard handed it back to pass 1's lattice, and the run integrated the smaller domain
over the whole sweep. Every reflection the two share then carried the larger domain's intensity,
which broke the cubic 2-folds (operator CC 0.48 against 0.89 for the 3-fold about the twin axis),
the added-operator R gate refused 23 and the run merged in R3 on the cubic axes - the I centring
undeclared, primitive volume 2x.
Now the spots the chosen lattice leaves on the spread frames are indexed again, as the
leftover-lattice census does, and where they give the same lattice (Niggli-reduced primitive
edges within 2 %) in another orientation, the two are judged on the validation spots each puts
on itself over a wrong spindle angle (ValidationEvidencePrefers); the larger domain is kept. It
is decided on the same early frames, so part B's principle stands.
Measured (GPU, battery, against rc175 7c9a6c1):
insu_I_x06da_low_isa R 3:R, d 1.59, R_model 0.315 -> I 2 3, d 1.43, R_model 0.226 (= pre-B)
5reo adopts the larger domain (86.5 % against 68.4 % of validation spots):
ISa 23.0 -> 34.3, R_meas 23.6 -> 23.3 %, R_model 0.205 -> 0.202,
d 1.64 -> 1.69 A, still C2
5nw5, 8v4o, 9rci, 5ky6, 7atg, 9qw8, myob_x06da_powder_2, nothing_1, nothing_2: p.mtz
md5-identical (the check ran on most of them and kept the lattice).
Result-changing only where a second domain is adopted.
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:
@@ -2230,6 +2230,67 @@ bool Rugnux::FirstPassRotationIndexing(PipelineLocals &p) {
|
||||
best = *rescued;
|
||||
}
|
||||
|
||||
// A second domain of the same lattice. Two twin domains, or two crystals of one cell, each
|
||||
// index every validation frame, so the frame count cannot say which of them is the crystal,
|
||||
// and the FFT returns whichever its sample happens to favour. On the first part of a sweep
|
||||
// that can be the domain there is less of (measured: an I-centred cubic crystal with a second
|
||||
// domain at 60 deg about [111] took the smaller one; integrated, it carried the larger one's
|
||||
// intensity on every reflection the two share, which broke the cubic 2-folds and merged in
|
||||
// R3). So the spots the lattice leaves on the spread frames are indexed again, as the
|
||||
// leftover-lattice census below does, and where they give the same lattice in another
|
||||
// orientation, the two are judged on the validation spots each puts on itself.
|
||||
if (!cancelled_ && best.result.has_value()) {
|
||||
indexer->ForceRotationIndexerResult(*best.result);
|
||||
prefetch_spots(spread_ordinals);
|
||||
std::vector<std::vector<SpotToSave>> left(spread_ordinals.size());
|
||||
ParallelFor(static_cast<int>(spread_ordinals.size()),
|
||||
std::min<size_t>(spread_ordinals.size(), config_.nthreads), [&](int i) {
|
||||
const auto &sp = spot_cache.at(spread_ordinals[i]);
|
||||
DataMessage m{};
|
||||
m.number = spread_ordinals[i];
|
||||
m.spots = sp;
|
||||
indexer->CountSpotsOnLattice(m, validation_settings);
|
||||
for (size_t k = 0; k < sp.size(); k++)
|
||||
if (m.spots.size() != sp.size() || !(m.spots[k].indexed && m.spots[k].lattice == 0))
|
||||
left[i].push_back(sp[k]);
|
||||
});
|
||||
RotationIndexer leftover(experiment_, *indexer_pool);
|
||||
leftover.MaxSpotsPerImage(first_pass_spots_per_image);
|
||||
leftover.VerifyMemo(config_.verify_first_pass_memo);
|
||||
for (size_t i = 0; i < spread_ordinals.size() && !leftover.AccumulationFull(); i++)
|
||||
leftover.ProcessImage(spread_ordinals[i], left[i], rot_angle(spread_ordinals[i]));
|
||||
leftover.RunIndexing();
|
||||
const auto reduced_edges = [](const RotationIndexerResult &r) {
|
||||
const auto uc = r.lattice.ToPrimitive(r.search_result.centering).NiggliReduce().GetUnitCell();
|
||||
std::array<double, 3> e = {uc.a, uc.b, uc.c};
|
||||
std::sort(e.begin(), e.end());
|
||||
return e;
|
||||
};
|
||||
if (auto other = leftover.GetLattice()) {
|
||||
const auto e1 = reduced_edges(*best.result);
|
||||
const auto e2 = reduced_edges(*other);
|
||||
bool same_lattice = true;
|
||||
for (int i = 0; i < 3; i++)
|
||||
same_lattice = same_lattice && std::abs(e2[i] - e1[i]) <= 0.02 * e1[i];
|
||||
if (same_lattice) {
|
||||
const PooledEvidence e_best = pooled_evidence(*indexer, *best.result);
|
||||
const PooledEvidence e_other = pooled_evidence(*indexer, *other);
|
||||
const bool adopt = ValidationEvidencePrefers(e_best, e_other);
|
||||
logger.Info("Second domain: the spots the lattice leaves give the same lattice in "
|
||||
"another orientation, which puts {}/{} validation spots on itself ({} at "
|
||||
"a wrong spindle angle) against the lattice's {}/{} ({}) - {}",
|
||||
e_other.on_lattice, e_other.spots, e_other.by_chance, e_best.on_lattice,
|
||||
e_best.spots, e_best.by_chance,
|
||||
adopt ? "it is the crystal, adopting it" : "keeping the lattice");
|
||||
if (adopt) {
|
||||
best.result = std::move(*other);
|
||||
best.score = count_indexed(*indexer, *best.result);
|
||||
best.name += " (second domain)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the run has a lattice at all is decided here, on the pooled spots rather than on
|
||||
// the frame count the rescues above are steered by. A frame count asks each frame to clear
|
||||
// a bar of its own and then counts the frames that did, which measures the spots per frame
|
||||
|
||||
Reference in New Issue
Block a user