Build Packages / Create release (push) Successful in 18s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 6m52s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m4s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m3s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m20s
Build Packages / build:windows:nocuda (push) Successful in 17m9s
Build Packages / build:windows:cuda (push) Successful in 19m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m54s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m21s
Build Packages / build:rugnux:windows (push) Successful in 11m3s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m46s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m49s
Build Packages / Generate python client (push) Successful in 1m4s
Build Packages / Build documentation (push) Successful in 1m29s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m3s
Build Packages / build:rpm (rocky8) (push) Successful in 17m23s
Build Packages / build:rpm (rocky9) (push) Successful in 18m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m22s
Build Packages / Unit tests (push) Successful in 1h13m48s
A powder ring is reproducible. Past the crystal's own limit the ice is still there and still the same in both half-sets, so the two halves agree about the ice, and a Pearson correlation cannot tell that agreement from diffraction. Shells have been measured at CC1/2 0.765 where <I/sigma> is -0.1 and R_meas is 470 per cent, and at 0.828 where R_meas is 560 - inside the written range, because the logistic was fitted through them. Against archived references a run whose frames carry ice is written a mean twelve per cent finer than the reference and one without four, and every over-claim past twenty per cent has rings. The same ring drags the curve the other way where the data are good: one shell of forty falls to CC1/2 0.51 because the two strongest ice lines cross it, and the shell either side reads 0.99. That is a real defect of those reflections, but it is narrower than the shell it is reported in, so it defames three quarters of a shell that is fine. Both signs are the same cause, and both leave the fit here. Scaling, the error model and the space-group search already keep ice out of their own fits - what changes is only that the resolution DECISION now reads the curve those three read. The reflections are still merged, still written, still counted in the shell table: the ring contaminates an intensity, it does not make it absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
90 lines
4.4 KiB
C++
90 lines
4.4 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <cmath>
|
|
|
|
#include "SpotToSave.h"
|
|
|
|
struct Reflection {
|
|
int32_t h;
|
|
int32_t k;
|
|
int32_t l;
|
|
float image_number; // Can be in-between for 3D integration
|
|
float delta_phi_deg; // phi angle from XDS - difference from middle of current frame (NOT an absolute angle)
|
|
float predicted_x;
|
|
float predicted_y;
|
|
float observed_x;
|
|
float observed_y;
|
|
float d;
|
|
float I;
|
|
float bkg;
|
|
float var_bkg; // non-signal (background) part of sigma^2, carried to the merge
|
|
float sigma;
|
|
float dist_ewald;
|
|
// The reciprocal Lorentz factor (rotation only - a still's Lorentz factor is one) times the
|
|
// reciprocal polarization factor, and nothing else. This is what LP means everywhere in the
|
|
// field, and it is what the CBOR key "rlp" and the HDF5 dataset "lp" store the reciprocal of.
|
|
// What it is not is a scale: the fitted per-image scale and the partiality stay out of it and
|
|
// are divided in separately below. (Named after DIALS's prescaling_correction.)
|
|
float prescaling_corr;
|
|
// The sensor's angle-dependent efficiency, QE(0)/QE(alpha): always <= 1, and exactly 1 where the
|
|
// sensor is opaque or its material and thickness are unknown. It is carried BESIDE
|
|
// prescaling_corr rather than inside it, because LP and detector response are two different
|
|
// things and every file the field reads keeps them apart. It is one of the three factors whose
|
|
// product is the total deterministic correction, with prescaling_corr above and flight_corr
|
|
// below. Defaulted to 1 so a reflection read from a file written before this existed is a no-op
|
|
// rather than a zero.
|
|
float qe_corr = 1.0f;
|
|
// The medium in the sample-to-pixel flight path, exp(D/L * (1/cos(alpha) - 1)) with D the
|
|
// normal-incidence distance, L the medium's attenuation length and alpha the angle of incidence
|
|
// on the detector: always >= 1, because an oblique reflection crossed more of the medium than
|
|
// one arriving head-on. Exactly 1 under --flight-path vacuum. Carried beside the two above for the same
|
|
// reason they are carried apart - it is neither beam geometry nor detector response but the
|
|
// medium in between, and unlike either of them it is set by the flight distance. The total
|
|
// deterministic correction on a reflection is prescaling_corr * qe_corr * flight_corr, and every
|
|
// site that corrects an intensity multiplies all three. Defaulted to 1 so a reflection read
|
|
// from a file written before this existed is a no-op rather than a zero.
|
|
float flight_corr = 1.0f;
|
|
float partiality; // fraction of the reflection recorded in the sampled (rocking) slice
|
|
float zeta;
|
|
float image_scale_corr; // I_true = image_scale_corr * I; = prescaling_corr * qe_corr * flight_corr / (partiality * image_scale)
|
|
bool observed = false;
|
|
bool on_ice_ring = false; // sits on a hexagonal-ice powder ring: excluded from scaling, kept for merging
|
|
};
|
|
|
|
struct MergedReflection {
|
|
int32_t h = 0;
|
|
int32_t k = 0;
|
|
int32_t l = 0;
|
|
float I = NAN;
|
|
float sigma = NAN;
|
|
float I_half[2] = {NAN, NAN};
|
|
float sigma_half[2] = {NAN, NAN};
|
|
float d = 0.0;
|
|
// Any observation of this reflection sat on an ice ring. The intensity is still merged and
|
|
// written - the ring contaminates it, it does not make it absent - and this only marks it so
|
|
// a consumer that must not read the ring as crystal signal can leave it out.
|
|
bool on_ice_ring = false;
|
|
bool rfree_flag = false;
|
|
float F = NAN; // French-Wilson amplitude |F| (filled by ApplyFrenchWilson at end of merge)
|
|
float sigmaF = NAN; // its sigma
|
|
// Anomalous (Bijvoet) split of this reflection's own observations, kept even when the merge is
|
|
// Friedel-averaged (I above is the Friedel mean). Lets I(+)/I(-) be written and CCano reported by
|
|
// default without scaling anomalously; NaN when a hand was not measured or for centrics.
|
|
float I_plus = NAN;
|
|
float sigma_plus = NAN;
|
|
float I_minus = NAN;
|
|
float sigma_minus = NAN;
|
|
// French-Wilson amplitudes of the two hands (filled by ApplyFrenchWilson from I_plus/I_minus).
|
|
float F_plus = NAN;
|
|
float sigmaF_plus = NAN;
|
|
float F_minus = NAN;
|
|
float sigmaF_minus = NAN;
|
|
};
|
|
|
|
|