Files
Jungfraujoch/tests/LoadReferenceMtzFreeFlagsTest.cpp
leonarski_f 84228bf8be
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
v1.0.0-rc.173 (#83)
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports.
* jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls.
* Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results.
* Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable.
* Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate.
* Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do.
* Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence.
* Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags.
* Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check.
* Rugnux: Clear error messages when a data set needs more GPU or host memory than is available.

Reviewed-on: #83
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-29 15:57:32 +02:00

97 lines
4.0 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <catch2/catch_all.hpp>
#include <filesystem>
#include <map>
#include <tuple>
#include <vector>
#include <gemmi/mtz.hpp>
#include "../image_analysis/LoadFCalcFromMtz.h"
namespace {
// Write a small P4(3)2(1)2 MTZ with an IMEAN column and a FreeR_flag column whose free reflections
// carry `free_value` (and the rest its complement). ~1 in 10 reflections is flagged free.
std::string WriteMtzWithFreeR(const std::string& path, int free_value) {
gemmi::Mtz mtz(true);
mtz.spacegroup = gemmi::find_spacegroup_by_number(96);
mtz.set_cell_for_all(gemmi::UnitCell(78.0, 78.0, 37.0, 90.0, 90.0, 90.0));
mtz.add_dataset("test");
mtz.add_column("IMEAN", 'J', -1, -1, false);
mtz.add_column("FreeR_flag", 'I', -1, -1, false);
std::vector<float> data;
int nref = 0;
for (int h = 1; h <= 10; ++h)
for (int k = 1; k <= 10; ++k)
for (int l = 1; l <= 6; ++l) {
const bool is_free = ((h * 7 + k * 13 + l * 17) % 10 == 0);
const int flag = is_free ? free_value : (free_value == 0 ? 1 : 0);
data.insert(data.end(), {static_cast<float>(h), static_cast<float>(k),
static_cast<float>(l), 100.0f, static_cast<float>(flag)});
++nref;
}
mtz.nreflections = nref;
mtz.data = std::move(data);
mtz.write_to_file(path);
return path;
}
bool ExpectedFree(int h, int k, int l) { return (h * 7 + k * 13 + l * 17) % 10 == 0; }
}
TEST_CASE("LoadReferenceMtz imports FreeR (CCP4 convention: test set = 0)", "[reference_mtz][portable]") {
const auto path = (std::filesystem::temp_directory_path() / "rugnux_freer_ccp4.mtz").string();
WriteMtzWithFreeR(path, /*free_value=*/0);
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
REQUIRE(ref.has_free_flags);
CHECK(ref.n_free > 0);
CHECK(ref.n_free < static_cast<int>(ref.reflections.size()) / 2); // free is the minority
for (const auto& r : ref.reflections)
CHECK(r.rfree_flag == ExpectedFree(r.h, r.k, r.l));
}
TEST_CASE("LoadReferenceMtz imports FreeR (phenix convention: test set = 1) via the complement", "[reference_mtz]") {
// Here flag 0 is the majority (work), so the loader must take the complement and treat 1 as free.
const auto path = (std::filesystem::temp_directory_path() / "rugnux_freer_phenix.mtz").string();
WriteMtzWithFreeR(path, /*free_value=*/1);
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
REQUIRE(ref.has_free_flags);
CHECK(ref.n_free > 0);
CHECK(ref.n_free < static_cast<int>(ref.reflections.size()) / 2);
for (const auto& r : ref.reflections)
CHECK(r.rfree_flag == ExpectedFree(r.h, r.k, r.l));
}
TEST_CASE("LoadReferenceMtz without a FreeR column reports no free flags", "[reference_mtz]") {
const auto path = (std::filesystem::temp_directory_path() / "rugnux_no_freer.mtz").string();
gemmi::Mtz mtz(true);
mtz.spacegroup = gemmi::find_spacegroup_by_number(96);
mtz.set_cell_for_all(gemmi::UnitCell(78.0, 78.0, 37.0, 90.0, 90.0, 90.0));
mtz.add_dataset("test");
mtz.add_column("IMEAN", 'J', -1, -1, false);
std::vector<float> data;
int nref = 0;
for (int h = 1; h <= 6; ++h)
for (int k = 1; k <= 6; ++k)
for (int l = 1; l <= 4; ++l) {
data.insert(data.end(), {static_cast<float>(h), static_cast<float>(k),
static_cast<float>(l), 100.0f});
++nref;
}
mtz.nreflections = nref;
mtz.data = std::move(data);
mtz.write_to_file(path);
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
CHECK_FALSE(ref.has_free_flags);
CHECK(ref.n_free == 0);
}