Skip to content

feat: Bragg disk detection .single runs on every detector size - #61

Merged
Paullo9 merged 1 commit into
mainfrom
issue-60-bragg-single-any-size
Oct 31, 2026
Merged

Paullo9 merged 1 commit into
mainfrom
issue-60-bragg-single-any-size

Conversation

@Paullo9

@Paullo9 Paullo9 commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Requested by Paul · project thread

Fixes #60

What changes

  • SingleState (the .single Bragg path) builds FloatFFT2D(anyRows:columns:) instead of FloatFFT2D(rows:columns:). On sizes vDSP DFT takes, that is the same calls and the same bits. On any other size it runs Bluestein through a longer vDSP DFT. Before this change .single threw invalidOption on those sizes.
  • BraggDetector.supportsSinglePrecision now returns true for every positive size. The signature is unchanged and no consumer has to change code, so this is feat:, not feat!.
  • The BraggPrecision.single doc comment, the two DocC pages, docs/bragg.md (Sizes) and the consumer-check follow.
  • Not stacked: FloatFFT2D(anyRows:columns:) is already on main. This PR does not touch the files of docs: CPU mean no longer behind NumPy, close CuboKit#7 in open items #58 or feat: single-slice ptychography .single takes every detector size #59.

Tests (red first)

  • New nonVDSPSizesAgreeWithDouble covers 22 x 21, 165 x 40 and 40 x 56, with and without the pattern Gaussian, under .parabolic and .multicorr. Results:
    • Correlation map gap: 4.3e-7 of the map maximum (limit SingleTolerance.correlation, 1e-5).
    • Fourier product gap: 4.0e-7 (limit SingleTolerance.fourier, 3e-5).
    • 500 peaks: worst gaps 9.9e-5 px and 8.0e-6 relative intensity.
    • The whole-cube .single pass equals the per-pattern pass.
  • Peak bound on the tiled scene. The tiled S1 scene has broad, low-contrast maxima. On it, even a size vDSP takes directly (160 x 40) exceeds the strict per-peak intensity tier: 8.4e-6 against 5e-6. So the miss comes from the scene, not from Bluestein. These peaks get a stated bound of 2e-4 px (the app tolerance against py4DSTEM) and 2e-5.
  • The real fixture S2 (40 x 56, not a vDSP size) now runs in singleAgreesWithDouble and singleMatchesPy4DSTEM at the strict tier (5e-5 px, 5e-6 on peaks above 1e-3 of the brightest). The minimum case counts rose to 39 and 29 so that S2 has to stay in.
  • everySizeIsSupportedAndDoubleStaysDefault replaces the old refusal test.
  • Before the fix the new tests failed with invalidOption ("22 × 21 is not one"); after the fix they pass.
  • Gate: swift test -c release --parallel --num-workers 1 -j 1 --skip CuboKitParityTests passes all 429 tests in 8 targets. The CuboBragg tests run in 0.13 s, the slowest target in 0.62 s, and the incremental build took 8 s.
  • The consumer check (tools/consumer-check/run.sh) was not run locally because it builds with 2 jobs. CI runs it.

Review

A reviewer read the diff before the commit because it touches numerics. It found no correctness problem: every .single entry goes through SingleState, the Bluestein forward and inverse are right, the transposed layout and naturalSpectrum still work, and sharing a Bluestein line across workers is safe. Two stale comments and the peak bound were fixed after the review.

For a later docs catch-up

docs/open-items.md is in two open PRs, so this PR leaves it alone. Any line there that says .single Bragg refuses non-vDSP sizes is now resolved by this PR.

🤖 Generated with Claude Code

The Float32 path built FloatFFT2D(rows:columns:), which is nil for lengths vDSP DFT cannot take, so .single refused them with invalidOption. It now builds FloatFFT2D(anyRows:columns:): the same vDSP calls and bits on sizes vDSP takes, Bluestein on the others. BraggDetector.supportsSinglePrecision is true for every positive size; docs follow.

Tests: nonVDSPSizesAgreeWithDouble (22x21, 165x40, 40x56; map 4.3e-7 and Fourier 4.0e-7 of max against 1e-5 and 3e-5; peaks within 2e-4 px and 2e-5; whole-cube equals per-pattern), everySizeIsSupportedAndDoubleStaysDefault, and the S2 fixture (40x56) now in singleAgreesWithDouble and singleMatchesPy4DSTEM at the strict tier. Red before (invalidOption), green after.

Done: .single Bragg detection on any size (CuboKit#60). Next: CI and the consumer check on the PR; a docs catch-up can drop the open-items mention of the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Paullo9 Paullo9 self-assigned this Oct 31, 2026
@Paullo9
Paullo9 merged commit f60ef41 into main Oct 31, 2026
8 checks passed
@Paullo9
Paullo9 deleted the issue-60-bragg-single-any-size branch October 31, 2026 19:46
Paullo9 added a commit that referenced this pull request Nov 1, 2026
…ctor size (#69)

Since #61 the Float32 Bragg path builds FloatFFT2D(anyRows:columns:), so .single runs on every positive detector size and supportsSinglePrecision is true for all of them. The consumer check still expected a 40 x 56 .single run to throw invalidOption, so it failed 1 of 152 on main and on the Release PR.

The check now runs .single and .double on a 22 x 21 detector (a size vDSP DFT cannot take, FloatFFT2D.supports is false) with one planted disk: both find one peak near it and agree within 1e-3 px. supportsSinglePrecision also covers 22 x 21 (true) and a negative column count (false), next to the existing zero-row case.

No kit source or number changes. consumer-check: 152 of 152 pass; the fast gate is green.

Done: the consumer check matches the #61 behaviour. Next: the Release PR CI goes green after this merges.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bragg disk detection .single precision refuses detector sizes vDSP cannot take

1 participant