Files
Jungfraujoch/tests/LoadReferenceSfCifTest.cpp
T
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

154 lines
4.9 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 <fstream>
#include <string>
#include <zlib.h>
#include "../image_analysis/LoadFCalcFromMtz.h"
namespace {
// A synthetic structure-factor mmCIF in the layout of a PDB deposition: an unmerged block first
// (which the loader must pass over), then the merged block with intensities, sigmas and the
// _refln.status test-set marks ('f' free, 'o' working, '-' neither). Cell and space group are
// synthetic.
const char* SF_CIF = R"(data_r0testsf_unmerged
_cell.length_a 50.0
_cell.length_b 60.0
_cell.length_c 70.0
_cell.angle_alpha 90.0
_cell.angle_beta 90.0
_cell.angle_gamma 90.0
_symmetry.space_group_name_H-M 'P 21 21 21'
loop_
_diffrn_refln.index_h
_diffrn_refln.index_k
_diffrn_refln.index_l
_diffrn_refln.intensity_net
_diffrn_refln.intensity_sigma
1 0 0 5.0 1.0
1 0 0 6.0 1.0
#
data_r0testsf
_cell.length_a 50.0
_cell.length_b 60.0
_cell.length_c 70.0
_cell.angle_alpha 90.0
_cell.angle_beta 90.0
_cell.angle_gamma 90.0
_symmetry.space_group_name_H-M 'P 21 21 21'
loop_
_refln.index_h
_refln.index_k
_refln.index_l
_refln.status
_refln.intensity_meas
_refln.intensity_sigma
0 0 2 o 100.0 5.0
0 1 1 f 200.0 6.0
1 1 1 o 300.0 7.0
1 2 3 f 400.0 8.0
2 2 2 - 500.0 9.0
3 1 4 o ? ?
)";
std::string WriteFile(const std::string& name, const std::string& content) {
const auto path = (std::filesystem::temp_directory_path() / name).string();
std::ofstream(path) << content;
return path;
}
std::string WriteGzFile(const std::string& name, const std::string& content) {
const auto path = (std::filesystem::temp_directory_path() / name).string();
gzFile f = gzopen(path.c_str(), "wb");
gzwrite(f, content.data(), static_cast<unsigned>(content.size()));
gzclose(f);
return path;
}
void CheckSyntheticReference(const ReferenceMtzData& ref) {
CHECK(ref.source == "SF-mmCIF block r0testsf");
CHECK(ref.used_column == "IMEAN");
CHECK_FALSE(ref.squared);
REQUIRE(ref.cell.has_value());
CHECK(ref.cell->a == Catch::Approx(50.0));
CHECK(ref.cell->b == Catch::Approx(60.0));
CHECK(ref.cell->c == Catch::Approx(70.0));
REQUIRE(ref.space_group_number.has_value());
CHECK(*ref.space_group_number == 19);
// The reflection with no intensity ('?') is not a reference reflection.
REQUIRE(ref.reflections.size() == 5);
CHECK(ref.reflections[1].h == 0);
CHECK(ref.reflections[1].k == 1);
CHECK(ref.reflections[1].l == 1);
CHECK(ref.reflections[1].I == Catch::Approx(200.0));
CHECK(ref.reflections[4].I == Catch::Approx(500.0));
// 'f' is the test set; 'o' and '-' are not.
REQUIRE(ref.has_free_flags);
CHECK(ref.n_free == 2);
CHECK_FALSE(ref.reflections[0].rfree_flag);
CHECK(ref.reflections[1].rfree_flag);
CHECK_FALSE(ref.reflections[2].rfree_flag);
CHECK(ref.reflections[3].rfree_flag);
CHECK_FALSE(ref.reflections[4].rfree_flag);
}
}
TEST_CASE("LoadReferenceMtz reads a structure-factor mmCIF", "[reference_mtz][portable]") {
// Named without a .cif extension: the format is recognised by content.
const auto path = WriteFile("rugnux_ref_sf.dat", SF_CIF);
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
CheckSyntheticReference(ref);
}
TEST_CASE("LoadReferenceMtz reads a gzipped structure-factor mmCIF", "[reference_mtz]") {
const auto path = WriteGzFile("rugnux_ref_sf.cif.gz", SF_CIF);
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
CheckSyntheticReference(ref);
}
TEST_CASE("LoadReferenceMtz SF-mmCIF: amplitudes and pdbx_r_free_flag when status marks no test set", "[reference_mtz]") {
// Only amplitudes: FP is squared to an intensity. The status column has no 'f', so the free set
// comes from pdbx_r_free_flag (here 1 marks free, as phenix writes it - the minority value).
const auto path = WriteFile("rugnux_ref_sf_fp.cif", R"(data_r0testsf
_cell.length_a 50.0
_cell.length_b 60.0
_cell.length_c 70.0
_cell.angle_alpha 90.0
_cell.angle_beta 90.0
_cell.angle_gamma 90.0
_symmetry.space_group_name_H-M 'P 21 21 21'
loop_
_refln.index_h
_refln.index_k
_refln.index_l
_refln.status
_refln.pdbx_r_free_flag
_refln.F_meas_au
_refln.F_meas_sigma_au
0 0 2 o 0 10.0 1.0
0 1 1 o 1 20.0 1.0
1 1 1 o 0 30.0 1.0
1 2 3 o 0 40.0 1.0
2 2 2 o 0 50.0 1.0
)");
const auto ref = LoadReferenceMtz(path);
std::filesystem::remove(path);
CHECK(ref.used_column == "FP");
CHECK(ref.squared);
REQUIRE(ref.reflections.size() == 5);
CHECK(ref.reflections[2].I == Catch::Approx(900.0));
REQUIRE(ref.has_free_flags);
CHECK(ref.n_free == 1);
CHECK(ref.reflections[1].rfree_flag);
}