Skip to content

Fixed spurious Max iterations exceeded errors in the stop root-finding problem#168

Open
jacobdparker wants to merge 1 commit into
mainfrom
fix/solver-robustness
Open

Fixed spurious Max iterations exceeded errors in the stop root-finding problem#168
jacobdparker wants to merge 1 commit into
mainfrom
fix/solver-robustness

Conversation

@jacobdparker

Copy link
Copy Markdown
Contributor

First of a stack of three PRs fixing the normalized-coordinate stop solver for non-all-mirror systems (next: object-as-field-stop support, then transmissive stops). These bugs currently block the FZP tutorial (#153), the grazing-spectrograph tutorial (#166), and the FURST Spectrograph model (Kankelborg-Group/furst-optics#25).

Problem

The Newton solve in _calc_rayfunction_stops_only relied on two scale-dependent defaults:

  • na.jacobian's default perturbation is an absolute 1e-10 in the units of the variable. For variables measured in physical units (e.g. positions in mm), that is below the floating-point noise floor of the raytrace, so the Jacobian is computed from noise and the iterates step wildly.
  • na.optimize.root_newton's default tolerance is 1e-10 in the units of the residual — 0.1 femtometers for mm residuals. Solves that had converged to machine precision (~1e-14 of the system scale) were reported as Max iterations exceeded because a few elements oscillate in float noise just above the threshold.

Changes

  • The Jacobian perturbation and the convergence tolerance are now proportional to the size of the target aperture wire.
  • Solver failures are re-raised with the names of the stop surfaces involved and a hint about marking the object surface as the field stop for dispersive systems, instead of an anonymous Max iterations exceeded from inside named_arrays.

No behavior change for systems that already converged; the existing test battery passes unchanged. Regression tests for the newly-solvable systems come with the next two PRs in the stack.

🤖 Generated with Claude Code

…ing problem

The Newton solve in `_calc_rayfunction_stops_only` used the default
perturbation of `na.jacobian`, an absolute 1e-10 in the units of the
variable, which is below the floating-point noise floor of the raytrace
for variables measured in physical units, yielding a Jacobian made of
noise and wild iterates. The default convergence tolerance of
`na.optimize.root_newton` (1e-10 in the units of the residual) had the
same scale-dependence problem: solves whose residuals had converged to
machine precision were reported as "Max iterations exceeded". Both are
now proportional to the size of the target aperture wire.

Failures that remain are re-raised with the names of the stop surfaces
involved and a hint about marking the object surface as the field stop
for dispersive systems, instead of an anonymous "Max iterations
exceeded" from deep inside named_arrays.

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 75.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.29%. Comparing base (9d38b2c) to head (9905960).

Files with missing lines Patch % Lines
optika/systems/_sequential.py 75.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #168      +/-   ##
==========================================
- Coverage   99.34%   99.29%   -0.05%     
==========================================
  Files         116      116              
  Lines        5967     5978      +11     
==========================================
+ Hits         5928     5936       +8     
- Misses         39       42       +3     
Flag Coverage Δ
unittests 99.29% <75.00%> (-0.05%) ⬇️

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.

# the size of the target aperture to be achievable in floating
# point for systems of any physical scale.
scale = np.maximum(
np.abs(grid_last.x).max(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you use ptp() here instead of max?

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.

2 participants