Skip to content

Commit 02ba893

Browse files
fixed error in Zernike code
1 parent 29a1109 commit 02ba893

2 files changed

Lines changed: 77 additions & 3 deletions

File tree

OpticalWavePropSim.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,18 @@ def noll_to_nm(j):
470470
m = -m
471471
return n, m
472472

473-
def zernike(j, r, theta):
474-
n, m_val = noll_to_nm(j)
473+
def zernike(j, r, theta, z_map=None):
474+
"""
475+
j: Zernike index
476+
r, theta: radial and azimuthal coordinates
477+
z_map: optional dictionary mapping j to (n, m)
478+
"""
479+
# Use mapping if provided, otherwise default to Noll
480+
if z_map is not None and j in z_map:
481+
n, m_val = z_map[j]
482+
else:
483+
n, m_val = noll_to_nm(j)
484+
475485
m = abs(m_val)
476486
# Use a small epsilon for the boundary to avoid floating point clipping
477487
r_safe = np.where(r <= 1.000001, r, 0.0)
@@ -481,4 +491,4 @@ def zernike(j, r, theta):
481491
elif j % 2 == 0:
482492
return np.sqrt(2 * (n + 1)) * _zrf(n, m, r_safe) * np.cos(m * theta)
483493
else:
484-
return np.sqrt(2 * (n + 1)) * _zrf(n, m, r_safe) * np.sin(m * theta)
494+
return np.sqrt(2 * (n + 1)) * _zrf(n, m, r_safe) * np.sin(m * theta)

examples/Chapter5/zernikeTest.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# zernikeTest.py
2+
3+
import numpy as np
4+
from scipy.special import gamma
5+
6+
def _zrf(n, m, r):
7+
R = np.zeros_like(r, dtype=float)
8+
# n-m must be even and non-negative
9+
if (n - m) % 2 != 0 or n < m:
10+
return R
11+
12+
for s in range(int((n - m) / 2) + 1):
13+
num = (-1)**s * gamma(n - s + 1)
14+
denom = (gamma(s + 1) * gamma((n + m) / 2 - s + 1) * gamma((n - m) / 2 - s + 1))
15+
R += num / denom * r**(n - 2 * s)
16+
return R
17+
18+
def noll_to_nm(j):
19+
"""Corrected Noll to (n, m) mapping."""
20+
n = 0
21+
j1 = j
22+
while j1 > (n + 1):
23+
j1 -= (n + 1)
24+
n += 1
25+
26+
# The number of modes up to radial degree n-1 is n(n+1)/2
27+
# But Noll uses a specific pyramid. Let's use the explicit Noll formula:
28+
n = int((np.sqrt(8 * j - 7) - 1) / 2)
29+
m_abs = j - n * (n + 1) // 2 - 1
30+
31+
# Correcting parity and m value
32+
if n % 2 == 0:
33+
m = 2 * int(m_abs / 2)
34+
else:
35+
m = 2 * int((m_abs + 1) / 2) - 1
36+
37+
if j % 2 > 0:
38+
m = -m
39+
return n, m
40+
41+
def zernike(j, r, theta):
42+
n, m_val = noll_to_nm(j)
43+
m = abs(m_val)
44+
# Use a small epsilon for the boundary to avoid floating point clipping
45+
r_safe = np.where(r <= 1.000001, r, 0.0)
46+
47+
if m == 0:
48+
return np.sqrt(n + 1) * _zrf(n, 0, r_safe)
49+
elif j % 2 == 0:
50+
return np.sqrt(2 * (n + 1)) * _zrf(n, m, r_safe) * np.cos(m * theta)
51+
else:
52+
return np.sqrt(2 * (n + 1)) * _zrf(n, m, r_safe) * np.sin(m * theta)
53+
54+
# --- RE-RUN VALIDATION ---
55+
print("--- Revised Zernike Validation ---")
56+
n4, m4 = noll_to_nm(4)
57+
print(f"j=4 maps to n={n4}, m={m4}")
58+
val_4 = zernike(4, np.array([1.0]), np.array([0.0]))[0]
59+
print(f"j=4 (Defocus) at r=1.0: {val_4:.5f} (Target: 1.73205)")
60+
61+
n100, m100 = noll_to_nm(100)
62+
print(f"j=100 maps to n={n100}, m={m100}")
63+
val_100 = zernike(100, np.array([1.0]), np.array([0.0]))[0]
64+
print(f"j=100 at r=1.0: {val_100:.5f} (Target: ~5.099)")

0 commit comments

Comments
 (0)