Skip to content

Add optika.zernikes and ZernikeSag#172

Open
jacobdparker wants to merge 3 commits into
fix/polygonal-aperture-boundsfrom
feature/zernike-sag
Open

Add optika.zernikes and ZernikeSag#172
jacobdparker wants to merge 3 commits into
fix/polygonal-aperture-boundsfrom
feature/zernike-sag

Conversation

@jacobdparker

Copy link
Copy Markdown
Contributor

Summary

Stacked on #171 (will retarget to main once it merges; the branches are independent in content).

  • optika.zernikes: Noll-indexed, RMS-normalized Zernike polynomials (noll(), zernike()) and their analytic gradients (zernike_gradient()), matching the convention of :cite:t:Noll1976 (reference added to refs.bib). The gradient uses an origin-safe series for R_n^m(rho)/rho, so it is exact at the center of the pupil.
  • optika.sags.ZernikeSag: a sag profile that perturbs a base sag with a sum of Zernike polynomials, for modeling measured or analytic figure error. Element i of coefficients is the Noll-j = i + 1 coefficient over the disk of the given radius. The analytic normal() combines the base gradient with the Zernike gradients; the ray intercept falls back to the inherited numeric solver.

Because the figure error perturbs the surface, the same object is seen consistently by both the geometric raytrace and (in an upcoming PR) the physical-optics propagation - a single source of truth for tolerancing.

Tests

  • noll() against the published index table; closed forms for j = 1, 2, 3, 4, 11.
  • Monte-Carlo orthonormality of the basis over the unit disk.
  • Gradients vs central differences.
  • ZernikeSag through the standard AbstractTestAbstractSag battery, plus a geometric check that Noll Z4 shifts the focus of a paraboloid by the analytic amount.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.37%. Comparing base (9479771) to head (8455e8d).

Additional details and impacted files
@@                        Coverage Diff                        @@
##           fix/polygonal-aperture-bounds     #172      +/-   ##
=================================================================
+ Coverage                          99.35%   99.37%   +0.02%     
=================================================================
  Files                                116      120       +4     
  Lines                               6024     6248     +224     
=================================================================
+ Hits                                5985     6209     +224     
  Misses                                39       39              
Flag Coverage Δ
unittests 99.37% <100.00%> (+0.02%) ⬆️

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.

Comment thread optika/sags/_zernike.py Outdated
plt.legend()
"""

sag: None | AbstractSag = None

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.

Kinda unfortunate this is called sag as well, if you refer to it, it has stutter: sag.sag. Is there a better word like base or sag_base or something that captures it without using the exact same word?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — renamed the attribute to base, so it now reads sag.base. Done in 8455e8d.

Comment thread optika/sags/_zernike.py
plt.figure()
na.plt.plot(position.y, z, axis="y", label="perturbed")
na.plt.plot(position.y, z_base, axis="y", label="base")
plt.legend()

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.

I don't see any difference between the two curves in this plot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, and it was a real bug in the example: the perturbation is coma (Noll index 8), which varies as cos(phi) and is therefore identically zero along the y axis — exactly the slice the plot used, so the curves landed on top of each other. Switched the slice to vary along x, where the coma term is nonzero (~2.8 mm peak difference). Fixed in 8455e8d.

Comment thread optika/zernikes.py

def zernike(
j: int,
position: na.AbstractCartesian2dVectorArray,

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.

I feel like position should be first, is it standard for this function to put j first?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — there's no strong convention for j-first, and putting position first matches the other field-evaluation functions. Flipped both zernike() and zernike_gradient() to (position, j) and updated the call sites. Done in 8455e8d.

jacobdparker and others added 3 commits June 15, 2026 11:42
…adients

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename the ZernikeSag base-profile attribute from `sag` to `base` to avoid
  the `sag.sag` stutter when referring to it.
- Fix the ZernikeSag docstring example: the coma term (Noll index 8) varies as
  cos(phi) and is identically zero along the y axis, so the original y-slice
  showed the perturbed and base curves on top of each other. Slice along x
  instead, where the perturbation is visible.
- Put `position` first in the signatures of `zernike()` and
  `zernike_gradient()`, matching the convention of the other field-evaluation
  functions; update call sites and tests accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jacobdparker jacobdparker force-pushed the fix/polygonal-aperture-bounds branch from 1759e2b to 9479771 Compare June 15, 2026 17:58
@jacobdparker jacobdparker force-pushed the feature/zernike-sag branch from 4f496ec to 8455e8d Compare June 15, 2026 17:58
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