Files
Jungfraujoch/writer/RugnuxResultBlocks.cpp
T
leonarski_fandClaude Opus 5.5 aca27be862 jfjoch_writer writes rugnux's .mtz, _maps.mtz and _model.cif from the END blocks
New gemmi-only library JFJochRugnuxFiles (writer/), linked by JFJochWriter, so
the writer needs no analysis library and builds with JFJOCH_WRITER_ONLY:
- WriteMergedMtz / WriteMapCoefficientsMtz / PlacedModelStructure make the
  files from the structured blocks; WriteRugnuxResultFiles writes whichever
  blocks END carries beside the master, each under a temporary name and renamed
  into place, an existing file replaced only when the run's overwrite is set.
  A file that fails costs only itself (logged), not the run.
- FillRugnuxResultBlocks reads the blocks back from the files rugnux wrote at
  a prefix (p.mtz, p_maps.mtz, p_model.cif), for the broker to fill END.

WriteMtzReflections now goes through WriteMergedMtz (MergedReflectionsForWriter
builds the block); its output is byte-identical to before, checked by md5 on a
synthetic set in both the Friedel-merged and the anomalous layout.

StreamWriter writes the files after the master and before the END ACK, so the
ACK means they are in place; END receipt and each rename are logged at INFO
with the wall-clock time, for last-frame-to-files latency. The broker's direct
HDF5 pusher writes them too.

Test: rugnux's three files -> blocks -> CBOR -> writer reproduces all of them
(and _model.pdb) byte for byte; a ~25k-reflection END is 2.7 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:19:51 +02:00

