Refinement: an exception in a trial slot or in RefineModel no longer ends the run

A slot builder thread that throws (gemmi's topology or hydrogen placement, bad_alloc) would
call std::terminate - rugnux or the broker gone. The slot is now left out as one the card
cannot take is, with a warning, and MakeOwnSlot's GPU engine catches std::exception, not only
JFJochException. Both callers of RefineModel (after the merge, and --mode refine) catch
std::exception: the refinement is reported NOT_PERFORMED with "refinement failed: <what>" and
the run writes everything else (cell e.s.d.s, unmerged MTZ, report), as the refinement is
processing-inert.

Test: --mode refine on a cell too large for the bulk-solvent grid comes back with
REFINE= NOT_PERFORMED and the reason, not an exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit dc4e99fe4b3da2a9a3fa4290b5f70bfcc8c7bdb8)
This commit is contained in:
leonarski_f committed 2026-10-26 15:38:58 +01:00
1 parent 0143d57eb0
commit 3d382ce0a0
4 files changed
+98 -59

No files matched your search

+76 -54
View File
@@ -45,6 +45,62 @@ gemmi::Mtz MakeMtz(const std::vector<std::pair<std::string, char>> &columns,
bool InTestSet(int h, int k, int l) { return (h * 7 + k * 13 + l * 17) % 10 == 0; }
// Waters scattered over the test cell, and their own |F|^2 as the data: the model as given scores well.
void SyntheticRefineInput(gemmi::Structure &st, RugnuxReflections &data) {
const gemmi::SpaceGroup *sg = gemmi::find_spacegroup_by_name("P 21 21 21");
st.cell = test_cell;
st.spacegroup_hm = sg->xhm();
st.setup_cell_images();
gemmi::Model model(1);
gemmi::Chain chain("A");
std::mt19937 rng(7);
std::uniform_real_distribution<double> frac(0.0, 1.0);
for (int i = 0; i < 120; i++) {
gemmi::Residue res;
res.name = "HOH";
res.seqid = gemmi::SeqId(i + 1, ' ');
res.het_flag = 'H';
gemmi::Atom a;
a.name = "O";
a.element = gemmi::El::O;
a.pos = test_cell.orthogonalize(gemmi::Fractional(frac(rng), frac(rng), frac(rng)));
a.b_iso = 15.0f;
a.occ = 1.0f;
res.atoms.push_back(a);
chain.residues.push_back(res);
}
model.chains.push_back(chain);
st.models.push_back(model);
gemmi::DensityCalculator<gemmi::IT92<float>, float> dc;
dc.d_min = 2.0;
dc.grid.unit_cell = test_cell;
dc.grid.spacegroup = sg;
dc.set_refmac_compatible_blur(st.models[0]);
dc.put_model_density_on_grid(st.models[0]);
const gemmi::AsuData<std::complex<float>> f =
gemmi::transform_map_to_f_phi(dc.grid, true).prepare_asu_data(2.0, dc.blur);
data.name = "synthetic.mtz";
data.source = "MTZ";
data.cell = test_cell;
data.space_group = sg->xhm();
data.amplitudes = false;
for (const auto &hv : f.v) {
if (hv.hkl == gemmi::Miller{{0, 0, 0}})
continue;
const double fc = std::abs(hv.value);
MergedReflection m;
m.h = hv.hkl[0];
m.k = hv.hkl[1];
m.l = hv.hkl[2];
m.d = static_cast<float>(test_cell.calculate_d(hv.hkl));
m.I = static_cast<float>(fc * fc);
m.sigma = static_cast<float>(0.02 * fc * fc + 1.0);
data.reflections.push_back(m);
}
}
} // namespace
TEST_CASE("RugnuxReflectionsFromMtz takes observed amplitudes over map coefficients", "[refine_only]") {
@@ -124,62 +180,9 @@ TEST_CASE("ParseRugnuxReflections reads an MTZ from its bytes", "[refine_only]")
TEST_CASE("RunRefineOnly scores a model against intensities, with amplitudes and free flags made for it",
"[refine_only]") {
// Atoms scattered over the cell; the data are their own |F|^2, so the model as given scores well.
const gemmi::SpaceGroup *sg = gemmi::find_spacegroup_by_name("P 21 21 21");
gemmi::Structure st;
st.cell = test_cell;
st.spacegroup_hm = sg->xhm();
st.setup_cell_images();
gemmi::Model model(1);
gemmi::Chain chain("A");
std::mt19937 rng(7);
std::uniform_real_distribution<double> frac(0.0, 1.0);
for (int i = 0; i < 120; i++) {
gemmi::Residue res;
res.name = "HOH";
res.seqid = gemmi::SeqId(i + 1, ' ');
res.het_flag = 'H';
gemmi::Atom a;
a.name = "O";
a.element = gemmi::El::O;
a.pos = test_cell.orthogonalize(gemmi::Fractional(frac(rng), frac(rng), frac(rng)));
a.b_iso = 15.0f;
a.occ = 1.0f;
res.atoms.push_back(a);
chain.residues.push_back(res);
}
model.chains.push_back(chain);
st.models.push_back(model);
gemmi::DensityCalculator<gemmi::IT92<float>, float> dc;
dc.d_min = 2.0;
dc.grid.unit_cell = test_cell;
dc.grid.spacegroup = sg;
dc.set_refmac_compatible_blur(st.models[0]);
dc.put_model_density_on_grid(st.models[0]);
const gemmi::AsuData<std::complex<float>> f =
gemmi::transform_map_to_f_phi(dc.grid, true).prepare_asu_data(2.0, dc.blur);
RugnuxReflections data;
data.name = "synthetic.mtz";
data.source = "MTZ";
data.cell = test_cell;
data.space_group = sg->xhm();
data.amplitudes = false;
for (const auto &hv : f.v) {
if (hv.hkl == gemmi::Miller{{0, 0, 0}})
continue;
const double fc = std::abs(hv.value);
MergedReflection m;
m.h = hv.hkl[0];
m.k = hv.hkl[1];
m.l = hv.hkl[2];
m.d = static_cast<float>(test_cell.calculate_d(hv.hkl));
m.I = static_cast<float>(fc * fc);
m.sigma = static_cast<float>(0.02 * fc * fc + 1.0);
data.reflections.push_back(m);
}
SyntheticRefineInput(st, data);
RefinementSettings settings;
settings.score_only = true;
settings.gpu = false;
@@ -205,3 +208,22 @@ TEST_CASE("RunRefineOnly scores a model against intensities, with amplitudes and
CHECK(text.find("REFINE_FREE_FLAGS= GENERATED") != std::string::npos);
CHECK(text.find("REFINE_FRENCH_WILSON= TRUE") != std::string::npos);
}
TEST_CASE("RunRefineOnly reports a refinement that throws as not performed, with the reason", "[refine_only]") {
// The data's cell, a hundred metres on a side, makes the bulk-solvent mask's grid too large to allocate.
gemmi::Structure st;
RugnuxReflections data;
SyntheticRefineInput(st, data);
data.cell = gemmi::UnitCell(1e6, 1e6, 1e6, 90, 90, 90);
RefinementSettings settings;
settings.gpu = false;
settings.nthreads = 2;
Logger logger("RefineOnlyTest");
RefineOnlyResult r;
REQUIRE_NOTHROW(r = RunRefineOnly(data, st, 0.0, settings, logger));
CHECK_FALSE(r.refinement.ok);
CHECK(r.refinement.failure_reason.find("refinement failed") == 0);
const std::string text = RenderReportText(BuildRefineReportDocument("out", "synthetic.mtz", "model.cif", r), true);
CHECK(text.find("REFINE= NOT_PERFORMED") != std::string::npos);
CHECK(text.find("REFINE_REASON= refinement failed") != std::string::npos);
}