--export-unmerged now writes one row per reflection with its partials summed, which is how a rotation dataset is normally handed to a scaling program. The previous behaviour is still available as --export-unmerged-partials. Partials made the file's usefulness depend on the reading program's partial-assembly settings. On an orthorhombic case only 31% of partial sets passed aimless's default PARTIALS test, so it merged 2% of the file: multiplicity 4.7 against our own 12.4, CC1/2 0.788, and CC against our merged intensities 0.806. Summed, aimless uses 89% of the file and rejects nothing: multiplicity 12.5, CC1/2 0.931, completeness 96.1 to 98.0, pointless Laue confidence 0.669 to 0.792, CC 0.848. The other two test crystals are ties overall and win their outer shells, and the anomalous signal comes out stronger from the fulls. iotbx.merging_statistics, which never assembled partials at all, now reports real merging statistics rather than statistics of parts. Events are grouped by Combine()'s own rule so the file and the merge assemble exactly the same reflections; the sum is plain with variances in quadrature and no partiality division, which reproduces the merge's full times FRACTIONCALC to a few percent. Per-part metadata is combined on a partiality-weighted centroid rather than an intensity-weighted one: 8-41% of events have parts summing to zero or less, where intensity weights are undefined or sign-flipped, and 10-21% of intensity-weighted centroids fall outside the event's own frame range. Events below --min-partiality are not written. Declaring a reflection whose rocking curve caught a thousandth of itself as a complete measurement gives a scaling program noise with a small sigma and it weights it heavily; there is no partial test to catch it once it is a full. This is the same constant already applied to the same quantity in the merge, and it is what takes the orthorhombic case from CC 0.219 to 0.848. --min-partiality 0 writes every event. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHMmeM1d489zvNFT7ZMN2P
75 lines
3.9 KiB
C++
75 lines
3.9 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 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 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,
|
|
bool sum_partials,
|
|
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); |