ModelValidation: score the candidate changes of basis at the same time

Each candidate setting of the model was scored one after another on the
coarse shell (a density, an FFT and an isotropic scale fit, 0.2-0.5 s
each), and a run with a model in another setting scores them in both of
its validations. They share nothing but what they read, so they now run
on ParallelFor, each on its own copy of the model, and are read back in
their own order: the log lines, the ranking and the tie-break (first of
equal R wins) are those of the serial loop.

Measured (16-core workstation, loaded): 8sa8 3 candidates 0.64 s ->
0.22 s; 9ea5 3 candidates 1.47 s -> 0.50 s; 8xtg 1.45 s -> 0.40 s.
p.mtz, the maps, the map MTZ and the placed model md5-identical.

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:
2026-10-09 12:08:39 +02:00
co-authored by Claude Opus 5.5
parent 91e63b6f52
commit aabac1541a
2 changed files with 19 additions and 7 deletions
@@ -32,6 +32,7 @@
#include "../../common/CorrelationCoefficient.h"
#include "../../common/JFJochMath.h" // PI (M_PI is not standard, and MSVC does not define it)
#include "../../common/Logger.h"
#include "../../common/ParallelFor.h"
#include "../scale_merge/ReindexAmbiguity.h" // ReindexReflections
#include "../scale_merge/CrystalSetting.h" // CellMappingOperators
#include "ModelFFT.h"
@@ -481,13 +482,15 @@ ModelValidationResult Validate(const std::vector<MergedReflection> &merged,
"is not how the data describe this lattice; scoring {} change(s) of basis",
st.cell.a, st.cell.b, st.cell.c, st.cell.alpha, st.cell.beta, st.cell.gamma,
sg->xhm(), frames.size() - 1);
size_t best = 0;
double best_r = 0;
for (size_t i = 0; i < frames.size(); i++) {
// The candidates are scored at the same time, each on its own copy of the model, and read
// in their own order below, so the ranking and its tie-break are those of one after another.
std::vector<double> frame_r(frames.size(), 0.0);
std::vector<const gemmi::SpaceGroup *> frame_sg(frames.size(), nullptr);
ParallelFor(static_cast<int>(frames.size()), nthreads, [&](int i) {
gemmi::Structure trial = st;
const gemmi::SpaceGroup *trial_sg = sg;
if (i > 0 && !change_model_basis(trial, trial_sg, frames[i]))
continue; // no origin shift names the transformed group; not a basis we can take
return; // no origin shift names the transformed group; not a basis we can take
refractionalize_into(trial, data_cell);
trial.setup_cell_images();
// Each frame at the data's best indexing: the alternative indexings are only probed
@@ -499,8 +502,17 @@ ModelValidationResult Validate(const std::vector<MergedReflection> &merged,
if (same_point_group)
for (const gemmi::Op &law : reindex_ops)
r = std::min(r, frame_probe_r(trial, trial_sg, ReindexReflections(obs, law), d_min));
frame_r[i] = r;
frame_sg[i] = trial_sg;
});
size_t best = 0;
double best_r = 0;
for (size_t i = 0; i < frames.size(); i++) {
if (frame_sg[i] == nullptr)
continue;
const double r = frame_r[i];
logger.Info("Model validation: {:<12} -> {:<12} R {:.4f} on the coarse shell",
frames[i].triplet(), trial_sg->xhm(), r);
frames[i].triplet(), frame_sg[i]->xhm(), r);
if (i == 0 || r < best_r) {
best_r = r;
best = i;