Skip to content

Weight wfs_input_opd_rms_m by the pupil and remove piston (2.0.0) - #12

Merged
jacotay7 merged 2 commits into
mainfrom
fix/pupil-weighted-input-rms
Oct 7, 2026
Merged

jacotay7 merged 2 commits into
mainfrom
fix/pupil-weighted-input-rms

Conversation

@jacotay7

@jacotay7 jacotay7 commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Breaking (2.0.0). wfs_input_opd_rms_m now follows aocore CONVENTIONS.md 4.1: the RMS of the input OPD in metres, weighted by pupil intensity, with piston removed. In 1.x it was the quadratic mean over the whole input grid: every pixel counted equally, including pixels outside the pupil, and piston was kept.

  • Weights. The weights are the intensity (amplitude squared, as the engines use it) of the pupil the optics use, on the input grid:
    • An analytic pupil is evaluated on input.shape with the same telescope model and numerics.pupil_supersampling the engine uses on its own grid. This is the map WavefrontSensor.pupil_illumination() returns, computed in float64. Both grids span input.grid_extent_m.
    • A custom mask exists only on the engine's pupil grid (configured_pupil, now exposed on both engines), so its intensity is area-averaged onto the input grid by the new sampling.area_rebin. That function uses exact cell overlaps for any shape ratio and returns the mask unchanged when the shapes match.
  • Old value kept. The 1.x quantity is still recorded as wfs_input_opd_rms_unweighted_m.
  • Still on the device. Both values are reduced on the device and cross to the host with the captured rate in the existing single backend.scalars batch, so there is no new host round trip.
  • Empty pupil. If the pupil has no transmission on the input grid, the sensor now refuses to build.
  • Docs. docs/concepts.md defines both keys, docs/stability.md has a "Migrating to 2.0" note, the CHANGELOG has a Breaking section, and AGENTS.md records the contract.
  • Examples. closed_loop_injection.py and showcase.py now plot the metadata value instead of a whole-grid np.std.
  • Version. 2.0.0 in __about__.py, CITATION.cff and the CHANGELOG (dated 2026-10-07).

Consumers. I grepped the whole aosim stack (shmpipeline-ao, pyRTC, solvephase, aocore, pyturb) for wfs_input_opd_rms. Nothing outside makewfs reads the key.

aocore 0.1.3 adoption (second commit). The dependency is now aocore>=0.1.3,<0.2.

  • block_sum. sampling.block_sum drops the local factor-two shortcut and delegates to aocore.block_sum. I timed both at the real Shack-Hartmann call sites (medians, CPU / P620):

    Spot stack aocore old shortcut
    (400,16,16) f32 112 / 65 us 173 / 94 us
    (3600,12,12) f64 0.90 / 0.10 ms 1.27 / 0.17 ms
    (64,16,16) f32 about equal (CPU)

    Shack-Hartmann images now differ from 1.2.0 by addition-order rounding: at most about 1e-7 relative in float32 and 2e-16 in float64. Pyramid images are bit-identical.

  • Coordinates. Pixel-centre coordinates are built on the device with aocore.centered_coordinates(dtype=, backend=), through ArrayBackend.centered_coordinates. The values are identical.

  • rms_unweighted. aocore.rms_unweighted is now the reference for the unweighted key in the tests. It is not used per frame because it returns a host float, which would add a synchronization.

Tests

New tests/test_input_rms.py, run on both sensors:

  • pupil_rms passes aocore.conformance.check_rms.
  • A piston-only input gives 0 for the weighted key and the piston for the unweighted one.
  • A tilt matches the analytic annulus RMS s R sqrt(1+e^2)/2 to 0.2 %, and matches aocore.rms on the sensor's pupil to 1e-12.
  • Random OPD outside the pupil leaves the weighted key bit-for-bit unchanged and inflates the unweighted one.
  • expose_integrated reports the RMS of the mean OPD.
  • Custom-mask weights are area-averaged, at a 1:1 shape ratio (SH) and a 2:1 ratio (pyramid).
  • Phase input is converted before the reduction.
  • area_rebin keeps uniform maps uniform, conserves the area integral, and is a block mean for integer factors.

The new GPU test (-m gpu) checks CPU/GPU agreement to 1e-12 for both keys, both sensors, analytic and custom pupils, and both single and integrated exposures.

Local runs (conda 3_13, Quadro P620):

  • ruff check/format and mypy pass.
  • pytest: 232 passed, 2 failed. The failures are the 2 known OOPAO validation tests, which fail the same way on main (argmin of an empty sequence).
  • pytest -m gpu: 21 passed.
  • Benchmark regression check passes.
  • mkdocs build --strict and python -m build pass.
  • The gallery, closed-loop and showcase examples run.

Cost: the extra reductions add about 0.1 ms of CuPy dispatch per frame on the P620. That is within the run-to-run noise of a 2.5 ms warm SH expose on a GPU shared with other processes.

🤖 Generated with Claude Code

jacotay7 and others added 2 commits October 6, 2026 19:08
wfs_input_opd_rms_m now follows aocore CONVENTIONS 4.1: the RMS of the
input OPD in metres weighted by the intensity of the pupil the optics use,
on the input grid, with the intensity-weighted mean removed. An analytic
pupil is evaluated on input.shape with the configured supersampling; a
custom mask is area-averaged from the engine's pupil grid. The 1.x
quantity, the unweighted whole-grid RMS with piston kept, is still
recorded as wfs_input_opd_rms_unweighted_m. Both are reduced on the
device and cross in the existing single batched transfer.

Release 2.0.0: this changes the meaning of an existing metadata key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Require aocore>=0.1.3,<0.2. block_sum delegates every factor to
aocore.block_sum, which measured at least as fast as the local factor-two
shortcut on the Shack-Hartmann spot stacks on CPU and GPU. Pixel-centre
coordinates are built on the selected device through
ArrayBackend.centered_coordinates. The input-RMS tests check the
unweighted key against aocore.rms_unweighted; the per-frame reduction
stays local because the aocore metrics return host floats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jacotay7
jacotay7 merged commit 74b5369 into main Oct 7, 2026
12 checks passed
@jacotay7
jacotay7 deleted the fix/pupil-weighted-input-rms branch October 7, 2026 05:21
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