Space-group search and frame disposition: three host-side waits shortened
The first arm's space-group search now runs beside the wait for the all-observation merge and the search started on it, instead of after both. Classifying a reflection for a candidate group asks gemmi twice which operators fix it (once for the absence, once for the epsilon factor); one pass over the operators answers both, with the absence read off a fixing operator's own translation, which is what gemmi's test reduces to once the centring has been checked. The delta-CC1/2 frame disposition builds its per-reflection totals on all threads, each taking a range of reflections and walking every observation so the sums keep their order, and measures up to sixteen candidate chains side by side with a worker's scratch made on first use. Same numbers, same bits; only the waiting changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
1 parent
bbcae8ae7f
commit
093ac1612c
3 files changed
+44
-19
No files matched your search
@@ -3214,19 +3214,27 @@ void RotationScaleMerge::MeasureBatchDeltaCCHalf(int n_groups) {
|
||||
std::vector<Sums> d;
|
||||
std::vector<int32_t> touched;
|
||||
};
|
||||
const int n_workers = static_cast<int>(std::clamp<size_t>(nthreads, 1, 8));
|
||||
// A worker's scratch is as long as the merge, so it is made on first use, by the worker: the
|
||||
// workers a measurement does not need cost nothing, and the ones it does zero theirs side by side.
|
||||
const int n_workers = static_cast<int>(std::clamp<size_t>(std::min<size_t>(nthreads, n_batch), 1, 16));
|
||||
std::vector<RangeScratch> scratch(n_workers);
|
||||
for (auto &sc : scratch)
|
||||
sc.d.assign(n_groups, Sums{});
|
||||
auto scratch_of = [&](int t) -> RangeScratch & {
|
||||
if (scratch[t].d.empty()) scratch[t].d.assign(n_groups, Sums{});
|
||||
return scratch[t];
|
||||
};
|
||||
// Each worker takes a range of the reflections and walks every observation for them, so a
|
||||
// reflection's sums are added in observation order whatever the split: the one serial sum.
|
||||
auto build_totals = [&]() {
|
||||
std::fill(tot.begin(), tot.end(), Sums{});
|
||||
for (int i = 0; i < n_full; ++i) {
|
||||
const int g = obs_group[i];
|
||||
if (g < 0 || frame_rejected[obs_frame[i]]) continue;
|
||||
Sums &t = tot[g];
|
||||
t.n += 1; t.s1 += obs_I[i]; t.s2 += obs_I[i] * obs_I[i];
|
||||
t.w += obs_info[i];
|
||||
}
|
||||
ParallelChunks(n_groups, nthreads, [&](int g0, int g1) {
|
||||
std::fill(tot.begin() + g0, tot.begin() + g1, Sums{});
|
||||
for (int i = 0; i < n_full; ++i) {
|
||||
const int g = obs_group[i];
|
||||
if (g < g0 || g >= g1 || frame_rejected[obs_frame[i]]) continue;
|
||||
Sums &t = tot[g];
|
||||
t.n += 1; t.s1 += obs_I[i]; t.s2 += obs_I[i] * obs_I[i];
|
||||
t.w += obs_info[i];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// delta-CC1/2 of the frames [f0, f1]. delta_cc is the difference in CC units, which is what a user
|
||||
@@ -3289,9 +3297,10 @@ void RotationScaleMerge::MeasureBatchDeltaCCHalf(int n_groups) {
|
||||
return r;
|
||||
};
|
||||
auto measure = [&](int f0, int f1) {
|
||||
accumulate(scratch[0], f0, f1);
|
||||
const RangeDelta r = evaluate(scratch[0]);
|
||||
clear(scratch[0]);
|
||||
RangeScratch &sc = scratch_of(0);
|
||||
accumulate(sc, f0, f1);
|
||||
const RangeDelta r = evaluate(sc);
|
||||
clear(sc);
|
||||
return r;
|
||||
};
|
||||
// Every range in `ranges`, side by side. Ranges that start on the same frame are one chain, measured
|
||||
@@ -3311,7 +3320,7 @@ void RotationScaleMerge::MeasureBatchDeltaCCHalf(int n_groups) {
|
||||
std::vector<RangeDelta> out(ranges.size());
|
||||
std::atomic<int> next_chain{0};
|
||||
parallel_detail::RunTasks(std::min(n_workers, n_chain), [&](int t) {
|
||||
RangeScratch &sc = scratch[t];
|
||||
RangeScratch &sc = scratch_of(t);
|
||||
for (int c = next_chain++; c < n_chain; c = next_chain++) {
|
||||
int added_to = ranges[sorted[chain_start[c]]].first - 1;
|
||||
for (size_t k = chain_start[c]; k < chain_start[c + 1]; ++k) {
|
||||
|
||||
@@ -144,9 +144,21 @@ namespace {
|
||||
if (CenteringAbsent(gops, hkl))
|
||||
return REFL_CENTRING_ABSENT;
|
||||
const uint8_t zone = static_cast<uint8_t>((MirrorZoneOf(mirrors, hkl) + 1) << 4);
|
||||
if (gops.is_systematically_absent(hkl))
|
||||
// gemmi's is_systematically_absent and epsilon_factor_without_centering in one pass over the
|
||||
// operators: both ask which of them fix hkl. The absence test adds each centring vector to a
|
||||
// fixing operator's translation, but no centring vector shifts the phase of this reflection
|
||||
// (above), so the operator's own translation decides, as it does for one with none.
|
||||
const gemmi::Op::Miller denh = {{gemmi::Op::DEN * hkl[0], gemmi::Op::DEN * hkl[1], gemmi::Op::DEN * hkl[2]}};
|
||||
int epsilon = 0;
|
||||
bool absent = false;
|
||||
for (const gemmi::Op& op : gops.sym_ops)
|
||||
if (op.apply_to_hkl_without_division(hkl) == denh) {
|
||||
++epsilon;
|
||||
absent |= gemmi::GroupOps::has_phase_shift(op.tran, hkl);
|
||||
}
|
||||
if (absent)
|
||||
return REFL_ABSENT | zone;
|
||||
return REFL_PRESENT | zone | (gops.epsilon_factor_without_centering(hkl) > 1 ? REFL_ON_AXIS : 0);
|
||||
return REFL_PRESENT | zone | (epsilon > 1 ? REFL_ON_AXIS : 0);
|
||||
}
|
||||
|
||||
// The zone a mirror fixes, named the way a crystallographer names it - "h0l" for the mirror
|
||||
|
||||
@@ -1916,6 +1916,11 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) {
|
||||
// else. Whether the all-observation arm is wanted at all is known only from the first arm
|
||||
// (below), so nothing of it reaches the run before then. A merge that failed beside the
|
||||
// others is made again where it is wanted, in series, as it would have been without it.
|
||||
// The first arm's search runs beside the wait for that merge and the search on it.
|
||||
TimingMark first_arm_mark(logger, "SearchSpaceGroup (first arm)");
|
||||
auto first_search_ahead = std::async(std::launch::async, [&merged = sm.merged, sg_opts] {
|
||||
return SearchSpaceGroup(merged, sg_opts);
|
||||
});
|
||||
std::optional<RotationScaleMerge::Result> all_merge;
|
||||
std::future<SearchSpaceGroupResult> all_search_ahead;
|
||||
if (rsm_all && !all_arm_ahead.valid())
|
||||
@@ -1937,8 +1942,7 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) {
|
||||
return SearchSpaceGroup(merged, all_opts);
|
||||
});
|
||||
}
|
||||
TimingMark first_arm_mark(logger, "SearchSpaceGroup (first arm)");
|
||||
auto sg_search = SearchSpaceGroup(sm.merged, sg_opts);
|
||||
auto sg_search = first_search_ahead.get();
|
||||
first_arm_mark.Stop();
|
||||
|
||||
// The Lorentz-filtered arm's merge, kept past the point where the all-observation one
|
||||
|
||||
Reference in new issue
Block a user