Files
Jungfraujoch/writer/RugnuxModelFile.cpp
T
leonarski_f a218c0bec8 rugnux --mode refine: refine a model against merged structure factors, nothing processed
rugnux --mode refine --model model.cif -o out data.mtz

The input is an MTZ or a structure-factor mmCIF, gzipped or not, by content
(writer/RugnuxReflectionFile: parsing in the file layer, the library reads no file). Columns are
found, not named: F/SIGF-like amplitudes (calculated amplitudes and map coefficients passed over),
else intensities made into amplitudes by French-Wilson, else the mean of the anomalous pairs; the
free set from a FreeR_flag-like column (0 the test set, or the minority value), else rugnux's own
5 % set. RunRefineOnly (rugnux/RugnuxRefine) runs RefineModel; the outputs are --refine's
(_refined_model.cif, _refined_maps.mtz, _blobs.json) and a report with the REFINE_ keys, the input's
REFINE_FRENCH_WILSON / REFINE_FREE_FLAGS, and every term of the start score under REFINE_SCORE_
(also in --refine's section). --refine-score-only scores the model as given; --refine-d-min cuts
the resolution.

tools/battery/refine_parity.py runs REFMAC 5.8 at 0 cycles and rugnux --refine-score-only on the
same files and prints the terms side by side, with R by resolution from both programs' model
amplitudes on the same reflections.

Tests: [refine_only] - column choice (observed over map coefficients, intensities, 1-marks-free
flags, no flags, no usable column), an MTZ read from its bytes, and the mode end to end on a
synthetic cell (French-Wilson, generated flags, score-only, the report keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 63ee830c39c988588cffb8c9d4638a678815ec2c)
(cherry picked from commit d7397311e6ce0d3bb37ac91e8973b6052e38589f)
2026-10-26 01:38:24 +01:00

104 lines
3.9 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "RugnuxModelFile.h"
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <zlib.h>
#include <nlohmann/json.hpp>
#include <gemmi/mmread.hpp>
#include <gemmi/calculate.hpp> // count_atom_sites
namespace {
// The bytes of a gzip stream inflated, every member of it.
std::string Gunzip(const std::string &bytes) {
z_stream s{};
if (inflateInit2(&s, 15 + 32) != Z_OK)
throw std::runtime_error("cannot start zlib");
std::string out;
char buf[1 << 16];
s.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(bytes.data()));
s.avail_in = static_cast<uInt>(bytes.size());
int ret = Z_OK;
while (ret != Z_STREAM_END || s.avail_in > 0) {
if (ret == Z_STREAM_END)
inflateReset(&s);
s.next_out = reinterpret_cast<Bytef *>(buf);
s.avail_out = sizeof buf;
ret = inflate(&s, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END) {
inflateEnd(&s);
throw std::runtime_error("the gzip stream is damaged");
}
out.append(buf, sizeof buf - s.avail_out);
if (ret == Z_OK && s.avail_in == 0 && s.avail_out != 0) {
inflateEnd(&s);
throw std::runtime_error("the gzip stream is cut short");
}
}
inflateEnd(&s);
return out;
}
std::string ReadFile(const std::string &path) {
std::ifstream f(path, std::ios::binary);
if (!f)
throw std::runtime_error("cannot open " + path);
return {std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()};
}
} // namespace
std::string GunzipIfCompressed(const std::string &bytes) {
return bytes.size() >= 2 && bytes[0] == '\x1f' && bytes[1] == '\x8b' ? Gunzip(bytes) : bytes;
}
RugnuxModel ParseRugnuxModel(const std::string &bytes, const std::string &name, const std::string &pae_json) {
RugnuxModel m;
m.name = name;
std::string text = GunzipIfCompressed(bytes);
gemmi::cif::Document doc;
m.structure = gemmi::read_structure_from_memory(text.data(), text.size(), name, gemmi::CoorFormat::Detect, &doc);
if (gemmi::count_atom_sites(m.structure) == 0)
throw std::runtime_error(name + " holds no atoms");
m.modelcif = m.structure.input_format == gemmi::CoorFormat::Mmcif && !doc.blocks.empty()
&& doc.blocks[0].has_mmcif_category("_ma_qa_metric");
if (!pae_json.empty()) {
try {
const nlohmann::json j = nlohmann::json::parse(pae_json);
const nlohmann::json &pae = j.is_array() ? j.at(0).at("predicted_aligned_error")
: j.at("predicted_aligned_error");
std::vector<std::vector<double>> rows;
for (const auto &row : pae)
rows.push_back(row.get<std::vector<double>>());
m.pae = std::move(rows);
} catch (const std::exception &) {
}
}
return m;
}
RugnuxModel ReadRugnuxModelFile(const std::string &path) {
const std::string pae_path = PaePathFor(path);
std::string pae_json;
if (!pae_path.empty() && std::ifstream(pae_path))
pae_json = ReadFile(pae_path);
return ParseRugnuxModel(ReadFile(path), path, pae_json);
}
std::string PaePathFor(const std::string &model_path) {
const size_t slash = model_path.find_last_of("/\\");
const std::string dir = slash == std::string::npos ? std::string() : model_path.substr(0, slash + 1);
const std::string name = model_path.substr(dir.size());
const size_t at = name.rfind("-model_v");
if (at == std::string::npos)
return {};
const size_t dot = name.find('.', at);
const std::string version = name.substr(at + 8, dot == std::string::npos ? std::string::npos : dot - at - 8);
return dir + name.substr(0, at) + "-predicted_aligned_error_v" + version + ".json";
}