Skip to content

ToO mode: ~40x faster Target runs (ephemeris grid, dedup, serial EAS below threshold) - #128

Open
Areustle wants to merge 4 commits into
mainfrom
too-sun-moon-dedup
Open

Areustle wants to merge 4 commits into
mainfrom
too-sun-moon-dedup

Conversation

@Areustle

Copy link
Copy Markdown
Contributor

Summary

Target (ToO) mode was dominated by astropy coordinate work, not shower physics: at 1M thrown, 97% of a 28.9 s run was get_body/AltAz evaluated independently at every thrown time. Three commits, each A/B'd against the previous state:

  1. sun_moon_cut fetches each ephemeris once (dd39545). It called get_body four times per evaluation (twice again inside moon_phase_angle) and built three identical AltAz frames. Bit-identical output; cut stage 1.67x faster.
  2. Sky positions on a coarse time grid + cubic spline (1055862). New simulation.target.ephemeris_step (default 60 s; 0 = exact per-time evaluation, the old behaviour). Source AltAz is interpolated as a unit vector (azimuth wrap-safe); sun/moon altitude and phase angle directly. Scalar/few-point queries still evaluate exactly. At 200k thrown: source error 2e-8 rad (astropy's own noise floor — 300 s gives the same), sun/moon 2e-10 rad, zero mask flips for steps up to 900 s. End-to-end 1M thrown: same rows and passing events, OMCINT rel. diff 2.6e-11, 25.1 s → 1.7 s.
  3. EAS optical runs in-process below a valid-shower threshold (dc591c2). The dask cluster's ~1 s spawn/teardown was half the remaining wall time. CphotAng.__call__/EAS.__call__ gain serial=True (same packed chunk kernel, whole batch, this process); compute() decides after geometry using new simulation.eas_parallel_threshold (default 150 000; 0 = always cluster). Bit-identical both ways in Diffuse and Target at all sizes tested; break-even measured at ~150k valid (≈11 µs/shower serial vs ≈1.3 s + 2.7 µs/shower cluster).

Net: 1M-throw Target run 28.9 s → 0.74 s; large Diffuse runs unchanged (still use the cluster).

A/B evidence

Serial vs cluster, seed-matched, every column + MC meta array_equal:

mode valid cluster serial
Diffuse 99k 1.88 s 1.10 s
Diffuse 992k 3.99 s 11.36 s
Diffuse 3.97M 10.9 s 45.5 s
Target 39k 2.27 s 0.74 s
Target 194k 2.88 s 3.07 s

Also prototyped (not adopted) a closed-form ICRS→CIRS-once + Earth-rotation-angle transform for the source: 2.5e-6 rad error (polar motion + diurnal aberration missing), and with ERFA apio13/atioq 9e-7 rad and slower than the spline. The grid already exploits the same slow/fast structure and reaches astropy's noise floor.

Reviewer notes

  • Two new config fields, both defaulting to the fast path with an escape hatch to old behaviour (ephemeris_step = 0, eas_parallel_threshold = 0). test_config serialization expectations updated accordingly.
  • Public signatures of get_sun, get_moon, moon_phase_angle(time) are unchanged; new sun_moon_state(time) and phase_angle_from_bodies(sun, moon) helpers.
  • New test test_ephemeris_grid_matches_exact pins interpolated vs exact positions (<1e-6 rad) and identical cut masks on 5000 random times.
  • Ruff flags a pre-existing unused refractive_index import in cphotang.py; left alone as unrelated.

Test plan

  • pytest test/core test/simulation/geometry test/simulation/eas_optical — 162 passed
  • black / flake8 clean on changed files
  • A/B scripts above (scratch, not committed)

🤖 Generated with Claude Code

Areustle and others added 4 commits September 11, 2026 11:33
sun_moon_cut called get_body four times per evaluation (sun and moon for
the altitude cuts, then both again inside moon_phase_angle) and built
three identical AltAz frames. Fetch each body once, share one frame, and
compute the phase angle from the coordinates already in hand.

Output is bit-identical (A/B on 1k/10k/40k random observation times);
the cut stage runs 1.67x faster, ~12% off ToO-mode wall time at 1M
thrown. get_sun/get_moon/moon_phase_angle(time) keep their signatures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Target mode asked astropy for the exact source AltAz at every thrown
time and exact sun/moon ephemerides at every valid time. Those are
smooth functions over the observation window, so evaluate them exactly
on a grid (simulation.target.ephemeris_step, default 60 s; 0 = exact)
and cubic-interpolate to the requested times. The source is
interpolated as a unit vector so azimuth wrap is harmless; sun/moon
altitude and phase angle are interpolated directly. Scalar or few-point
queries (fewer than the grid) still evaluate exactly.

A/B vs exact at 200k thrown (24 h window): source position error
2e-8 rad, sun/moon altitude 2e-10 rad, phase 3e-11 rad; horizon, volume
and sun/moon masks identical for steps up to 900 s. End-to-end compute
at 1M thrown: identical row count and passing events, OMCINT relative
difference 2.6e-11, wall 25.1 s -> 1.7 s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dask LocalCluster costs ~0.7 s to spawn and ~0.25 s to close, which
was half the wall time of a 1M-throw Target run once the ToO geometry
was fixed. CphotAng.__call__ and EAS.__call__ gain serial=True, which
runs the same packed chunk kernel on the whole batch in this process.
compute() now decides after geometry, when the valid count is known:
below simulation.eas_parallel_threshold (default 150_000; 0 = always
cluster) it runs serially and never starts a cluster; at or above it,
the background cluster starts there and overlaps the spectra/tau/decay
stages as before.

A/B, seed-matched, every column and MC meta bit-identical both ways:
  Diffuse   99k valid: cluster 1.88 s, serial  1.10 s
  Diffuse  992k valid: cluster 3.99 s, serial 11.36 s
  Diffuse 3.97M valid: cluster 10.9 s, serial 45.5 s
  Target    39k valid: cluster 2.27 s, serial  0.74 s
  Target   194k valid: cluster 2.88 s, serial  3.07 s
Fit: ~11 us/shower serial vs ~1.3 s + ~2.7 us/shower on the cluster,
break-even ~150k valid showers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 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.

3 participants