// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #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 high_resolution_limit_A; std::optional wedge_for_scaling; std::optional 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 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 public: ScalingSettings& RefineRotationWedge(bool input); ScalingSettings& RotationWedgeForScaling(std::optional input); ScalingSettings& MergeFriedel(bool input); ScalingSettings& HighResolutionLimit_A(double limit); ScalingSettings& HighResolutionLimit_A(std::optional limit); // nullopt clears the limit ScalingSettings& MinPartiality(double min_partiality); ScalingSettings& ForcedMosaicity(std::optional 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& 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 GetRotationWedgeForScaling() const; [[nodiscard]] double GetMaxWedge() const; [[nodiscard]] bool GetMergeFriedel() const; [[nodiscard]] std::optional GetHighResolutionLimit_A() const; [[nodiscard]] double GetMinPartiality() const; [[nodiscard]] std::optional 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]] ResolutionCutoffMethod GetResolutionCutoff() const; [[nodiscard]] double GetResolutionCCTarget() const; [[nodiscard]] int GetReportShellCount() const; };