Build Packages / Create release (push) Successful in 26s
Build Packages / Build documentation (push) Successful in 1m43s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m7s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 11m48s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 7m22s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 12m24s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 25m29s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 29m41s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 21m44s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 24m41s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 26m9s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 26m3s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 28m28s
Build Packages / Generate python client (push) Successful in 27s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 31m16s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m44s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 26m27s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 22m52s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 27m44s
Build Packages / Unit tests (push) Successful in 1h46m55s
Build Packages / Validation Battery (push) Successful in 52m50s
* Rugnux: Several rotation sweeps of one crystal are merged as one dataset (`rugnux a_master.h5 b_master.h5`), on a common indexing and cell, with a non-isomorphous sweep left out and reported; `--sweeps separate` processes each sweep on its own. Bruker `.sfrm` frames and imgCIF files with a full goniometer axis chain are read. * Rugnux: More robust rotation processing - a retry with a more sensitive spot finder when no lattice is found, a self-consistent detector distance, long axes and doubled cells tested as hypotheses, a `-C` supercell multiple stepped down, and each ice ring merged, down-weighted or rejected on its own. * Rugnux: Serial stills are merged with a partiality-aware error model, reported as `ERROR_MODEL_A/B/C` with no ISa or R_merge/R_meas/R_pim; runs without `-S` have an overall intensity scale about 0.86-0.88 times that of earlier versions, with unchanged statistics. A second crystal form is left out of the merge and reported, the indexing ambiguity is resolved more reliably, and `--model` no longer influences processing. * Rugnux: The reported CC1/2 is the standard unweighted one, which can be lower than before on sweeps with weak frames; ISa comes with a 95% interval (`ISA_LOW`/`ISA_HIGH`). * Rugnux: Model refinement adds anisotropic/TLS, local NCS and twin refinement where R-free supports them; building waters and relabelling ions is now off by default (`--refine-solvent` turns it on). * NXmx: A detector of several modules is written as `module_0` ... `module_N` only, so DIALS reads each module as its own panel. * Other: JPEG previews use a bundled encoder (libjpeg-turbo no longer needed), Rugnux prints how to cite it, and the release archives contain the HTML documentation. Reviewed-on: #92 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
157 lines
9.3 KiB
C++
157 lines
9.3 KiB
C++
// SPDX-FileCopyrightText: 2025 Paul Scherrer Institute
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../common/Reflection.h"
|
|
#include "../common/UnitCell.h"
|
|
#include "../common/DiffractionExperiment.h"
|
|
#include "../common/MtzTable.h"
|
|
#include "IntegrationOutcome.h"
|
|
#include "scale_merge/Merge.h"
|
|
#include "../common/StillsRunWidth.h"
|
|
|
|
struct MergeStatistics;
|
|
struct TwinningAnalysisResult;
|
|
struct CellEsd; // geom_refinement/CellEsd.h
|
|
|
|
// The error model as it is reported, already formatted. `isa` is the whole-range 1/sqrt(a*b), the
|
|
// same quantity XDS's ISa denotes, so a file written here is directly comparable with a CORRECT.LP;
|
|
// `isa_asymptotic` is the strong-reflection tier, which only the rotation path has. `a` and `b` are
|
|
// in XDS's convention, sigma^2 = a*(sigma0^2 + b*I^2), and `c` is the stills partiality term in the same
|
|
// convention, sigma^2 = a*(sigma0^2 + b*I^2 + c*((1-p)/p)^2*I^2). Empty strings are written as unknown.
|
|
// A stills merge writes no ISa (MergeStatistics::stills).
|
|
struct ErrorModelReport {
|
|
std::string isa;
|
|
std::string isa_asymptotic;
|
|
std::string a;
|
|
std::string b;
|
|
std::string c;
|
|
};
|
|
|
|
// What the reflection files record about where they came from, beyond the experiment itself and the
|
|
// Rugnux version and commit: the MTZ HISTORY records and the mmCIF's _software and
|
|
// _jfjoch_provenance. Empty strings are left out.
|
|
struct OutputProvenance {
|
|
std::string command_line;
|
|
std::string input_file; // the input as found on disk, absolute
|
|
std::string input_identity; // a cheap identifier of it: size and modification time
|
|
std::string collection_date; // as the input states it (NXmx /entry/start_time), ISO-8601
|
|
};
|
|
|
|
// An e.s.d. below this is a parameter the symmetry fixes: written "." in the mmCIF.
|
|
constexpr double CELL_ESD_FIXED = 1e-9;
|
|
|
|
// The merged reflections, their statistics and the experiment, as one PDBx/mmCIF data block.
|
|
// nthreads: workers for the per-reflection row formatting, which is the bulk of the file.
|
|
// cell_esd: the cell's e.s.d.s (_cell.*_esd, _cell_measurement), where there are any.
|
|
void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const MergeStatistics &statistics,
|
|
const ErrorModelReport &error_model,
|
|
const TwinningAnalysisResult &twinning,
|
|
const OutputProvenance &provenance,
|
|
const std::string &filename,
|
|
size_t nthreads,
|
|
const CellEsd *cell_esd = nullptr);
|
|
|
|
// The merged MTZ: H K L IMEAN SIGIMEAN [I(+) SIGI(+) I(-) SIGI(-)] F SIGF [F(+) SIGF(+) F(-) SIGF(-)]
|
|
// FreeR_flag, one row per unique reflection in the CCP4 anomalous layout, the bracketed columns only where
|
|
// the merge kept a Bijvoet split; with the HISTORY records saying where it came from. WriteMtzReflections
|
|
// writes it (WriteMtz, writer/WriteRugnuxFiles.h).
|
|
MtzTable MergedMtzTable(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const OutputProvenance &provenance);
|
|
|
|
void WriteMtzReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const OutputProvenance &provenance,
|
|
const std::string &filename);
|
|
|
|
// SHELX HKLF-4 text file, FORMAT(3I4,2F8.2) h k l I sigma(I), scaled so the largest value fits the
|
|
// field, ended by a 0 0 0 record. nthreads: workers for the row formatting, as for the mmCIF.
|
|
// Merged reflections, Bijvoet mates as separate records (the stills output).
|
|
void WriteShelxHklReflections(const std::vector<MergedReflection> &reflections,
|
|
const DiffractionExperiment &experiment,
|
|
const std::string &filename,
|
|
size_t nthreads);
|
|
// Unmerged scaled fulls, one record each at its measured index (the rotation output).
|
|
void WriteShelxHklReflections(const std::vector<ScaledFull> &fulls,
|
|
const std::string &filename,
|
|
size_t nthreads);
|
|
|
|
// The SHELX instruction file to go with the .hkl: TITL, CELL (at the wavelength), ZERR where the
|
|
// e.s.d.s are known, LATT, SYMM, a placeholder SFAC/UNIT, HKLF 4, END - what SHELXT reads, and what
|
|
// SHELXL's CIF takes the cell and its e.s.d.s from.
|
|
void WriteShelxIns(const UnitCell &cell, const CellEsd *esd, const DiffractionExperiment &experiment,
|
|
const std::string &title, const std::string &filename);
|
|
|
|
// Unmerged observations in the column and batch-header layout POINTLESS writes: aimless, pointless,
|
|
// careless and iotbx.merging_statistics all read that layout. H K L are the ASU indices and M/ISYM
|
|
// recovers the index the reflection was measured at (which is what careless needs to see the crystal
|
|
// frame) and says whether the observation is a partial.
|
|
// sum_partials: add the partials of each rocking event into one full, written at the batch of the
|
|
// event's centroid with the summed rocking-curve fraction in FRACTIONCALC - the plain sum every
|
|
// rotation program writes, over the events the combine would assemble (--min-partiality). False
|
|
// writes one row per integrated box instead, flagged as partials for the reading program to sum.
|
|
// Stills have no rocking events and are unaffected either way.
|
|
// The intensities carry the deterministic per-reflection corrections and nothing else - Lorentz
|
|
// and polarization in LP, the sensor's angle-dependent efficiency in QE, and the attenuation of the
|
|
// flight-path medium in FLIGHT - so raw counts are I / LP * QE * FLIGHT. The partiality and the
|
|
// per-image scale are left for the reading program to fit.
|
|
// Given the merge's scaled fulls (rotation, summed partials), the file holds the merge's observations
|
|
// instead, as a scaling program's unmerged output does: each summed event's I and SIGI are those of the
|
|
// full the merge kept for it - scaled, and with the sigma the merge weighted it by - and an event the
|
|
// merge rejected or cut is left out.
|
|
void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
bool sum_partials,
|
|
const OutputProvenance &provenance,
|
|
const std::string &filename,
|
|
size_t nthreads = 1,
|
|
const std::vector<ScaledFull> *scaled_fulls = nullptr);
|
|
|
|
// The same file as its table, in two steps, for a caller that builds it while the outcomes' per-image
|
|
// scale fields are still being written: UnmergedMtzTable reads everything but mosaicity_deg, which
|
|
// SetUnmergedMtzMosaicity then copies into the batch headers.
|
|
// scaled_fulls: the merge's, on the indices the outcomes carry (see above); null or empty writes the
|
|
// unscaled events.
|
|
MtzTable UnmergedMtzTable(const std::vector<IntegrationOutcome> &outcomes,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
bool sum_partials,
|
|
const OutputProvenance &provenance,
|
|
size_t nthreads = 1,
|
|
const std::vector<ScaledFull> *scaled_fulls = nullptr);
|
|
void SetUnmergedMtzMosaicity(MtzTable &mtz, const std::vector<IntegrationOutcome> &outcomes);
|
|
|
|
// Diagnostic (--export-observations, stills): every integrated observation of the merged run with what the
|
|
// merge made of it - one row per reflection of every outcome, the ones the merge left out included, with a
|
|
// FLAGS bit for each test it failed (ObservationFlag; BEYOND_DMIN 4096 = past the written resolution cut).
|
|
// verdicts: MergeOnTheFly::Verdicts of the written merge, in the outcomes' order. Columns are listed in the
|
|
// code and documented with the campaign's loader, not here; H K L are the indices as measured.
|
|
MtzTable ObservationMtzTable(const std::vector<IntegrationOutcome> &outcomes,
|
|
const std::vector<std::vector<ObservationVerdict>> &verdicts,
|
|
std::optional<double> written_d_min,
|
|
const std::optional<StillsRunWidth> &run_width,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment);
|
|
|
|
void WriteReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const MergeStatistics &statistics,
|
|
const ErrorModelReport &error_model,
|
|
const TwinningAnalysisResult &twinning,
|
|
const OutputProvenance &provenance,
|
|
const std::string &filename,
|
|
size_t nthreads,
|
|
const std::vector<ScaledFull> *scaled_fulls = nullptr);
|