-z (now also --reference; --reference-mtz still works) takes an SF-mmCIF (e.g. a deposited -sf.cif, gzipped or not) as well as an MTZ. The format is recognised by content: a file starting with "MTZ " is an MTZ, anything else is parsed as CIF. The first merged reflection block with the requested column (or, by default, one the auto choice accepts) is converted to a gemmi::Mtz in memory with GEMMI's CifToMtz and then read by the unchanged MTZ loader, so the in-memory reference is exactly what the MTZ path yields. Unmerged (_diffrn_refln) and anomalous-only blocks are passed over; the log names the block used. The R-free set comes from _refln.status (f -> FreeR_flag 0, o -> 1: the CCP4 convention the loader already reads) and is preferred to _refln.pdbx_r_free_flag, whose convention varies by program; a status column with no 'f' is ignored and pdbx_r_free_flag is used instead. Checked on two open-arm sets in the deposited setting (one with F_meas_au only, one with intensity_meas): reference loaded with the deposited cell and group, the inherited free set agrees with the deposited status 'f' on every common reflection, and the merge correlates at CC 0.99 with the deposition. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
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]") {
|
|
// 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);
|
|
}
|