rugnux: joint sweeps - only a sweep that failed is given the crystal's cell; relations measured in parallel

The previous commit gave the first measured sweep's cell to every sweep after it. On a crystal of a
~600 A cell with a pseudo-centring that moved sweeps which index well de novo into another setting
or centring (two of twenty lost, two gained: still 13 joined), so the cell now goes only to a sweep
that found no lattice of the crystal on its own - it merged nothing, or nothing that correlates with
the reference on any axes - and that sweep is processed once more given the shared-cell target's
cell. A space group given as well (-S) was tried and refused by the metric check on such sweeps.

The isomorphism ratios and the held-out gains of the sweeps are independent of each other and are
now measured at once, one task per sweep (the held-out merge of the joined sweeps still built once).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-12 07:53:25 +01:00
1 parent 4345764a92
commit ec3f2ebb80
2 files changed
+28 -22

No files matched your search

+19 -5
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#include <future>
#include <map>
#include <numeric>
#include <random>
@@ -261,9 +262,13 @@ std::vector<HeldOutGain> MeasureHeldOutGains(const std::vector<const std::vector
out.se = std::sqrt(std::max(0.0, (ss - s * s / m) / (m - 1)));
return out;
};
std::vector<HeldOutGain> out;
// ... and each sweep against it on its own, all of them at once.
std::vector<std::future<HeldOutGain>> gains;
for (size_t i = 0; i < ds.size(); ++i)
out.push_back(measure(*ds[i], cob_ds[i]));
gains.push_back(std::async(std::launch::async, [&measure, &ds, &cob_ds, i] { return measure(*ds[i], cob_ds[i]); }));
std::vector<HeldOutGain> out;
for (auto &g : gains)
out.push_back(g.get());
return out;
}
@@ -434,7 +439,8 @@ JointSweepRelation RelateJointSweeps(const std::vector<JointMemberSweep> &sweeps
laws_mark.Stop();
TimingMark iso_mark(logger, "joint sweeps: isomorphism");
std::vector<size_t> doubtful;
// Each sweep's ratio is measured on its own, all of them at once.
std::vector<std::future<SweepIsomorphism>> isomorphism(sweeps.size());
for (size_t k = 0; k < sweeps.size(); ++k) {
JointSweepRelation::Sweep &s = out.sweeps[k];
if (static_cast<int>(k) == out.reference || !s.decision.empty())
@@ -445,8 +451,16 @@ JointSweepRelation RelateJointSweeps(const std::vector<JointMemberSweep> &sweeps
"(best CC {:.3f})", s.cc);
continue;
}
const SweepIsomorphism iso = MeasureSweepIsomorphism(sweeps[k].fulls, s.cob, ref.fulls,
gemmi::Op::identity(), ref.space_group);
isomorphism[k] = std::async(std::launch::async, [&sweeps, &ref, cob = s.cob, k] {
return MeasureSweepIsomorphism(sweeps[k].fulls, cob, ref.fulls, gemmi::Op::identity(), ref.space_group);
});
}
std::vector<size_t> doubtful;
for (size_t k = 0; k < sweeps.size(); ++k) {
if (!isomorphism[k].valid())
continue;
JointSweepRelation::Sweep &s = out.sweeps[k];
const SweepIsomorphism iso = isomorphism[k].get();
s.iso_ratio = iso.ratio;
s.iso_se = iso.se;
s.iso_n = iso.n;
+9 -17
View File
@@ -4152,21 +4152,10 @@ int RugnuxCli::RunJointSweeps(const RugnuxSweepPlan &plan) {
};
std::vector<JointMemberSweep> members;
std::vector<std::string> inputs;
// The sweeps are of one crystal: once one has merged with a measured accuracy (its error model resolved
// an ISa, as the reference is chosen), its cell is given to the sweeps after it as a reference cell (-C),
// which de novo indexing of one sweep can otherwise miss on a large cell - and a lattice it misses costs
// more than a whole run before it is found to merge nothing. Where -C was given, every sweep has it.
std::optional<UnitCell> crystal_cell;
std::vector<bool> cell_given(plan.sweeps.size(), false);
for (size_t k = 0; k < plan.sweeps.size(); k++) {
logger.Info("Sweep {} of {}: {}, processed on its own", k + 1, plan.sweeps.size(), plan.sweeps[k].label);
cell_given[k] = crystal_cell.has_value() || fixed_reference_unit_cell.has_value();
members.push_back(run_member(k, std::nullopt, crystal_cell, ""));
members.push_back(run_member(k, std::nullopt, std::nullopt, ""));
inputs.push_back(plan.sweeps[k].input);
if (!cell_given[k] && members[k].isa > 0.0) {
crystal_cell = members[k].cell;
logger.Info("Sweep {}: its cell is given to the sweeps after it", k + 1);
}
}
// One cell for the crystal (SharedCellTarget): a sweep whose own cell disagrees with it - joined or not
@@ -4177,12 +4166,15 @@ int RugnuxCli::RunJointSweeps(const RugnuxSweepPlan &plan) {
{
const JointSweepRelation &first = relation;
const int target = SharedCellTarget(members, first);
// A sweep processed before any cell was given that found no lattice of this crystal - it merged
// nothing, or nothing that correlates with the reference on any axes - is processed again given the
// target sweep's cell. Its join is then decided on its intensities, as every sweep's is.
for (size_t k = 0; target >= 0 && k < members.size(); ++k) {
// A sweep that found no lattice of this crystal on its own - it merged nothing, or nothing that
// correlates with the reference on any axes - is processed again given the target sweep's cell (-C):
// de novo indexing of one sweep can miss a large cell that the crystal's other sweeps have measured.
// Its join is then decided on its intensities, as every sweep's is. Only a sweep that failed is given
// the cell: given to every sweep from the start, it moved sweeps that index well de novo into another
// setting or centring. Where -C was given, every sweep already had a cell.
for (size_t k = 0; target >= 0 && !fixed_reference_unit_cell && k < members.size(); ++k) {
const std::string &decision = first.sweeps[k].decision;
if (cell_given[k] || (decision != "no merge" && decision != "no axes"))
if (decision != "no merge" && decision != "no axes")
continue;
logger.Info("Sweep {}: {}; processed again given sweep {}'s cell", k + 1, first.sweeps[k].why_left_out,
target + 1);