Files
Jungfraujoch/image_analysis/bragg_prediction/BraggPrediction.h
T
leonarski_fandClaude Opus 5 46bb3bdbab Bragg prediction: say so when a frame overflows the prediction buffer
Found while chasing a 12% run-to-run spread in the merged reflection count of one
crystal. The GPU kernels claim output slots with an atomicAdd and, on overflow,
undid the increment with an atomicSub - so the counter saturated at the capacity
and the host could not tell a full buffer from an overflowing one. Which
reflections survived was then decided by CUDA block scheduling and changed every
run. Measured on that dataset: every frame predicts 23000-44000 against a 20000
buffer, and the spread reached the merged output (161591 / 165193 / 166110 /
166479 unique across four runs of the same command). Single-threaded runs diverge
too - this is entirely GPU-side.

Stop clamping the counter, so the true number predicted reaches the host, and
warn once per predictor when it exceeds the buffer. Which reflections are kept is
unchanged: making that reproducible means deciding what to keep when a frame
predicts more than the pipeline carries, and the obvious answers are worse - the
capacity is not the real limit, kPredictionOutput (10000, selected by smallest
excitation error) is, and on this crystal both a bigger buffer and a strided
selection collapse the merge, because the rotation combine rebuilds fulls from
exactly the partials that a smallest-excitation-error cut throws away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:22:39 +02:00

64 lines
2.8 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;
int max_hkl = 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:
const int max_reflections;
std::vector<Reflection> reflections;
// A frame that predicts more than the buffer holds keeps an ARBITRARY subset of them: the GPU
// kernels claim slots with an atomicAdd, so which ones survive depends on block scheduling and
// changes from run to run. Say so, once per predictor, rather than let it pass silently - it is
// not a small effect (measured: a 3% run-to-run spread in the number of merged reflections, and
// every frame of that dataset overflowed).
void ReportOverflow(int predicted);
bool overflow_reported = false;
// 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 (the GPU kernels then fill it in a non-deterministic atomic order - ReportOverflow
// warns when that happens; a large unit cell, ~2.8e6 A^3, reaches ~40000 per frame and overflows on
// every one). 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.
static constexpr int kPredictionCapacity = 20000;
static constexpr int kPredictionOutput = 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;
};