Skip to content

Commit 0e92b70

Browse files
committedFeb 12, 2025
Add robust Zernike evaluation
1 parent 8210fd3 commit 0e92b70

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed
 

‎galsim/zernike.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -721,17 +721,40 @@ def gradY(self):
721721
newCoef /= self.R_outer
722722
return Zernike(newCoef, R_outer=self.R_outer, R_inner=self.R_inner)
723723

724-
def __call__(self, x, y):
724+
def __call__(self, x, y, robust=False):
725725
"""Evaluate this Zernike polynomial series at Cartesian coordinates x and y.
726726
Synonym for `evalCartesian`.
727727
728+
Parameters:
729+
x: x-coordinate of evaluation points. Can be list-like.
730+
y: y-coordinate of evaluation points. Can be list-like.
731+
robust: If True, use a more robust method for evaluating the polynomial.
732+
This can sometimes be slower, but is usually more accurate,
733+
especially for large Noll indices. [default: False]
734+
Returns:
735+
Series evaluations as numpy array.
736+
"""
737+
if robust:
738+
return self.evalCartesianRobust(x, y)
739+
return self.evalCartesian(x, y)
740+
741+
def evalCartesianRobust(self, x, y):
742+
"""Evaluate this Zernike polynomial series at Cartesian coordinates x and y using a more
743+
robust method than the default `evalCartesian`.
744+
728745
Parameters:
729746
x: x-coordinate of evaluation points. Can be list-like.
730747
y: y-coordinate of evaluation points. Can be list-like.
748+
731749
Returns:
732750
Series evaluations as numpy array.
733751
"""
734-
return self.evalCartesian(x, y)
752+
x = np.asarray(x)
753+
y = np.asarray(y)
754+
rho = (x + 1j*y) / self.R_outer
755+
rhosq = np.abs(rho)**2
756+
ar = _noll_coef_array(len(self.coef)-1, self.R_inner/self.R_outer).dot(self.coef[1:])
757+
return horner2d(rhosq, rho, ar).real
735758

736759
def evalCartesian(self, x, y):
737760
"""Evaluate this Zernike polynomial series at Cartesian coordinates x and y.
@@ -743,7 +766,8 @@ def evalCartesian(self, x, y):
743766
Returns:
744767
Series evaluations as numpy array.
745768
"""
746-
return horner2d(x, y, self._coef_array_xy, dtype=float)
769+
ar = self._coef_array_xy
770+
return horner2d(x, y, ar, dtype=float)
747771

748772
def evalPolar(self, rho, theta):
749773
"""Evaluate this Zernike polynomial series at polar coordinates rho and theta.

‎tests/test_zernike.py

+25-12
Original file line numberDiff line numberDiff line change
@@ -1583,25 +1583,25 @@ def test_large_j(run_slow):
15831583
eps = R_inner/R_outer
15841584

15851585
test_vals = [
1586-
(10, 1e-12), # Z66
1587-
(20, 1e-12), # Z231
1588-
(40, 1e-9), # Z861
1586+
(10, 1e-12, 1e-12), # Z66
1587+
(20, 1e-12, 1e-12), # Z231
1588+
(40, 1e-9, 1e-12), # Z861
15891589
]
15901590
if run_slow:
15911591
test_vals += [
1592-
(60, 1e-6), # Z1891
1593-
(80, 1e-3), # Z3321
1594-
# (100, 10), # Z5151 # This one is catastrophic failure!
1592+
(60, 1e-6, 1e-12), # Z1891
1593+
(80, 1e-3, 1e-12), # Z3321
1594+
(100, None, 1e-11), # Z5151
1595+
(200, None, 1e-11), # Z20301
15951596
]
15961597

15971598
print()
1598-
for n, tol in test_vals:
1599+
for n, tol, tol2 in test_vals:
15991600
j = (n+1)*(n+2)//2
16001601
_, m = galsim.zernike.noll_to_zern(j)
16011602
print(f"Z{j} => (n, m) = ({n}, {m})")
16021603
assert n == abs(m)
1603-
coefs = np.zeros(j+1)
1604-
coefs[j] = 1.0
1604+
coefs = [0]*j+[1]
16051605
zk = Zernike(coefs, R_outer=R_outer, R_inner=R_inner)
16061606

16071607
def analytic_zk(x, y):
@@ -1613,10 +1613,23 @@ def analytic_zk(x, y):
16131613
else:
16141614
return r**n * np.sin(n*theta) * factor
16151615

1616+
analytic_vals = analytic_zk(x, y)
1617+
if n < 100:
1618+
np.testing.assert_allclose(
1619+
zk(x, y),
1620+
analytic_vals,
1621+
atol=tol, rtol=tol
1622+
)
1623+
1624+
robust_vals = zk.evalCartesianRobust(x, y)
16161625
np.testing.assert_allclose(
1617-
zk(x, y),
1618-
analytic_zk(x, y),
1619-
atol=tol, rtol=tol
1626+
robust_vals,
1627+
analytic_vals,
1628+
atol=tol2, rtol=tol2
1629+
)
1630+
np.testing.assert_equal(
1631+
robust_vals,
1632+
zk(x, y, robust=True)
16201633
)
16211634

16221635

0 commit comments

Comments
 (0)
Failed to load comments.