Merge branch 'ci-fix' into rc175: jfjoch_mr_replay leaves the repository; unit tests and the battery take turns on the CI GPU
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// 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_<group>.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 <model> <answer group> <p.mtz> [<twin group> <p_twin.mtz>]
|
||||
//
|
||||
// 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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include <gemmi/mtz.hpp>
|
||||
|
||||
#include "../common/Logger.h"
|
||||
#include "../image_analysis/structure_refinement/MolecularReplacement.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<MergedReflection> 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<int>(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<MergedReflection> out;
|
||||
for (size_t row = 0; row < static_cast<size_t>(mtz.nreflections); row++) {
|
||||
MergedReflection r;
|
||||
r.h = static_cast<int>(get(row, 0));
|
||||
r.k = static_cast<int>(get(row, 1));
|
||||
r.l = static_cast<int>(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<float>(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 <model> <answer group> <p.mtz> [<twin group> <p_twin.mtz>]\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<MergedReflection> merged = read_merged(argv[3], cell);
|
||||
std::vector<MergedReflection> twin;
|
||||
std::vector<MRMerge> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user