Files
Jungfraujoch/image_analysis/bragg_prediction/BraggPrediction.h
T
leonarski_fandClaude Opus 5 b5b7cf2cf9 docs: say what the CPU prediction path actually does
BraggPrediction.h claimed the buffer "GROWS to whatever a frame actually
predicts, so a large cell is never truncated here". Only the two GPU Calc
overrides call GrowCapacity; both CPU predictors stop at max_reflections.
The cap is applied inside the h/k/l walk and before the resolution test,
so what survives is the low-|h| block, not the reflections nearest the
Ewald sphere - a cell large enough to overflow 20000 gives different
merged reflections with and without a GPU. Documented rather than
silently claimed otherwise.

Also removed a paragraph describing a once-per-predictor overflow warning
that no longer exists, and fixed the rugnux_cli.cpp path in HDF5.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:02:57 +02:00

88 lines
4.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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).
// When > 0 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.
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 kPredictionOutput: if more were predicted, keep the ones
// closest to the Ewald sphere (smallest excitation error), ties broken by hkl. 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 kPredictionOutput, 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. 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 smallest excitation errors, i.e. the nearly
// fully-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;
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;
};