Skip to content

Added support for using the object surface as the field stop of a SequentialSystem#169

Open
jacobdparker wants to merge 1 commit into
fix/solver-robustnessfrom
fix/object-field-stop
Open

Added support for using the object surface as the field stop of a SequentialSystem#169
jacobdparker wants to merge 1 commit into
fix/solver-robustnessfrom
fix/object-field-stop

Conversation

@jacobdparker

Copy link
Copy Markdown
Contributor

Second PR in the stop-solver stack — stacked on #168 (the diff shown here is relative to that branch).

Problem

Marking the object surface (with an angular, dimensionless aperture) as the field stop is the natural configuration for dispersive systems — at a single wavelength of a dispersed spectrum, most of the sensor outline is unreachable, so targeting the sensor wire is ill-posed. But this configuration was broken in three independent ways:

  1. The position-variable branch of _calc_rayfunction_stops_only called sag() with a 2-component vector → TypeError before the solve started.
  2. The solved outputs were never broadcast over both stop axes → axis error in the field_min/max / pupil_min/max reductions.
  3. The hardcoded initial guess (rays at the first stop's origin, direction +z) produces NaN first residuals whenever the next surface is far off that axis (e.g. the feed mirror of a Rowland-circle spectrograph, 174 mm off-axis in FURST) or on steep grazing flanks — and Newton cannot recover from NaN.

Changes

  • zfunc builds a proper 3-D vector before calling sag().
  • _calc_rayfunction_stops broadcasts position and direction against each other before the stop-axis reductions.
  • New _anchor_surface helper: the seed now aims each ray at its own target point on the last stop surface when no powered surface lies between the stops (nearly exact — the grazing solve converges in ~5 iterations), and otherwise at the center of the first powered surface.

Tests

Adds TestSequentialSystemGrazingSpectrograph: a grazing-incidence paraboloid + transmission-grating spectrograph whose source is the field stop, asserting the recovered field equals the source's angular radius (0.25°) to 1e-6 deg. Also verified against the FURST Spectrograph model from Kankelborg-Group/furst-optics#25, which recovers field_max = ±0.26656° (the sunpy solar angular radius).

🤖 Generated with Claude Code

…uentialSystem

Marking the object surface (with an angular, dimensionless aperture) as
the field stop is the natural configuration for dispersive systems,
where the sensor outline is not reachable at a single wavelength, but
the stop root-finding problem was broken in three ways for this case:

- The position-variable branch of `_calc_rayfunction_stops_only` called
  `sag()` with a 2-component vector, raising a TypeError before the
  solve even started.
- The solved outputs were never broadcast over both stop axes, so the
  reductions in `field_min`/`field_max`/`pupil_min`/`pupil_max` raised
  an axis error.
- The initial guess started every ray at the origin of the first stop
  with direction +z, which produces NaN residuals on the first
  iteration for surfaces far from that axis (e.g. the off-axis feed
  mirror of a Rowland-circle spectrograph) or on steep grazing-incidence
  flanks, and Newton's method cannot recover from NaN.

The seed now aims each ray at its own target point on the last stop
surface when no surface with optical power lies between the two stops
(nearly exact), and otherwise at the center of the first powered
surface.

Adds a grazing-incidence spectrograph regression test whose source is
the field stop, asserting that the recovered field equals the source's
angular radius.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.79592% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.36%. Comparing base (9905960) to head (40c7ea5).

Files with missing lines Patch % Lines
optika/systems/_sequential.py 87.80% 5 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                    @@
##           fix/solver-robustness     #169      +/-   ##
=========================================================
+ Coverage                  99.29%   99.36%   +0.07%     
=========================================================
  Files                        116      116              
  Lines                       5978     6024      +46     
=========================================================
+ Hits                        5936     5986      +50     
+ Misses                        42       38       -4     
Flag Coverage Δ
unittests 99.36% <89.79%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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