Build Packages / build:viewer-tgz:cpu (push) Successful in 8m30s
Build Packages / build:viewer-tgz:cuda (push) Successful in 9m3s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m52s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m23s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m59s
Build Packages / build:rpm (rocky8) (push) Successful in 11m45s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m59s
Build Packages / XDS test (durin plugin) (push) Successful in 7m14s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / Build documentation (push) Successful in 1m7s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (rocky9) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m3s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m38s
Build Packages / DIALS test (push) Successful in 14m4s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m4s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m49s
Build Packages / Unit tests (push) Successful in 1h1m28s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
zeta is the sine of the angle between a reflection's rocking path and the spindle. Near 0 the reflection crosses the Ewald sphere almost tangentially, spends many frames in diffracting position and is measured worst. The de-novo space-group search asks how EQUAL an operator's paired intensities are, so its answer is dominated by whichever reflections are measured worst - and when the spindle lies in a lattice plane, an operator that permutes the two in-plane axes samples a different mixture of measurement qualities than one that only flips signs. That is not a fair comparison, and it can make a real symmetry operator look like a twin law. Measured on a thaumatin set mounted that way (its 4-fold is 88.9 deg from the spindle), the added operators' disagreement is 1.74x the parent's over pairs where both reflections have zeta < 0.85 and 1.003x - i.e. the symmetry is exact - over pairs where both are above it. The search consequently refuses the 422 promotion and merges the crystal in P222, while the same data forced to the right group give CC1/2 99.2% at multiplicity 10.7, matching XDS. With the option the de-novo pass ignores those observations (the final merge keeps everything - there completeness is the point): zeta cut observations ignored H ratio adopted 0 (off) - 1.47 P222 0.5 1620648 1.44 P222 0.7 3006013 1.34 P21212 0.85 4536724 promoted P4212 (correct point group) OFF BY DEFAULT, and it must stay off, because the same cut costs four other crystals their space group (P41212 -> P212121, I23 -> P2, I23 -> I222 twice): at 0.85 it discards 40-80% of all observations, which on a crystal whose geometry is not the problem simply starves the search. Two independent implementations - filtering the pairs that enter the statistic, and filtering the observations that enter the merge - trade exactly the same crystals, so this is a property of the cut and not of where it is applied. Verified bit-identical to the previous binary when off. The companion diagnostic is already there: the run now reports how close a symmetry axis lies to the spindle, which is the geometry that makes this option worth reaching for. Implementation note for anyone tempted by the cheaper route: excluding these observations from the ASU grouping alone does NOT work. The 3D combine selects partials on corr, not on their group, so their intensity still reaches the fulls and the merged intensities are unchanged - measured, the statistic did not move by 0.03 while 67% of observations were nominally excluded. Zeroing corr is what removes an observation from the combine, the merge and the error model alike. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
152 lines
8.5 KiB
C++
152 lines
8.5 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <optional>
|
|
#include "JFJochException.h"
|
|
|
|
// How the high-resolution cutoff for the written reflections and the reported shell table is chosen
|
|
// when no explicit --scaling-high-resolution is given. Off = keep the full (detector-edge) range;
|
|
// CCHalfLogistic = fit the CC1/2 fall-off and cut one shell past cc_target (DIALS-style, generous).
|
|
enum class ResolutionCutoffMethod { Off, CCHalfLogistic };
|
|
|
|
class ScalingSettings {
|
|
bool refine_wedge = false;
|
|
|
|
bool merge_friedel = true;
|
|
std::optional<double> high_resolution_limit_A;
|
|
std::optional<double> wedge_for_scaling;
|
|
std::optional<double> forced_mosaicity; // diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed
|
|
double min_partiality = 0.02;
|
|
// Capture-aware systematic uncertainty for the rot3d combine: a full reconstructed from only
|
|
// a fraction f<1 of its rocking curve is extrapolated, and the unobserved (1-f) carries a
|
|
// systematic error ~coeff*(1-f)*I that plain counting sigma misses. 0 = off (baseline).
|
|
double capture_uncertainty_coeff = 0.0;
|
|
// Full-level captured-fraction floor for the rot3d combine: drop a reconstructed full whose rocking
|
|
// curve was only fractionally captured (sum of its partials' partiality < this). Distinct from
|
|
// min_partiality, which gates individual partials; this gates the assembled full. 0 = off (baseline).
|
|
double min_captured_fraction = 0.0;
|
|
double min_cc_for_image = 0.0;
|
|
|
|
// Exclude observations whose Lorentz geometry |zeta| falls below this from the DE-NOVO space-group
|
|
// search merge only (see RotationScaleMerge::search_min_zeta). 0 = off.
|
|
double search_min_zeta = 0.0;
|
|
double outlier_reject_nsigma = 0.0; // per-observation merge outlier rejection (XDS/DIALS-style); 0 = off, e.g. 6 enables
|
|
|
|
// Scale fulls: after the rotation 3D combine, refit a per-frame scale on the combined fulls (XDS
|
|
// order). Only used by the rotation path (RotationScaleMerge).
|
|
bool scale_fulls = false;
|
|
|
|
// Correction surfaces fitted on the rot3d fulls after scale-fulls: a decay (per-run Debye-Waller B)
|
|
// and an absorption surface (over the diffracted-beam direction in the goniometer frame). Both are
|
|
// cross-validated, so they no-op when their systematic is absent - hence ON by default (they only ever
|
|
// help or do nothing). No-op without rot3d. Set false to disable both.
|
|
bool correction_surfaces = true;
|
|
// Absorption-surface refinement iteration count (used when correction_surfaces is on).
|
|
int absorption_iter = 3;
|
|
|
|
// Physical partiality post-refinement for the STILLS merge (StillsPartialityRefine): refine a per-crystal
|
|
// orientation tilt against the running merge, recompute each reflection's partiality from the refined
|
|
// geometry (angular Ewald-proximity model), and re-scale/merge - the "full model" for stills. ON by
|
|
// default (helps mono stills, neutral on pink beam, tames weak data via a soft prior). rugnux
|
|
// --simple-stills turns it OFF, reverting to treating every reflection as a full (p = 1, single pass).
|
|
bool stills_partiality_refine = true;
|
|
|
|
// Expected-variance merge weighting for the STILLS merge (MergeOnTheFly). When combining a reflection's
|
|
// redundant observations by inverse variance, rebuild the Poisson signal part of each observation's
|
|
// variance at the reflection's EXPECTED <I> instead of the observation's own intensity. Weighting by an
|
|
// observation's own sigma^2 biases the inverse-variance mean low at <1 photon (an up-fluctuated
|
|
// observation gets a larger sigma and is over-downweighted). Default on - it mirrors the rotation combine
|
|
// (RotationScaleMerge::process_rawrun), which already does this, and is R-free-neutral on strong data and
|
|
// better on weak. --no-expected-variance-merge restores the old observed-sigma weighting.
|
|
bool expected_variance_merge = true;
|
|
|
|
// Smooth the per-frame scale G across frames (centered moving average of log G) before the rot3d
|
|
// combine, so a rocking event's partials share a consistent scale. Given as a ROTATION RANGE in
|
|
// degrees (like XDS DELPHI), converted to an odd frame window from the oscillation step; this keeps
|
|
// the smoothing physical (independent of frame slicing). 0 = off. A no-op without rot3d.
|
|
double smooth_g_deg = 0.0;
|
|
|
|
// Per-batch relative-B on the rot3d fulls (beyond the single global decay slope): bin frames into
|
|
// rotation-range batches of this width in degrees and refine one relative Debye-Waller B per batch, so
|
|
// NON-monotonic changes in scattering power across the run (absorption path, crystal slippage, dose
|
|
// bursts) are corrected the resolution-flat per-frame G and the single decay slope both miss. Cross-
|
|
// validated + zero-mean-anchored, so a no-op when absent. 0 = off. A no-op without rot3d.
|
|
double relative_b_deg = 0.0;
|
|
|
|
double rfree_fraction = 0.05;
|
|
|
|
// Automatic high-resolution cutoff for the written reflections + reported shells (not the scaling
|
|
// or the error model, and not the per-image _process.h5). Applied only when no explicit
|
|
// high_resolution_limit_A is set - that manual limit always wins and disables the auto-cut.
|
|
ResolutionCutoffMethod resolution_cutoff = ResolutionCutoffMethod::CCHalfLogistic;
|
|
double resolution_cc_target = 0.30; // CC1/2 value defining the fall-off limit before the +1 shell
|
|
int report_shell_count = 10; // number of resolution shells in the reported statistics table
|
|
|
|
bool scaling_regularize = false;
|
|
public:
|
|
ScalingSettings& RefineRotationWedge(bool input);
|
|
ScalingSettings& RotationWedgeForScaling(std::optional<double> input);
|
|
ScalingSettings& MergeFriedel(bool input);
|
|
ScalingSettings& HighResolutionLimit_A(double limit);
|
|
ScalingSettings& HighResolutionLimit_A(std::optional<double> limit); // nullopt clears the limit
|
|
ScalingSettings& MinPartiality(double min_partiality);
|
|
ScalingSettings& ForcedMosaicity(std::optional<double> input);
|
|
ScalingSettings& CaptureUncertaintyCoeff(double input);
|
|
ScalingSettings& MinCapturedFraction(double input);
|
|
ScalingSettings& MinCCForImage(double min_cc_for_image);
|
|
ScalingSettings& SearchMinZeta(double search_min_zeta);
|
|
ScalingSettings& OutlierRejectNsigma(double input);
|
|
ScalingSettings& ScaleFulls(bool input);
|
|
ScalingSettings& AbsorptionIter(int input);
|
|
ScalingSettings& CorrectionSurfaces(bool input);
|
|
ScalingSettings& StillsPartialityRefine(bool input);
|
|
ScalingSettings& ExpectedVarianceMerge(bool input);
|
|
ScalingSettings& SmoothGDegrees(double input);
|
|
ScalingSettings& RelativeBDegrees(double input);
|
|
|
|
ScalingSettings& RfreeFraction(double input);
|
|
ScalingSettings& ScalingRegularize(bool input);
|
|
|
|
ScalingSettings& ResolutionCutoff(ResolutionCutoffMethod input);
|
|
ScalingSettings& ResolutionCCTarget(double input);
|
|
ScalingSettings& ReportShellCount(int input);
|
|
|
|
[[nodiscard]] bool GetRefineWedge() const;
|
|
|
|
[[nodiscard]] double GetMinMosaicity() const;
|
|
[[nodiscard]] double GetDefaultMosaicity() const;
|
|
[[nodiscard]] double GetMaxMosaicity() const;
|
|
|
|
[[nodiscard]] double GetMinWedge() const;
|
|
[[nodiscard]] std::optional<double> GetRotationWedgeForScaling() const;
|
|
[[nodiscard]] double GetMaxWedge() const;
|
|
|
|
[[nodiscard]] bool GetMergeFriedel() const;
|
|
|
|
[[nodiscard]] std::optional<double> GetHighResolutionLimit_A() const;
|
|
|
|
[[nodiscard]] double GetMinPartiality() const;
|
|
[[nodiscard]] std::optional<double> GetForcedMosaicity() const;
|
|
[[nodiscard]] double GetCaptureUncertaintyCoeff() const;
|
|
[[nodiscard]] double GetMinCapturedFraction() const;
|
|
[[nodiscard]] double GetMinCCForImage() const;
|
|
[[nodiscard]] double GetSearchMinZeta() const;
|
|
[[nodiscard]] double GetOutlierRejectNsigma() const;
|
|
[[nodiscard]] bool GetScaleFulls() const;
|
|
[[nodiscard]] int GetAbsorptionIter() const;
|
|
[[nodiscard]] bool GetCorrectionSurfaces() const;
|
|
[[nodiscard]] bool GetStillsPartialityRefine() const;
|
|
[[nodiscard]] bool GetExpectedVarianceMerge() const;
|
|
[[nodiscard]] double GetSmoothGDegrees() const;
|
|
[[nodiscard]] double GetRelativeBDegrees() const;
|
|
|
|
[[nodiscard]] double GetRfreeFraction() const;
|
|
[[nodiscard]] bool GetScalingRegularize() const;
|
|
|
|
[[nodiscard]] ResolutionCutoffMethod GetResolutionCutoff() const;
|
|
[[nodiscard]] double GetResolutionCCTarget() const;
|
|
[[nodiscard]] int GetReportShellCount() const;
|
|
};
|