diff --git a/image_analysis/structure_refinement/ModelValidation.cpp b/image_analysis/structure_refinement/ModelValidation.cpp index 3673483dc..3bbf9af2d 100644 --- a/image_analysis/structure_refinement/ModelValidation.cpp +++ b/image_analysis/structure_refinement/ModelValidation.cpp @@ -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 &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 frame_r(frames.size(), 0.0); + std::vector frame_sg(frames.size(), nullptr); + ParallelFor(static_cast(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 &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; diff --git a/image_analysis/structure_refinement/ModelValidation.h b/image_analysis/structure_refinement/ModelValidation.h index 47bffdd26..bcfcad445 100644 --- a/image_analysis/structure_refinement/ModelValidation.h +++ b/image_analysis/structure_refinement/ModelValidation.h @@ -219,8 +219,8 @@ struct ModelValidationResult { // the runner-up beats the lead the same model in a random orientation takes, since a random model // also picks a winner. Holohedral crystals (no twin laws) are unaffected either way. // -// nthreads is what the null's replicates run on - they are independent of each other and of the real -// model, so they run at once. Nothing else here is threaded, and the answer does not depend on it. +// nthreads is what the null's replicates and the candidate changes of basis run on - each is +// independent of the others, so they run at once. The answer does not depend on it. // // report_shell_d_min is the merge statistics' own shell bounds, coarse to fine, and it is what // cc_model_shells is binned on. Sharing the grid is the point: a reader has to be able to put a