`rugnux <images> <model> <reflections>` in any order, without --model or -z. Each positional file is classified by its contents (RugnuxInputKind.h, in JFJochRugnuxOutput - the library still reads no files): an MTZ (magic) or an mmCIF with a _refln / _diffrn_refln loop is reference data (-z; the input itself in --mode refine), a PDB file (a PDB record first, then ATOM/HETATM) or an mmCIF with an _atom_site loop is a model (--model), anything else is images for the readers. CBF (###CBF or _array_data) counts as images; a file whose first line is neither data_ nor a PDB record is decided there, so image files are not read through. An mmCIF holding both atoms and reflections is refused as ambiguous. At most one model and one reference, --model/--mr and -z included. Gzip read through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit 8f844b0bf86f338d20e8aed426a387722dd8d7cc)
80 lines
4.6 KiB
C++
80 lines
4.6 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 <zlib.h>
|
|
|
|
#include "../rugnux/RugnuxInputKind.h"
|
|
|
|
namespace {
|
|
const std::string MODEL_CIF = "data_test\n#\n_cell.length_a 50.0\nloop_\n_atom_site.group_PDB\n_atom_site.id\n"
|
|
"ATOM 1\n";
|
|
const std::string SF_CIF = "data_r1abcsf\n_cell.length_a 50.0\nloop_\n_refln.index_h\n_refln.index_k\n"
|
|
"_refln.index_l\n_refln.intensity_meas\n1 0 0 10.0\n";
|
|
const std::string PDB = "HEADER TEST\nREMARK 1 SYNTHETIC\nCRYST1 50.000 50.000 50.000 90.00 90.00 90.00 P 1\n"
|
|
"ATOM 1 CA ALA A 1 1.000 2.000 3.000 1.00 10.00 C\nEND\n";
|
|
|
|
std::string Write(const std::string &name, const std::string &content) {
|
|
std::ofstream(name, std::ios::binary) << content;
|
|
return name;
|
|
}
|
|
|
|
std::string WriteGz(const std::string &name, const std::string &content) {
|
|
gzFile f = gzopen(name.c_str(), "wb");
|
|
gzwrite(f, content.data(), static_cast<unsigned>(content.size()));
|
|
gzclose(f);
|
|
return name;
|
|
}
|
|
}
|
|
|
|
TEST_CASE("RugnuxInputKind_Classify", "[rugnux_inputs]") {
|
|
using K = RugnuxInputKind;
|
|
CHECK(ClassifyRugnuxInput(Write("ik_model.cif", MODEL_CIF)) == K::Model);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_sf.cif", SF_CIF)) == K::Reflections);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_diffrn.cif", "data_x\nloop_\n_diffrn_refln.index_h\n1\n")) == K::Reflections);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_model.pdb", PDB)) == K::Model);
|
|
CHECK(ClassifyRugnuxInput(WriteGz("ik_model.pdb.gz", PDB)) == K::Model);
|
|
CHECK(ClassifyRugnuxInput(WriteGz("ik_sf.cif.gz", SF_CIF)) == K::Reflections);
|
|
// the name says nothing: a model in a file called .mtz, reflections in one called .pdb
|
|
CHECK(ClassifyRugnuxInput(Write("ik_model_named.mtz", MODEL_CIF)) == K::Model);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_mtz_named.pdb", std::string("MTZ \0\0\0\0", 8) + "binary")) == K::Reflections);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_image.h5", std::string("\x89HDF\r\n\x1a\n\0\0\0\0", 12))) == K::Images);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_image.cbf", "###CBF: VERSION 1.5\ndata_image\n_array_data.data\n")) == K::Images);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_image2.cbf", "data_image\n_array_data.data\n_atom_site.x\n")) == K::Images);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_image.img", "{\nHEADER_BYTES= 512;\nDIM=2;\n}")) == K::Images);
|
|
CHECK(ClassifyRugnuxInput(Write("ik_binary", std::string("\0\1\2\3\4\5\6\7\0\0\0\0\0\0\0\0x", 17))) == K::Images);
|
|
// A PDB file without an atom is not a model
|
|
CHECK(ClassifyRugnuxInput(Write("ik_noatom.pdb", "HEADER TEST\nEND\n")) == K::Images);
|
|
// Both coordinates and reflections: the user has to say
|
|
CHECK(ClassifyRugnuxInput(Write("ik_both.cif", MODEL_CIF + "loop_\n_refln.index_h\n1\n")) == K::Ambiguous);
|
|
for (const char *f: {"ik_model.cif", "ik_sf.cif", "ik_diffrn.cif", "ik_model.pdb", "ik_model.pdb.gz", "ik_sf.cif.gz",
|
|
"ik_model_named.mtz", "ik_mtz_named.pdb", "ik_image.h5", "ik_image.cbf", "ik_image2.cbf",
|
|
"ik_image.img", "ik_binary", "ik_noatom.pdb", "ik_both.cif"})
|
|
std::filesystem::remove(f);
|
|
}
|
|
|
|
TEST_CASE("RugnuxInputKind_SplitAnyOrder", "[rugnux_inputs]") {
|
|
const std::string img = Write("ik_s_image.h5", std::string("\x89HDF\r\n\x1a\n\0\0\0\0", 12));
|
|
const std::string model = Write("ik_s_model.pdb", PDB);
|
|
const std::string sf = Write("ik_s_sf.cif", SF_CIF);
|
|
for (const auto &order: std::vector<std::vector<std::string>>{{img, model, sf}, {model, sf, img}, {sf, img, model}}) {
|
|
const RugnuxPositionalInputs s = SplitRugnuxInputs(order);
|
|
CHECK(s.images == std::vector<std::string>{img});
|
|
CHECK(s.model == model);
|
|
CHECK(s.reflections == sf);
|
|
}
|
|
// image files keep their order; a path that is no file (yet) is left to the image readers
|
|
const RugnuxPositionalInputs s = SplitRugnuxInputs({"later_master.h5", model, img});
|
|
CHECK(s.images == std::vector<std::string>{"later_master.h5", img});
|
|
|
|
const std::string model2 = Write("ik_s_model2.cif", MODEL_CIF);
|
|
CHECK_THROWS(SplitRugnuxInputs({img, model, model2}));
|
|
CHECK_THROWS(SplitRugnuxInputs({sf, img, sf}));
|
|
const std::string both = Write("ik_s_both.cif", MODEL_CIF + "loop_\n_refln.index_h\n1\n");
|
|
CHECK_THROWS(SplitRugnuxInputs({img, both}));
|
|
for (const auto &f: {img, model, sf, model2, both})
|
|
std::filesystem::remove(f);
|
|
}
|