diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 3c482e382..1b4aa19da 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -397,22 +397,28 @@ void Rugnux::StartSpeculativeGeometryProbe(std::shared_ptr run, const st run->prepass_detector_geometry_ = prepass_detector_geometry_; run->speculative_probe_.reset(); run->speculate_geometry_probe_ = false; - run->experiment_.BeamX_pxl(geometry[0]).BeamY_pxl(geometry[1]).DetectorDistance_mm(geometry[2]) - .PoniRot1_rad(geometry[3]).PoniRot2_rad(geometry[4]); run->indexing_probe_only_ = true; run->postrefine_probe_ = false; run->postrefine_probe_only_ = false; run->rerun_if_starved_ = false; + // The walk's other probe, at the geometry in hand, on a copy of its own: where this pass's own + // first pass has not left its memo the walk would index there too, after the pass. + auto hand = std::make_shared(*run); + run->experiment_.BeamX_pxl(geometry[0]).BeamY_pxl(geometry[1]).DetectorDistance_mm(geometry[2]) + .PoniRot1_rad(geometry[3]).PoniRot2_rad(geometry[4]); auto sp = std::make_shared(); sp->run = run; + sp->hand = hand; // A probe that fails leaves no memo, and the walk's own probe then runs it again and meets the // failure itself. It runs beside the merge of the pass that started it, on the same card: a merge // that does not fit beside it waits for it to end (GPUWorkBeside). - sp->done = std::async(std::launch::async, [run] { + sp->done = std::async(std::launch::async, [run, hand] { GPUWorkBeside beside; - try { - run->RunPipeline(nullptr, /*write_output=*/false, /*geometry_prepass=*/false); - } catch (const std::exception &) { + for (const auto &r : {run, hand}) { + try { + r->RunPipeline(nullptr, /*write_output=*/false, /*geometry_prepass=*/false); + } catch (const std::exception &) { + } } }); speculative_probe_ = std::move(sp); @@ -432,6 +438,8 @@ void Rugnux::CollectSpeculativeProbe() { speculative_probe_->done.wait(); for (const auto &m : speculative_probe_->run->first_pass_memo_) AddFirstPassMemo(m); + for (const auto &m : speculative_probe_->hand->first_pass_memo_) + AddFirstPassMemo(m); speculative_probe_.reset(); } @@ -491,7 +499,11 @@ std::vector Rugnux::SpotFindingKey(const DiffractionExperiment &x, } std::vector Rugnux::FirstPassInputKey() const { - std::vector k = ExperimentKey(); + return FirstPassInputKey(experiment_); +} + +std::vector Rugnux::FirstPassInputKey(const DiffractionExperiment &e) const { + std::vector k = ExperimentKey(e); // Run state the first pass reads besides the experiment. k.insert(k.end(), {static_cast(lattice_angles_demoted_), prepass_rotation_scale_.value_or(-1.0f), static_cast(force_rotation_result_.has_value()), diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index 3a1404ad1..65bcdf160 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -851,14 +851,17 @@ class Rugnux { [[nodiscard]] std::vector SpotFindingKey(const DiffractionExperiment &x, const AzimuthalIntegrationMapping &mapping) const; void AddFirstPassMemo(const FirstPassMemo &memo); - // The geometry walk's next indexing probe, started on a copy of the run as soon as the canonical - // pass has post-refined - it depends on nothing that pass does afterwards - so that it runs beside - // the pass's scaling, merge and output. It leaves its evidence in the copy's first-pass memo, and - // the walk's own probe takes it from there through the usual key check (CollectSpeculativeProbe). + // The geometry walk's next indexing probes - at the fit's geometry, then at the geometry in hand + // (a memo hit where the pass's own first pass left one) - started on copies of the run as soon as + // the canonical pass has post-refined - they depend on nothing that pass does afterwards - so that + // they run beside the pass's scaling, merge and output. They leave their evidence in the copies' + // first-pass memos, and the walk's own probes take it from there through the usual key check + // (CollectSpeculativeProbe). // RunAllPasses sets speculate_geometry_probe_ on the passes whose post-refinement the walk will // probe with an indexing pass. struct SpeculativeProbe { std::shared_ptr run; + std::shared_ptr hand; // the walk's probe at the geometry in hand, after run's std::future done; }; std::shared_ptr speculative_probe_; @@ -869,6 +872,11 @@ class Rugnux { void StartSpeculativeGeometryProbe(std::shared_ptr run, const std::array &geometry); void CollectSpeculativeProbe(); [[nodiscard]] std::vector FirstPassInputKey() const; + // The same, with another experiment in place of the run's. + [[nodiscard]] std::vector FirstPassInputKey(const DiffractionExperiment &e) const; + // Whether the geometry walk after the canonical pass will take a round, asked by that pass from + // what it already has in hand; `fit` is its post-refinement running beside the merge (RefinedPasses). + [[nodiscard]] bool GeometryWalkTakesRound(const ProcessResult &result, const std::future &fit) const; // The part of FirstPassInputKey that is the experiment and the spot-finding settings; without the // goniometer's angles where rotation_angles is false (SpotFindingKey). [[nodiscard]] std::vector ExperimentKey(bool rotation_angles = true) const { diff --git a/rugnux/RugnuxPasses.cpp b/rugnux/RugnuxPasses.cpp index caf84a643..787290c16 100644 --- a/rugnux/RugnuxPasses.cpp +++ b/rugnux/RugnuxPasses.cpp @@ -727,6 +727,45 @@ RotationScaleWalk Rugnux::WalkRotationScaleBeside(const ProcessResult &pass1, Ro } } +// The geometry walk's first question (see RefinedPasses), asked by the canonical pass before its +// space-group search and again before its analyses: every round the walk takes ends in a pass that +// writes the files again, so a canonical pass the walk is going to replace only spends its merge, +// model validation and files on an answer nobody reads. The question needs the pass's own +// post-refinement and, for a move of less than a step, the walk's two indexing probes, which the +// speculative probe runs beside the merge (or the pass's own first pass has run) - taken from the +// first-pass memos the walk's probes read them from, so the answer is the walk's own. Nothing waits +// for it: an answer not yet in leaves the pass to finish as it always did. +bool Rugnux::GeometryWalkTakesRound(const ProcessResult &result, const std::future &fit) const { + const auto ready = [](const std::future &f) { + return !f.valid() || f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + }; + if (!speculate_geometry_probe_ || cancelled_ || !ready(fit) || !prepass_detector_geometry_ + || !result.post_refine || !result.post_refine->detector_refined) + return false; + if (result.post_refine->large_move) + return true; + if (config_.verify_first_pass_memo || !speculative_probe_ || !ready(speculative_probe_->done)) + return false; + const auto evidence_at = [&](const std::array &g) -> std::optional { + DiffractionExperiment e = experiment_; + e.SetSpaceGroup(user_fixed_sg_); // as RunPipeline starts every pass + e.BeamX_pxl(g[0]).BeamY_pxl(g[1]).DetectorDistance_mm(g[2]).PoniRot1_rad(g[3]).PoniRot2_rad(g[4]); + const std::vector key = FirstPassInputKey(e); + const std::vector *memos[] = {&first_pass_memo_, &speculative_probe_->run->first_pass_memo_, + &speculative_probe_->hand->first_pass_memo_}; + for (const auto *list : memos) + for (const auto &m : *list) + if (m.key == key) + return m.evidence; + return std::nullopt; + }; + const auto at_hand = evidence_at({experiment_.GetBeamX_pxl(), experiment_.GetBeamY_pxl(), + experiment_.GetDetectorDistance_mm(), experiment_.GetPoniRot1_rad(), + experiment_.GetPoniRot2_rad()}); + const auto at_fit = evidence_at(*prepass_detector_geometry_); + return at_hand && at_fit && ValidationEvidencePrefers(*at_hand, *at_fit); +} + // The refined passes of the rotation two-pass, from the second pass on: everything after the // rotation-scale walk (see RunAllPasses). Empty where they ran beside the walk and it overturned them. std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, ProcessResult pass1, diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index be7bef8ca..55e5a2fd7 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -1243,6 +1243,14 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { // above triclinic - i.e. a promotion was made, which is what makes a later "the Laue class is // holohedral so there is no twin law" conclusion circular. bool promoted_point_group = false; + // A canonical pass the geometry walk is going to replace stops here, where the answer is + // already in (GeometryWalkTakesRound); it is asked again below, before the analyses. Not with a + // _process.h5 open: that file is written whole by every pass that opens it. + if (!writer && GeometryWalkTakesRound(result, p.postrefine_ahead)) { + logger.Info("The geometry walk will re-integrate at the geometry the post-refinement asks for, " + "so this pass stops before its space-group search"); + return true; + } TimingMark search_mark(logger, "space-group search"); if (search_space_group) { SearchSpaceGroupOptions sg_opts; @@ -2775,14 +2783,16 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { // the files are rewritten by the pass that supersedes this one, and the one place that touches // experiment_ - the model frame - is undone by the SetSpaceGroup at the top of RunPipeline. // - // A refined pass run beside the rotation-scale walk is superseded too where the walk overturns it - // (see RunAllPasses); it waits here for the walk's answer. + // So is a canonical pass the geometry walk is going to replace, where that answer is in by now + // (GeometryWalkTakesRound). A refined pass run beside the rotation-scale walk is superseded too + // where the walk overturns it (see RunAllPasses); it waits here for the walk's answer. superseded = (!cancelled_ && quality_guard_pass1_ && quality_guard_pass1_->has_merge_statistics && ((bragg_adaptive_ && bkg_starved_fraction_ && *bkg_starved_fraction_ > spot_width::BKG_STARVED_MAX_FRACTION) || !RefinedPassIsWorse(*quality_guard_pass1_, result).empty())) || (lattice_arm_check_ && lattice_arm_check_->lost) // only with a _process.h5 open, see above + || GeometryWalkTakesRound(result, p.postrefine_ahead) || (rotation_scale_check_ && rotation_scale_check_->Overturns()); if (superseded) { logger.Info("This pass is going to be re-run, so it makes no report and writes no files.");