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
* 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>
154 lines
4.9 KiB
C++
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);
|
|
}
|