From d172f22074597a39b92166770df77eb1356c2679 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Mon, 12 Oct 2026 12:59:43 +0200 Subject: [PATCH 1/2] tools: remove jfjoch_mr_replay A developer tool, not built or installed upstream. It also failed to link in both portable builds (static cuFFT without the device-link step on Linux with CUDA, ws2_32 on Windows). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3 --- tools/CMakeLists.txt | 5 -- tools/jfjoch_mr_replay.cpp | 112 ------------------------------------- 2 files changed, 117 deletions(-) delete mode 100644 tools/jfjoch_mr_replay.cpp diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index d8744bb51..b6754ec31 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,11 +12,6 @@ TARGET_LINK_LIBRARIES(jfjoch_extract_hkl JFJochReader) # rugnux, the single offline analysis CLI, lives in rugnux/ next to its library. -# rugnux --af-model's molecular replacement re-run on a run's merged MTZ files (developer utility, not -# installed): a survey of the search over many datasets in minutes instead of a reprocessing each. -ADD_EXECUTABLE(jfjoch_mr_replay jfjoch_mr_replay.cpp) -TARGET_LINK_LIBRARIES(jfjoch_mr_replay JFJochStructureRefinement) - # In-place re-compress /entry/data/data of a _data file from bitshuffle/LZ4 to bitshuffle/zstd. ADD_EXECUTABLE(jfjoch_recompress jfjoch_recompress.cpp) TARGET_LINK_LIBRARIES(jfjoch_recompress JFJochHDF5Wrappers) diff --git a/tools/jfjoch_mr_replay.cpp b/tools/jfjoch_mr_replay.cpp deleted file mode 100644 index addf45aea..000000000 --- a/tools/jfjoch_mr_replay.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -// Developer utility: rugnux --af-model's molecular replacement re-run on the merged reflections a rugnux -// run wrote, without reprocessing the images - the answer's p.mtz and, where the run made one, the twin -// hypothesis' p_twin_.mtz. The search, the decision and the hand check are rugnux's own code; the -// output is the MODEL PRIOR keys and table of the rugnux report. -// -// jfjoch_mr_replay [ ] -// -// The answer group is the one the run searched in: where it took the model's enantiomorph into its -// label, the file carries the relabelled group and the original has to be given here. - -#include -#include -#include -#include - -#include - -#include "../common/Logger.h" -#include "../image_analysis/structure_refinement/MolecularReplacement.h" - -namespace { - -std::vector read_merged(const std::string &path, UnitCell &cell) { - gemmi::Mtz mtz; - mtz.read_file(path); - const gemmi::UnitCell &c = mtz.cell; - cell = UnitCell(c.a, c.b, c.c, c.alpha, c.beta, c.gamma); - const auto col = [&](const char *label) -> int { - const gemmi::Mtz::Column *x = mtz.column_with_label(label); - return x ? static_cast(x->idx) : -1; - }; - const int i_mean = col("IMEAN"), s_mean = col("SIGIMEAN"), ip = col("I(+)"), sip = col("SIGI(+)"), - im = col("I(-)"), sim = col("SIGI(-)"), f = col("F"), sf = col("SIGF"), fp = col("F(+)"), - sfp = col("SIGF(+)"), fm = col("F(-)"), sfm = col("SIGF(-)"), free = col("FreeR_flag"); - const size_t nc = mtz.columns.size(); - const auto get = [&](size_t row, int i) { return i < 0 ? NAN : mtz.data[row * nc + i]; }; - std::vector out; - for (size_t row = 0; row < static_cast(mtz.nreflections); row++) { - MergedReflection r; - r.h = static_cast(get(row, 0)); - r.k = static_cast(get(row, 1)); - r.l = static_cast(get(row, 2)); - r.I = get(row, i_mean); - r.sigma = get(row, s_mean); - r.I_plus = get(row, ip); - r.sigma_plus = get(row, sip); - r.I_minus = get(row, im); - r.sigma_minus = get(row, sim); - r.F = get(row, f); - r.sigmaF = get(row, sf); - r.F_plus = get(row, fp); - r.sigmaF_plus = get(row, sfp); - r.F_minus = get(row, fm); - r.sigmaF_minus = get(row, sfm); - r.rfree_flag = get(row, free) == 0.0f; - r.d = static_cast(1.0 / std::sqrt(c.calculate_1_d2(gemmi::Miller{{r.h, r.k, r.l}}))); - out.push_back(r); - } - return out; -} - -} // namespace - -int main(int argc, char **argv) { - if (argc != 4 && argc != 6) { - std::cerr << "Usage: jfjoch_mr_replay [ ]\n"; - return 1; - } - Logger logger("jfjoch_mr_replay"); - const gemmi::SpaceGroup *answer_sg = gemmi::find_spacegroup_by_name(argv[2]); - UnitCell cell{}, twin_cell{}; - const std::vector merged = read_merged(argv[3], cell); - std::vector twin; - std::vector merges{{"MERGE", &merged, cell, *answer_sg}}; - if (argc == 6) { - twin = read_merged(argv[5], twin_cell); - merges.push_back({"TWIN_SUBGROUP_MERGE", &twin, twin_cell, *gemmi::find_spacegroup_by_name(argv[4])}); - } - const size_t nthreads = std::max(1u, std::thread::hardware_concurrency()); - MolecularReplacementResult mr = RunMolecularReplacement(argv[1], merges, logger, nthreads); - if (mr.chosen >= 0 && mr.decision == "ENANTIOMORPH") - CheckEnantiomorphHand(mr, merged, *answer_sg, logger, nthreads); - - std::cout << "MODEL_PRIOR= " << (mr.ok ? "PERFORMED" : "NOT_PERFORMED") << "\n"; - if (!mr.ok) { - std::cout << "MODEL_PRIOR_REASON= " << mr.failure_reason << "\n"; - return 0; - } - std::cout << "MODEL_PRIOR_DECISION= " << mr.decision << "\n"; - if (mr.chosen >= 0) { - std::cout << "MODEL_PRIOR_SPACE_GROUP= " << mr.candidates[mr.chosen].space_group.xhm() << "\n"; - std::cout << "MODEL_PRIOR_COPIES= " << mr.candidates[mr.chosen].copies << "\n"; - } - if (mr.hand_check.performed) - std::cout << "MODEL_PRIOR_HAND_CHECK= " << (mr.hand_check.adopted ? "ADOPTED" : "VETOED") << "\n"; - std::cout << fmt::format("MODEL_PRIOR_SECONDS= {:.2f}\n", mr.t_model + mr.t_transform + mr.t_rotation - + mr.t_translation + mr.t_rescore + mr.t_null - + mr.hand_check.seconds); - for (const MRCandidate &c : mr.candidates) { - const auto r = [](double v) { return std::isfinite(v) ? fmt::format("{:.3f}", v) : std::string("-"); }; - std::cout << fmt::format( - "# {:<19s} {:<11s} {:5.2f} {:6d} {:6d} {:5.1f} {:5.1f} {:4.1f}% {:>6s} {:>6s} {:>10s} {}\n", - c.merge == 0 ? "MERGE" : "TWIN_SUBGROUP_MERGE", c.space_group.xhm(), c.vm, c.copies, c.copies_likely, - c.rf_z, c.tf_z, - 100 * c.clash_fraction, r(c.r_work), r(c.r_free), - std::isfinite(c.null_sigma) ? fmt::format("{:+.1f}", c.null_sigma) : std::string("-"), c.verdict); - } - return 0; -} From 140739a69f4f3a99d004f025bcb3e656b3be8ec9 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Mon, 12 Oct 2026 12:59:55 +0200 Subject: [PATCH 2/2] CI: unit tests and the validation battery take turns on the GPU Both run on the same runner machine and its one GPU. In run 380387 the unit-test job reached Rugnux_FirstPassMemoMatchesRecomputation while the battery job (same runner, same minute) was processing one of its larger sets; the FFT indexer's cuFFT plan found 0.1 of 15.6 GB free and the test failed, as a GPU out-of-memory is meant to. The unit-test job joins the battery's concurrency group, so the two no longer overlap (which also keeps the battery's timings clean). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3 --- .gitea/workflows/build_and_test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitea/workflows/build_and_test.yml b/.gitea/workflows/build_and_test.yml index a6c1774ca..1bb6f53de 100644 --- a/.gitea/workflows/build_and_test.yml +++ b/.gitea/workflows/build_and_test.yml @@ -411,6 +411,12 @@ jobs: # was not already run, and a release build is the one case where the wait is pure delay. if: "!(github.event_name == 'workflow_dispatch' && (github.event.inputs.create_release == 'true' || github.event.inputs.create_release == true))" runs-on: jfjoch_rocky8 + # The battery (battery.yml) runs on the same machine and GPU, and its rugnux takes the card's + # memory as its sets need it: a GPU test beside it finds the card full, and a GPU out-of-memory + # ends the run by design. One at a time, in the battery's own concurrency group. + concurrency: + group: battery + cancel-in-progress: false container: image: gitea.psi.ch/leonarski_f/jfjoch_rocky8:2609 options: --gpus all