Files
Jungfraujoch/image_analysis/WriteReflections.h
T
leonarski_fandClaude Opus 5 5248b1bc00 rugnux: export the integrated observations as an unmerged MTZ
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
2026-08-25 15:21:57 +02:00

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);