Build Packages / build:windows:nocuda (push) Successful in 20m4s
Build Packages / Unit tests (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 16m5s
Build Packages / build:viewer-tgz:cuda (push) Successful in 17m26s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m46s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 20m17s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 26m13s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 23m17s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 28m11s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m30s
Build Packages / build:rpm (rocky8) (push) Successful in 24m34s
Build Packages / build:rpm (rocky9) (push) Successful in 21m30s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 23m33s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m18s
Build Packages / DIALS test (push) Successful in 18m23s
Build Packages / XDS test (durin plugin) (push) Successful in 11m30s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 10m16s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m2s
Build Packages / Generate python client (push) Successful in 49s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:cuda (push) Successful in 29m45s
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use. * **rugnux: significantly better quality of results, and faster.** A large rework of integration, scaling, merging, geometry refinement and space-group determination, together with measurements the program previously made no attempt at - the direct beam before indexing, the beam stop, the goniometer rotation scale, and the stretches of a sweep the crystal did not deliver. A rotation dataset typically gains observations at better <I/sigma> and R_meas, and every `mx` and `scale` run writes a `<prefix>_report.txt` results report modelled on XDS's `CORRECT.LP`. Many defaults moved with it: spot detection is self-calibrating, beam-stop detection and rotation geometry post-refinement are on, resolution limits default to as far as the detector reaches, and ice-ring handling engages only where the crystal is measured to have ice. * **jfjoch_viewer:** the beam-stop shadow, the detector calibration and the beam-centre measurement are reachable from "Analyze dataset"; the settings panel reports how the sample moved and how polarized the beam was; image rendering and interaction are faster. * **Performance:** bitshuffle+LZ4 images are decoded on the GPU rather than on the host, with the bitshuffle inverse fused into preprocessing so the decompressed frame is never held in device memory. * **Broker, writer, packaging and build:** image-slot lifetime and locking fixes, per-image datasets sized by the images actually written, the Debian/Ubuntu broker package renamed to `jfjoch`, and `image_analysis` compiling under MSVC again. **Breaking change to the rugnux command line:** * `--azint-only` and `--scale` are **removed**, replaced by `--mode azint` and `--mode scale`; the full pipeline is `--mode mx` and remains the default. A script passing the old flags now fails with the list of valid modes rather than silently running the wrong one. * `-t`/`--stride` is **refused on rotation data**: skipping frames cuts every reflection's rocking curve, so the combined fulls and their partiality would be measured over frames the sweep never recorded. Select a contiguous range with `-s`/`-e` instead. `--mode azint` and `--force-still` still take a stride. **Breaking changes to OpenAPI** - regenerate the client (`jfjoch-client` 1.0.0-rc.161, `frontend/src/client`) or read the affected fields as optional: * `image_scale_b` is removed from the `plot_type` enum, so a client requesting that plot now gets an error rather than a curve. * `azim_int_settings.high_q_recipA`, `spot_finding_settings.high_resolution_limit` and `spot_finding_settings.low_resolution_limit` are no longer `required`. All three mean "no limit at that end" when unset and are omitted from the response instead of carrying a placeholder value, which raises in a client generated from an rc.160-or-earlier spec. A value of 0 is still accepted and means the same thing. **Breaking changes to the stored formats** - a consumer reading these fields must treat them as optional: * The per-image image-scale B factor is no longer computed, so `/entry/MX/imageScaleBFactor` is absent from newly written HDF5 files and the corresponding key is absent from the CBOR DataMessage and END blocks. Files written by rc.160 and earlier still contain it and still open; nothing in the pipeline reads it any more. * `_reflns.jfjoch_diffrn_ISa` now carries the whole-range `1/sqrt(a*b)` that XDS's ISa denotes, and the error-model `a` and `b` are reported in XDS's convention; the strong-reflection asymptote moves to `_reflns.jfjoch_diffrn_ISa_asymptotic`. **A file written by an earlier version carries the asymptote under the plain `ISa` name.** Reviewed-on: #71 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
100 lines
5.8 KiB
C++
100 lines
5.8 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||
// SPDX-License-Identifier: GPL-3.0-only
|
||
|
||
#pragma once
|
||
|
||
#include <vector>
|
||
|
||
#include "../../common/CrystalLattice.h"
|
||
#include "../../common/DiffractionExperiment.h"
|
||
#include "../../common/Reflection.h"
|
||
|
||
struct BraggPredictionSettings {
|
||
float high_res_A = 1.5;
|
||
float ewald_dist_cutoff = 0.0005;
|
||
// Per-index half-widths of the box the predictor walks: h runs -max_h..+max_h, and so on. One limit
|
||
// per axis rather than one cube, because each index is bounded by its OWN axis (|h| <= a/d_min), so
|
||
// a cube sized for the longest axis walks the short ones far past anything the resolution cut can
|
||
// keep - on a 149/83/226 A cell that is ~16x the candidates a per-axis box generates.
|
||
int max_h = 100;
|
||
int max_k = 100;
|
||
int max_l = 100;
|
||
char centering = 'P';
|
||
float wedge_deg = 0.1f;
|
||
float mosaicity_deg = 0.2f;
|
||
float min_zeta = 0.05;
|
||
float mosaicity_multiplier = 4.0;
|
||
// Relative X-ray bandwidth Δλ/λ expressed as a Gaussian sigma (0 = monochromatic).
|
||
// Stills: the Ewald-shell acceptance is thickened radially per reflection by
|
||
// σ_bw = |recip_z|·bandwidth_sigma (= bλ/2d²), so the 1/d² pink-beam smear no
|
||
// longer clips high-resolution reflections.
|
||
// Rotation: differentiating Bragg's law at fixed d gives an extra rocking width
|
||
// Δθ = bandwidth_sigma·tan(θ_B), a spread in the same glancing angle the mosaic spread
|
||
// smears, so it adds to σ_M in quadrature. It is NOT divided by ζ: rotating the crystal
|
||
// by Δφ changes θ by ζ·Δφ, so the 1/ζ that turns an angular width into a rotation width is
|
||
// already the one the partiality applies to σ_M. CalcMosaicityXDS deconvolves the same term
|
||
// out of the fitted σ_M, so it is not counted twice.
|
||
float bandwidth_sigma = 0.0f;
|
||
};
|
||
|
||
class BraggPrediction {
|
||
protected:
|
||
// Not const: on the GPU path the buffer grows to fit a frame that predicts more than it holds.
|
||
int max_reflections;
|
||
std::vector<Reflection> reflections;
|
||
|
||
// Make room for `count` reflections. Overridden where device buffers have to follow. Called only
|
||
// when a frame predicted more than the current capacity, so a run pays for it a handful of times.
|
||
//
|
||
// NOTE: only the GPU Calc overrides call this. BraggPrediction::Calc and BraggPredictionRot::Calc
|
||
// stop filling at max_reflections instead, silently - and because that cap is applied inside the
|
||
// h/k/l walk, before the resolution test, what survives is the low-|h| block rather than the
|
||
// reflections nearest the Ewald sphere. A cell large enough to overflow 20000 therefore yields
|
||
// different merged reflections on a CPU-only build than on a GPU one.
|
||
virtual void GrowCapacity(int count);
|
||
|
||
// Deterministically cap Calc's output at output_limit: if more were predicted, keep the best-recorded
|
||
// ones - largest partiality on the rotation path, and, since partiality is 1 for every still, smallest
|
||
// excitation error on the still path - with hkl breaking what is left. Returns the kept count. Below
|
||
// the cap it is a no-op. Call at the end of every Calc override.
|
||
int TruncateToOutput(int count);
|
||
public:
|
||
// The prediction buffer holds up to kPredictionCapacity reflections so a strong lattice does not
|
||
// overflow it. Calc returns at most output_limit, the number that flows downstream and is serialized - kept low so the
|
||
// per-image reflection list stays within the frame transport headroom.
|
||
// Starting size. On the GPU path the buffer grows to whatever a frame actually predicts
|
||
// (GrowCapacity), so a large cell is not truncated there; the CPU path still caps at this value,
|
||
// see the note on GrowCapacity. It used to be a hard cap on both, and overflowing it was lossy
|
||
// and NON-DETERMINISTIC - the GPU kernels claim slots with an atomicAdd, so which reflections
|
||
// survived depended on block scheduling and changed between runs of the same command.
|
||
static constexpr int kPredictionCapacity = 20000;
|
||
// How many reflections may flow downstream per image, offline. Sized for a large unit cell: a
|
||
// ~2.8e6 A^3 cell predicts up to ~44000 per frame at 2.4 A. Truncating below what the frame really
|
||
// has costs more than it saves - the selection keeps the best-recorded reflections, and the rotation
|
||
// combine rebuilds a full FROM the partials it drops (measured on such a crystal: CC1/2 98 -> 60,
|
||
// ISa 8.6 -> 1.8). DiffractionExperiment's image-buffer headroom is derived from this, so the
|
||
// transport can carry what the analysis produces.
|
||
static constexpr int kPredictionOutput = 65536;
|
||
// What the ONLINE path may carry per image. The acquisition transports every reflection list through
|
||
// a fixed-size image-buffer slot, and the slot size divides a fixed total - so sizing the slot for
|
||
// kPredictionOutput would cut the number of slots, and with it the receiver's ability to absorb a
|
||
// burst, by about three. Online keeps the transport-sized limit it has always had; offline, which
|
||
// has no such budget, keeps the full one. DiffractionExperiment's buffer headroom derives from THIS.
|
||
static constexpr int kOnlineMaxReflections = 10000;
|
||
|
||
// How many reflections Calc may return. A caller with a tighter cap than the offline one - online,
|
||
// whose transport slot holds kOnlineMaxReflections - sets its own here, so the surplus is dropped
|
||
// before it is integrated rather than integrated and then thrown away.
|
||
int output_limit = kPredictionOutput;
|
||
|
||
explicit BraggPrediction(int max_reflections = kPredictionCapacity);
|
||
|
||
virtual ~BraggPrediction() = default;
|
||
virtual int Calc(const DiffractionExperiment &experiment, const CrystalLattice &lattice,
|
||
const BraggPredictionSettings &settings);
|
||
const std::vector<Reflection> &GetReflections() const;
|
||
};
|
||
|
||
|
||
|