Adds --export-unmerged <file.mtz>, in --mode mx and --mode scale alike. rugnux could only write merged reflections, which closed it off from the programs that consume unmerged observations - aimless, pointless, careless and iotbx.merging_statistics all read this file. The column layout follows what pointless itself writes (H K L M/ISYM BATCH I SIGI FRACTIONCALC XDET YDET ROT LP FLAG), with DELPHI, ZETA, BGMEAN and BGVAR added because we have them and a scale model can use them. Three details decide whether the file is usable: Lorentz-polarization is applied and recorded in LP. It is per-observation geometry spanning a factor of ~120 across a sweep and no reading program can recover it; merging with no scale model at all gives R_meas 0.372 / 0.346 / 0.324 in the low shells without it against 0.183 / 0.218 / 0.257 with it. The partiality is not divided out - it stays in FRACTIONCALC - and neither is the per-image scale, since these programs fit their own. The observations are partials and are flagged as such, packed as 256*M + ISYM with LDTYPE=1. Without the flag pointless reads every part as a whole observation and mis-assigns the symmetry; with it, a tetragonal case assembles 8.29M parts into 1.15M observations and comes back as its own space group at confidence 0.92. The scan axis is written as the negation of the stored goniometer axis, which is the Cambridge convention: against pointless's own orientation matrix that agrees to 0.6 degrees, where the axis as stored disagrees by 40. Merging the exported file with aimless reproduces rugnux's own merge to CC 0.9997 on a tetragonal case and 0.9978 on a cubic one, and the anomalous signal survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHMmeM1d489zvNFT7ZMN2P
70 lines
3.6 KiB
C++
70 lines
3.6 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 "IntegrationOutcome.h"
|
|
|
|
struct MergeStatistics;
|
|
struct TwinningAnalysisResult;
|
|
|
|
// 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). Empty strings are written as unknown.
|
|
struct ErrorModelReport {
|
|
std::string isa;
|
|
std::string isa_asymptotic;
|
|
std::string a;
|
|
std::string b;
|
|
};
|
|
|
|
// nthreads: workers for the per-reflection row formatting, which is the bulk of the file.
|
|
void WriteMmcifReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const MergeStatistics &statistics,
|
|
const ErrorModelReport &error_model,
|
|
const TwinningAnalysisResult &twinning,
|
|
const std::string &filename,
|
|
size_t nthreads);
|
|
|
|
void WriteMtzReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const std::string &filename);
|
|
|
|
// SHELX HKLF-4 text file (h k l I sigma(I), Bijvoet mates separate) for SHELXC / ANODE.
|
|
// nthreads: workers for the per-reflection row formatting, as for the mmCIF.
|
|
void WriteShelxHklReflections(const std::vector<MergedReflection> &reflections,
|
|
const DiffractionExperiment &experiment,
|
|
const std::string &filename,
|
|
size_t nthreads);
|
|
|
|
// Unmerged observations, one row per integrated reflection, 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.
|
|
// One observation per integrated box, so on a rotation run a reflection arrives as a run of partials
|
|
// over consecutive batches, for the reader to sum. The intensities carry the Lorentz-polarization
|
|
// factor and nothing else - the partiality and the per-image scale are left for the reading program
|
|
// to fit, since every program this file is for fits a scale model of its own.
|
|
void WriteUnmergedMtzReflections(const std::vector<IntegrationOutcome> &outcomes,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const std::string &filename);
|
|
|
|
void WriteReflections(const std::vector<MergedReflection> &reflections,
|
|
const UnitCell &unitCell,
|
|
const DiffractionExperiment &experiment,
|
|
const MergeStatistics &statistics,
|
|
const ErrorModelReport &error_model,
|
|
const TwinningAnalysisResult &twinning,
|
|
const std::string &filename,
|
|
size_t nthreads); |