Merged mmCIF: drop the scaled unmerged block

The unmerged observations already leave a run as the unmerged MTZ (and
the scaled fulls as the .hkl), and deposition does not come straight
from rugnux, so the _diffrn_refln block made the .cif up to ten times
larger for no consumer. The .cif is one data block again; the keys,
software rows, metadata, R_merge/R_pim/CC* and provenance stay.
ScaledFull loses the scale and frame it carried only for that block.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
2026-10-09 18:51:56 +02:00
co-authored by Claude Opus 5.5
parent 5584460de0
commit 4120b0daa8
6 changed files with 21 additions and 115 deletions
+3 -64
View File
@@ -140,7 +140,7 @@ void AddProvenanceHistory(gemmi::Mtz &mtz, const OutputProvenance &provenance) {
add("Collected: ", provenance.collection_date);
}
// _entry, _cell and _symmetry: what every data block of the file restates, so each one stands alone.
// _entry, _cell and _symmetry.
void WriteBlockHeader(std::ostream &out, const std::string &entry_id, const UnitCell &unitCell,
const gemmi::SpaceGroup &sg) {
out << "_entry.id " << entry_id << "\n";
@@ -253,7 +253,6 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
const MergeStatistics &statistics,
const ErrorModelReport &error_model,
const TwinningAnalysisResult &twinning,
const std::vector<ScaledFull> &scaled_fulls,
const OutputProvenance &provenance,
const std::string &filename,
size_t nthreads) {
@@ -266,7 +265,7 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
// The keys PDBx asks for are written with the one value this file has for each: one entry, one
// crystal, one diffraction experiment (diffrn_id 1, which _pdbx_reflns_twin below also points at)
// and one wavelength. The merged data come first, as the wwPDB expects of a structure-factor file.
// and one wavelength.
const std::string entry_id = "sample";
const gemmi::SpaceGroup &sg = experiment.GetSpaceGroupOrP1();
@@ -603,66 +602,6 @@ void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
out << "#\n";
out << "# End of reflections\n";
// ---------- the scaled unmerged observations, as a second data block ----------
// The observations the merge above averaged - each full with the scale and the error-model sigma
// the merge gave it, at the index it was measured at - which is what the wwPDB accepts as scaled
// unmerged data. The same records as the .hkl: an observation without a usable sigma is in
// neither. pdbx_image_id is the frame the full peaks on, numbered from 1 as the unmerged MTZ's
// BATCH is. (The unmerged MTZ itself stays unscaled, for the programs that fit their own scales.)
if (!scaled_fulls.empty()) {
out << "data_sample_unmerged\n";
out << "#\n";
WriteBlockHeader(out, entry_id, unitCell, sg);
out << "_exptl_crystal.id 1\n";
out << "#\n";
out << "_diffrn.id 1\n";
out << "_diffrn.crystal_id 1\n";
out << "#\n";
out << "_diffrn_radiation_wavelength.id 1\n";
out << "_diffrn_radiation_wavelength.wavelength " << Fmt(experiment.GetWavelength_A(), 5) << "\n";
out << "#\n";
out << "loop_\n";
out << "_diffrn_refln.diffrn_id\n";
out << "_diffrn_refln.id\n";
out << "_diffrn_refln.pdbx_image_id\n";
out << "_diffrn_refln.index_h\n";
out << "_diffrn_refln.index_k\n";
out << "_diffrn_refln.index_l\n";
out << "_diffrn_refln.intensity_net\n";
out << "_diffrn_refln.intensity_sigma\n";
out << "_diffrn_refln.pdbx_scale_value\n";
// Numbered in order over the usable fulls, so the id of each is known before the rows are
// formatted on several workers.
std::vector<int64_t> id(scaled_fulls.size() + 1, 0);
for (size_t i = 0; i < scaled_fulls.size(); ++i) {
const auto &f = scaled_fulls[i];
id[i + 1] = id[i] + ((std::isfinite(f.I) && std::isfinite(f.sigma) && f.sigma > 0.0f) ? 1 : 0);
}
const size_t nrow = scaled_fulls.size();
const size_t nw = std::max<size_t>(nthreads, 1);
const int nch = static_cast<int>(ThreadsForWork(nrow, nw, 4096));
std::vector<std::string> block(nch);
ParallelChunks(nch, nw, [&](int tlo, int thi) {
for (int t = tlo; t < thi; ++t) {
const size_t lo = nrow * t / nch, hi = nrow * (t + 1) / nch;
std::string &s = block[t];
s.reserve((hi - lo) * 64);
for (size_t i = lo; i < hi; ++i) {
if (id[i + 1] == id[i])
continue;
const auto &f = scaled_fulls[i];
const long frame = std::isfinite(f.image_number) ? 1 + std::lround(f.image_number) : 0;
s += "1 " + std::to_string(id[i + 1]) + " " + (frame > 0 ? std::to_string(frame) : "?") + " "
+ std::to_string(f.h) + " " + std::to_string(f.k) + " " + std::to_string(f.l) + " "
+ Fmt(f.I, 3) + " " + Fmt(f.sigma, 3) + " " + Fmt(f.scale, 5) + "\n";
}
}
});
for (const std::string &s : block)
out.write(s.data(), static_cast<std::streamsize>(s.size()));
out << "#\n";
}
out.close();
}
@@ -1296,7 +1235,7 @@ void WriteReflections(const std::vector<MergedReflection> &reflections,
// fulls where the merge provides them (rotation), the merged reflections otherwise (stills).
WriteMtzReflections(reflections, unitCell, experiment, provenance, filename + ".mtz");
WriteMmcifReflections(reflections, unitCell, experiment, statistics, error_model, twinning,
scaled_fulls ? *scaled_fulls : std::vector<ScaledFull>{}, provenance,
provenance,
filename + ".cif", nthreads);
if (scaled_fulls && !scaled_fulls->empty())
WriteShelxHklReflections(*scaled_fulls, filename + ".hkl", nthreads);