Skip to content

fix: reset ap/bp/cp at phi==0 (thread-order-dependent stale-buffer bug)#4

Open
gcmullen wants to merge 1 commit into
mainfrom
fix/phi0-stale-rotation
Open

fix: reset ap/bp/cp at phi==0 (thread-order-dependent stale-buffer bug)#4
gcmullen wants to merge 1 commit into
mainfrom
fix/phi0-stale-rotation

Conversation

@gcmullen

Copy link
Copy Markdown
Collaborator

Summary

At the phi==0 sample the spindle rotation is skipped (if(phi != 0.0)), but
ap/bp/cp are declared outside the pixel loop (OpenMP firstprivate), so they
still hold the previous pixel's rotated orientation instead of the unrotated
a0/b0/c0. Intensity is inflated whenever phi0==0 (the default) with phisteps>1.

Why it's a bug, not just a divergence

The stale buffer makes the result depend on thread count — which a physics
result must not. Same config, varying OMP_NUM_THREADS:

  • up to ~28% per-pixel difference (5 distinct md5s at threads 1/2/4/8/24)
  • ~14% total-intensity shift at 1 thread vs the intended phi==0 = a0 orientation

Aggregates wash out (~corr 1.0), which is why it hid for years. Independently
confirmed: -phi 1e-9 (phi never exactly 0) matches to float-eps at all phisteps.

Fix

Keep the fast phi==0 path (skip the rotation) and add an else resetting
ap/bp/cp = a0/b0/c0 — mirroring the mosaic reset just below. rotate_axis(phi=0)
is the identity, so this is bit-exact with unconditionally rotating and
unchanged for all phi!=0.

Scope

nanoBragg.c, +10 lines, one hunk. No API/CLI change.
Proposal for your review — happy to switch to remove-guard form (byte-identical).

The phi sweep skips the spindle rotation at phi==0 via `if(phi != 0.0)`, but
ap/bp/cp are declared outside the pixel loop (OpenMP firstprivate), so at the
phi==0 sample (default phi0=0 with phisteps>1) they still hold the previous
iteration's rotated values instead of the unrotated a0/b0/c0.

The result is thread-order-dependent: running the same config with different
OMP_NUM_THREADS gives per-pixel differences up to ~28% (a physics result must
not depend on thread count), and a ~14% total-intensity shift vs the intended
phi==0 = a0 orientation.

Keep the fast phi==0 path (skip the rotation) and add an `else` that resets
ap/bp/cp to a0/b0/c0 — mirroring the mosaic reset just below. Bit-exact with
rotating at phi==0 (rotate_axis(phi=0) is identity) and unchanged for all phi!=0.
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.

1 participant