141 lines
5.4 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "RugnuxResultBlocks.h"
#include <filesystem>
#include <gemmi/mtz.hpp>
#include <gemmi/mmread_gz.hpp>
namespace {
UnitCell ToUnitCell(const gemmi::UnitCell &c) {
return UnitCell{static_cast<float>(c.a), static_cast<float>(c.b), static_cast<float>(c.c),
static_cast<float>(c.alpha), static_cast<float>(c.beta), static_cast<float>(c.gamma)};
}
// The column with this label, empty where the file has none.
std::vector<float> Column(const gemmi::Mtz &mtz, const std::string &label) {
std::vector<float> v;
if (const gemmi::Mtz::Column *col = mtz.column_with_label(label))
for (int i = 0; i < col->size(); i++)
v.push_back((*col)[i]);
return v;
}
std::vector<int32_t> IntColumn(const gemmi::Mtz &mtz, const std::string &label) {
std::vector<int32_t> v;
for (float x : Column(mtz, label))
v.push_back(static_cast<int32_t>(x));
return v;
}
std::vector<uint8_t> FlagColumn(const gemmi::Mtz &mtz, const std::string &label) {
std::vector<uint8_t> v;
for (float x : Column(mtz, label))
v.push_back(static_cast<uint8_t>(x));
return v;
}
std::string SpaceGroupName(const gemmi::Mtz &mtz) {
return mtz.spacegroup != nullptr ? mtz.spacegroup->xhm() : std::string();
}
} // namespace
MergedReflectionsMessage MergedReflectionsFromMtz(const std::string &path) {
const gemmi::Mtz mtz = gemmi::read_mtz_file(path);
MergedReflectionsMessage m;
m.unit_cell = ToUnitCell(mtz.cell);
m.space_group_name = SpaceGroupName(mtz);
m.wavelength_A = static_cast<float>(mtz.datasets.back().wavelength);
m.crystal_name = mtz.datasets.back().crystal_name;
m.history = mtz.history;
m.h = IntColumn(mtz, "H");
m.k = IntColumn(mtz, "K");
m.l = IntColumn(mtz, "L");
m.i_mean = Column(mtz, "IMEAN");
m.sigma_i_mean = Column(mtz, "SIGIMEAN");
m.i_plus = Column(mtz, "I(+)");
m.sigma_i_plus = Column(mtz, "SIGI(+)");
m.i_minus = Column(mtz, "I(-)");
m.sigma_i_minus = Column(mtz, "SIGI(-)");
m.f = Column(mtz, "F");
m.sigma_f = Column(mtz, "SIGF");
m.f_plus = Column(mtz, "F(+)");
m.sigma_f_plus = Column(mtz, "SIGF(+)");
m.f_minus = Column(mtz, "F(-)");
m.sigma_f_minus = Column(mtz, "SIGF(-)");
m.free_flag = FlagColumn(mtz, "FreeR_flag");
return m;
}
MapCoefficientsMessage MapCoefficientsFromMtz(const std::string &path) {
const gemmi::Mtz mtz = gemmi::read_mtz_file(path);
MapCoefficientsMessage c;
c.unit_cell = ToUnitCell(mtz.cell);
c.space_group_name = SpaceGroupName(mtz);
c.wavelength_A = static_cast<float>(mtz.datasets.back().wavelength);
c.h = IntColumn(mtz, "H");
c.k = IntColumn(mtz, "K");
c.l = IntColumn(mtz, "L");
c.fp = Column(mtz, "FP");
c.fc = Column(mtz, "FC");
c.phic = Column(mtz, "PHIC");
c.fwt = Column(mtz, "FWT");
c.phwt = Column(mtz, "PHWT");
c.delfwt = Column(mtz, "DELFWT");
c.phdelwt = Column(mtz, "PHDELWT");
c.fom = Column(mtz, "FOM");
c.free_flag = FlagColumn(mtz, "FREE");
return c;
}
PlacedModelMessage PlacedModelFromStructure(const gemmi::Structure &st, const std::string &data_space_group_name) {
PlacedModelMessage p;
p.name = st.name;
p.unit_cell = ToUnitCell(st.cell);
p.space_group_name = data_space_group_name;
p.model_space_group_name = st.spacegroup_hm;
bool has_aniso = false;
for (const gemmi::Model &m : st.models)
for (const gemmi::Chain &ch : m.chains)
for (const gemmi::Residue &r : ch.residues)
for (const gemmi::Atom &a : r.atoms) {
p.model_number.push_back(m.num);
p.chain.push_back(ch.name);
p.residue_name.push_back(r.name);
p.seq_num.push_back(*r.seqid.num);
p.icode.push_back(static_cast<uint8_t>(r.seqid.icode));
p.het_flag.push_back(static_cast<uint8_t>(r.het_flag));
p.atom_name.push_back(a.name);
p.element.push_back(a.element.name());
p.altloc.push_back(static_cast<uint8_t>(a.altloc));
p.charge.push_back(a.charge);
p.x.push_back(a.pos.x);
p.y.push_back(a.pos.y);
p.z.push_back(a.pos.z);
p.b_iso.push_back(a.b_iso);
p.occupancy.push_back(a.occ);
p.aniso.insert(p.aniso.end(), {a.aniso.u11, a.aniso.u22, a.aniso.u33,
a.aniso.u12, a.aniso.u13, a.aniso.u23});
has_aniso = has_aniso || a.aniso.nonzero();
}
if (!has_aniso)
p.aniso.clear();
return p;
}
void FillRugnuxResultBlocks(const std::string &prefix, EndMessage &end) {
if (std::filesystem::exists(prefix + ".mtz"))
end.merged = MergedReflectionsFromMtz(prefix + ".mtz");
if (std::filesystem::exists(prefix + "_maps.mtz"))
end.map_coefficients = MapCoefficientsFromMtz(prefix + "_maps.mtz");
if (std::filesystem::exists(prefix + "_model.cif")) {
const gemmi::Structure st = gemmi::read_structure_gz(prefix + "_model.cif");
end.placed_model = PlacedModelFromStructure(st, end.merged ? end.merged->space_group_name
: st.spacegroup_hm);
}
}