Commit Graph
3 Commits
Author SHA1 Message Date
leonarski_f 0f11897615 beam centre: the combination and the search follow the transforms onto the device
With the transforms on cuFFT the capture's cost was no longer the transforms: on a 16 Mpixel
detector they are 26 ms, while bringing the four convolution surfaces back is 0.9 s, the
masked Pearson over them is 0.4 s and the greedy search is 1.4 s. All three now run where the
surfaces already are, and only the shortlist crosses PCIe.

The engine interface gains one virtual, PointShortlist, whose default is exactly what the
code did before - PointSurfaces, then BeamCenterPointScore, then BeamCenterShortlist2D on the
host - so the CPU path is unchanged and an engine that has nothing to gain overrides nothing.
Those two functions stop being file-local and become the documented reference the device
kernels are held to.

Memory: three 2h x 2w surfaces, not four. The last inverse's output is read where it lies in
the transform buffer, which nothing overwrites afterwards. Peak on a 16 Mpixel detector goes
from 1.68 GB to 2.55 GB, and DeviceMemoryNeeded counts the surfaces so the fit check and the
CPU fallback still cover it.

The parity test is widened to compensate for what the two paths no longer share. It now
compares the four convolutions, the scored surface (the device kernel against
BeamCenterPointScore, which is what PointScoreSurface exists for), the shortlist (the device
search against BeamCenterShortlist2D on one surface, exactly - the device path is
deterministic), and the whole score. Where the surfaces are compared the tolerance is stated
against the shortlist's reach: r is a ratio of two cancellations, so a weak centre carries
1e-4 of the transform's 1e-6 whichever path computed it, and at a peak - the only part of the
surface anything reads - the two agree to 7.5e-6.
2026-09-13 07:37:05 +02:00
leonarski_f 6326c8b301 beam centre: the capture's transforms run on the GPU, and the CPU stays the fallback
BeamCenterFFTScore is split the way the FFT indexer is: an engine interface with a cuFFT
implementation and an fftw3f one, chosen by whether CUDA is compiled in, a device is visible
and the card has room for the image. Everything that decides anything - the preparation, the
masked Pearson, the shortlist and the margins - is shared, so the engines can differ only in
how the four convolutions are computed, and the parity test compares two shortlists rather
than two answers.

The whole-detector transform was the entire added cost of the capture (4-15 s a run on CPU,
all of it the transforms). On the device it is milliseconds, so the composition is now
cheaper than the walk it replaced rather than dearer - which is what makes 2x2 binning, the
other way out, unnecessary: full resolution is affordable and the binned capture was measured
to pick a neighbouring peak on one dataset of 51.

Device discipline, because the card is shared with the run's own analysis workers: the four
convolution surfaces are brought back to the host and combined there, so the device holds
only one real buffer and three spectra; the spectrum of the image is reused for its square;
plans and buffers are created inside the call that needs them and freed when it returns; and
an image that would not fit is scored on the CPU instead.

Tests, none of which need a GPU or a dataset: the autoconvolution identity (an exactly
symmetric image is recovered at integer and half-pixel centres, scoring exactly 1), shift
equivariance, the four convolutions against brute force, the no-variance overlap that
VARIANCE_FLOOR exists for, the smooth pad, GPU against CPU, and the composition - a walk
seeded at the capture where the walk alone declines, the fallback to the capture alone at
BEAM_CENTER_CAPTURE_SIGMA_PXL, and the beam stop being blanked out of the scored image.
2026-09-13 07:37:05 +02:00
leonarski_fandClaude Opus 5 1d1d7fd686 rugnux: whole-detector FFT beam-centre capture, measured on every run, consumed by none
The centrosymmetry score of the pre-scan projection is a self-convolution, so one FFT set
scores every candidate centre on the detector at half-pixel spacing - the cost is O(N log N)
and independent of how far the header centre is from the truth, where every existing search
pays per pixel of error. BeamCenterFFT computes the 2D point-inversion score (a background
measurement, the physics FindBeamCenterFromBackground fits locally) and the two 1D line-mirror
scores (for the along-spindle coordinate this is exact Friedel physics, sharp exactly where
the indexing count is blind), and returns a non-maximum-suppressed shortlist plus a
peak-to-runner-up margin per surface - a shortlist and a margin, never a centre: measured on
17 datasets the surface can be locally flat (~25 px), and the margin is what says so.

Measured on the gross-header cases that motivate it (offline, float64 reference): a header
351 px wrong is captured at rank 1 within 1.0 px from 30 frames, and still from a 30 deg
wedge; two 73 px placeholder headers at rank 1 within 0.5 px; a dataset whose header is
~170 px wrong but which no centre can index is flagged by the lowest margin of the set
(0.8 %) instead of being answered confidently. Capture survives 30-120 deg wedges on all
four sets tried (union of 8 candidates within 6 px everywhere).

Numerics: fftwf (the tree's FFTW is single precision) with the valid-pixel mean subtracted
before the transform - the masked Pearson is exactly invariant under a global shift, and the
subtraction removes the large-term cancellation - and the per-element combination done in
double. Against the float64 numpy reference the shortlist positions are identical and the
margins agree to 3e-6 absolute on the two controls whose margins are 0.3 %. An overlap with
no image variance (a mirrored empty region) carries no evidence and minted r values of 4-274
in float64 as much as float32; such centres are now not scored (VARIANCE_FLOOR).

Wired report-only into the pre-scan next to the background estimate: one log line with the
strongest candidates, the margins and the wall time. Nothing consumes it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
2026-09-13 07:37:04 +02:00