Files
Jungfraujoch/tests/LoadReferenceSfCifTest.cpp
T
leonarski_fandClaude Opus 5.5 a8c559bf6b tests: jfjoch_portable_test, quick [portable] checks for the macOS and Windows jobs
A Catch2 executable that builds in the portable configurations (JFJOCH_VIEWER_ONLY /
JFJOCH_RUGNUX_ONLY) and links only what those build - JFJochRugnux, JFJochReader,
JFJochImageAnalysis, JFJochWriter, JFJochCommon - so it can run on the macOS arm64 and
Windows x64 jobs, where the receiver/broker/FPGA/HLS sources are not built and there is no GPU.
EXCLUDE_FROM_ALL, so a product build does not pay for it; catch2 is now made available in the
portable configure as well (it is EXCLUDE_FROM_ALL too).

The cases tagged [portable] cover what depends on the architecture, the compiler or the
standard library: bitshuffle/LZ4/zstd, HDF5 read-back (legacy/VDS/integrated, the direct-chunk
path), miniCBF/marCCD/SMV header parsing, CBOR, CPU spot finding, azimuthal mapping, Bragg
prediction/integration, gemmi MTZ/mmCIF. New in tests/PortableTest.cpp:
- a golden FNV-1a hash of two frames of compression_benchmark.h5, decoded from the raw chunk by
  the hperf and the classic bitshuffle and through the HDF5 filter (x86 hashes
  affea29c511b6ec2 / e46913009c95a1f1);
- a golden hash of a bitshuffle/LZ4 encode (the writer must produce the same bytes everywhere);
- the shipped bitshuffle block selector against the classic reference over elem 1/2/4/8 and
  block tails;
- the FFTW indexer, named explicitly, on a synthetic orthorhombic lattice (the existing FFT
  indexer lattice tests run only under CUDA);
- a 4-frame end-to-end rugnux run on the git-LFS rotation dataset (HDF5 via external links, CPU
  spot finding, indexing, integration); SKIPs when LFS was not pulled.
All 45 take ~4 s on Linux (~2 s without the LFS case), CPU-only build.

M_PI replaced by PI (common/JFJochMath.h) in the two tagged files that used it, as MSVC does not
define M_PI. The CBF gzip test shells out to gzip and is left untagged.

CI: build and run jfjoch_portable_test "[portable]" in build-windows (both variants),
build-rugnux-windows, build-macos-viewer and build-rugnux-macos, after the build and before
packaging.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
2026-09-27 23:44:53 +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);
}