With the Glass material from #176, refractive (lens) systems can be built, but the stop root-finding problem doesn't converge for them once #169 and #170 are applied. There are two distinct root causes, and both sit in the _anchor_surface / involutory-stop logic those PRs introduce — so I wanted to align on the design before writing more code.
All repros use the Glass material from #176 and were run against fix/transmissive-stops (i.e. #169 + #170) with that material cherry-picked on top.
Root cause 1 — a refractive surface used as the stop (#170)
Modeling the stop as the optic itself (the pattern in prime_focus.ipynb, where the primary mirror is the pupil stop):
import astropy.units as u, named_arrays as na, optika
nd = 1.5168; f = 100*u.mm; R = 2*(nd-1)*f; t = 5*u.mm; r = 10*u.mm
front = optika.surfaces.Surface(name="front", sag=optika.sags.SphericalSag(R),
material=optika.materials.Glass.n_bk7(),
aperture=optika.apertures.CircularAperture(r), is_pupil_stop=True)
back = optika.surfaces.Surface(name="back", sag=optika.sags.SphericalSag(-R),
material=optika.materials.Vacuum(), aperture=optika.apertures.CircularAperture(r),
transformation=na.transformations.Cartesian3dTranslation(z=t))
sensor = optika.sensors.ImagingSensor(name="sensor", width_pixel=15*u.um,
axis_pixel=na.Cartesian2dVectorArray("dx","dy"), num_pixel=na.Cartesian2dVectorArray(64,64),
transformation=na.transformations.Cartesian3dTranslation(z=t+f), is_field_stop=True)
grid = optika.vectors.ObjectVectorArray(wavelength=500*u.nm,
field=na.Cartesian2dVectorLinearSpace(-1,1,axis=na.Cartesian2dVectorArray("fx","fy"),num=1,centers=True),
pupil=na.Cartesian2dVectorLinearSpace(-1,1,axis=na.Cartesian2dVectorArray("px","py"),num=5,centers=True))
optika.systems.SequentialSystem(surfaces=[front,back], sensor=sensor, grid_input=grid).rayfunction_stops
This converges on main but raises Could not solve ... 'front' and 'sensor' on the stack. A refractive stop is (correctly) non-involutory, so _calc_rayfunction_stops_only puts it in propagators = subsystem and the seed ray is positioned on the stop surface. Re-intercepting a curved surface from a point already on it lands on the far side of the sphere, so the residual is meaningless. This is fine for the flat transmissive stops #170 targets (FZP / transmission gratings), but breaks for a curved refractive stop.
Root cause 2 — _anchor_surface seeding for on-axis / nearby power (#169)
The realistic camera-lens layout — a flat aperture stop (iris) just ahead of a refractive singlet — fails even for the single on-axis field point:
stop = optika.surfaces.Surface(name="stop", aperture=optika.apertures.CircularAperture(8*u.mm), is_pupil_stop=True)
front = optika.surfaces.Surface(name="front", sag=optika.sags.SphericalSag(R), material=optika.materials.Glass.n_bk7(),
aperture=optika.apertures.CircularAperture(12*u.mm), transformation=na.transformations.Cartesian3dTranslation(z=2*u.mm))
# back + sensor as above, shifted; stop is 2 mm ahead of the lens
It diverges with invalid value encountered in sqrt → Max iterations exceeded. _anchor_surface returns the curved front (2 mm downstream) and the seed aims every pupil ray at its center, so off-axis pupil points get near-grazing seed directions and the Newton step pushes |d_xy| > 1, making zfunc = sqrt(1 - |d_xy|^2) undefined. Moving the iris farther from the lens confirms the mechanism:
| iris → lens distance |
result |
| 2 mm |
diverges |
| 50 mm |
converges |
| 200 mm |
converges |
The anchor-center seed is right for an off-axis feed/fold mirror (the case it was added for), but wrong for an on-axis powered surface close to the stop, where a near-collimated seed (≈ +z for an object at infinity) is what's needed.
Question
Since both of these are in the anchor/involutory logic you're reviewing on #169/#170, how would you like to handle refractive support? The directions I see:
- Seeding: when the object is at infinity (or the anchor is essentially on-axis from the stop), seed the free ray near-collimated rather than aimed at the anchor center.
- Curved non-involutory stop: seed the ray before the stop surface (so its intercept is well-defined) instead of on it, or otherwise apply its interaction without the spurious re-intercept.
Happy to implement either once we agree on the approach. The Glass material in #176 is independent of all this and stands on its own. A lens tutorial (Cooke triplet) is the eventual goal but is blocked on this.
🤖 Generated with Claude Code
With the
Glassmaterial from #176, refractive (lens) systems can be built, but the stop root-finding problem doesn't converge for them once #169 and #170 are applied. There are two distinct root causes, and both sit in the_anchor_surface/ involutory-stop logic those PRs introduce — so I wanted to align on the design before writing more code.All repros use the
Glassmaterial from #176 and were run againstfix/transmissive-stops(i.e. #169 + #170) with that material cherry-picked on top.Root cause 1 — a refractive surface used as the stop (#170)
Modeling the stop as the optic itself (the pattern in
prime_focus.ipynb, where the primary mirror is the pupil stop):This converges on
mainbut raisesCould not solve ... 'front' and 'sensor'on the stack. A refractive stop is (correctly) non-involutory, so_calc_rayfunction_stops_onlyputs it inpropagators = subsystemand the seed ray is positioned on the stop surface. Re-intercepting a curved surface from a point already on it lands on the far side of the sphere, so the residual is meaningless. This is fine for the flat transmissive stops #170 targets (FZP / transmission gratings), but breaks for a curved refractive stop.Root cause 2 —
_anchor_surfaceseeding for on-axis / nearby power (#169)The realistic camera-lens layout — a flat aperture stop (iris) just ahead of a refractive singlet — fails even for the single on-axis field point:
It diverges with
invalid value encountered in sqrt→Max iterations exceeded._anchor_surfacereturns the curvedfront(2 mm downstream) and the seed aims every pupil ray at its center, so off-axis pupil points get near-grazing seed directions and the Newton step pushes|d_xy| > 1, makingzfunc = sqrt(1 - |d_xy|^2)undefined. Moving the iris farther from the lens confirms the mechanism:The anchor-center seed is right for an off-axis feed/fold mirror (the case it was added for), but wrong for an on-axis powered surface close to the stop, where a near-collimated seed (≈ +z for an object at infinity) is what's needed.
Question
Since both of these are in the anchor/involutory logic you're reviewing on #169/#170, how would you like to handle refractive support? The directions I see:
Happy to implement either once we agree on the approach. The
Glassmaterial in #176 is independent of all this and stands on its own. A lens tutorial (Cooke triplet) is the eventual goal but is blocked on this.🤖 Generated with Claude Code