// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include #include #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(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]") { // 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); }