quartiq / rayopt

Python optics and lens design, raytracing
GNU Lesser General Public License v3.0
254 stars 50 forks source link

Implementation of Off-Axis Parabolic Mirrors #23

Open aglavic opened 6 years ago

aglavic commented 6 years ago

Hi, thanks for providing this great library.

I'm trying to test and optimize an optical system that uses off-axis parabolic mirrors under 90° to relay an image. I did manage to get an imaging system with 90° reflection to work and also to produce the correct parabolic shape with conic constant=-1, but the optical axis is always at the pole of the parabola.

Is there any way to implement this component in rayopt correctly? And if not, how hard would it be to adapt the API to allow for this shape to be used?

Kind regards,

Artur

jordens commented 6 years ago

That should work using offset. Do you have a code snippet to show what you are trying to do?

aglavic commented 6 years ago

Yes, with the offset I can bring the shape to the right position, but the active area is still at the pole. Here is an example:

s = system_from_yaml("""
description: three lens monochromatic focusing f/1 50mm
stop: 2
wavelengths: [550.0e-9]
object: {angle_deg: 9.0, pupil: {radius: 25}}
elements:
- {material: air, radius: 25}
- {material: mirror, radius: 25, roc: -50.0, offset: [0, -25, 150], angles: [-1.571, 0, 0], conic: -1}
- {material: glass/SCHOTT-SF|N-SF5, roc: 247.7, distance: 150, radius: 25, direction: [0, 1, 0]}
- {material: glass/SCHOTT-BK|N-BK7HT, roc: 72.1, distance: 3, radius: 25, direction: [0, 1, 0]}
- {material: air, roc: -83.2, distance: 12, radius: 25, direction: [0, 1, 0]}
- {material: air, distance: 2, radius: 22.0, direction: [0, 1, 0]}
- {material: glass/HIKARI-BaF|E-BAF11, roc: 50.8, distance: 3.8, radius: 25, direction: [0, 1, 0]}
- {material: glass/SCHOTT-SF|N-SF11, roc: -41.7, distance: 20, radius: 25, direction: [0, 1, 0]}
- {material: air, roc: -247.7, distance: 3, radius: 25, direction: [0, 1, 0]}
- {material: air, distance: 100, radius: 1, direction: [0, 1, 0]}
""")
s.object=FiniteConjugate(radius=25.0, pupil=dict(radius=25.0))
s.update()
jordens commented 6 years ago

I see. You'd want both an off-axis vertex and an off-vertex shape/aperture. That's not implemented yet. You can either subclass rayopt.elements.Spheroid and overload clip() and use that new class where you have that mirror. That should be smooth sailing if you know how you want that to look. Or you can work around it and make the parabolic mirror symmetric (larger than and covering the actual piece) and just place a simple air-air interface that clips the rays to the desired aperture in front of the parabolic mirror at the shape and position where the actual cut parabola is. Maybe adapt the offner relay example. Notice how during aiming the rays are clipped to hit the small convex mirror.

aglavic commented 6 years ago

Thanks for the quick reply.

Overwriting the clip method does not seem to have the expected effect, at least on the way the rays are drawn. I tried to use a simple translation, which did not seem to change anything:

def clip(self, y, u):
        return super(OffClipSpheroid, self).clip(y-array([10., 0., 0.])[newaxis,:], u)

I also tried to use some air interfaces with the actual geometry, but now it does not seem to trace correctly:

s = system_from_yaml("""
description: three lens monochromatic focusing f/1 50mm
stop: 7
wavelengths: [550.0e-9]
object: {angle_deg: 9.0, pupil: {radius: 25}}
elements:
- {material: air, radius: 25}
- {material: mirror, radius: 35, offset: [0, 0, 50], angles: [-0.7853981633974483, 0, 0]}
- {material: air, offset: [0, 100, 100], radius: 25, angles: [0.78539816339744828,0,0]}
- {material: mirror, radius: 35, offset: [0, 150, -100], angles: [0.7853981633974483, 0, 0]}
- {material: air, offset: [0, -50, 0], radius: 25, angles: [-1.5707963267948966,0,0]}
- {material: glass/SCHOTT-SF|N-SF5, roc: 247.7, distance: 2, radius: 25, direction: [0,0,-1]}
- {material: glass/SCHOTT-BK|N-BK7HT, roc: 72.1, distance: 3, radius: 25, direction: [0, 0, -1]}
- {material: air, roc: -83.2, distance: 12, radius: 25, direction: [0, 0, -1]}
- {material: air, distance: 2, radius: 22.0, direction: [0, 0, -1]}
- {material: glass/HIKARI-BaF|E-BAF11, roc: 50.8, distance: 3.8, radius: 25, direction: [0, 0, -1]}
- {material: glass/SCHOTT-SF|N-SF11, roc: -41.7, distance: 20, radius: 25, direction: [0, 0, -1]}
- {material: air, roc: -247.7, distance: 3, radius: 25, direction: [0, 0, -1]}
- {material: air, distance: 100, radius: 1, direction: [0, 0, -1]}
""")
s.object=FiniteConjugate(radius=25.0, pupil=dict(radius=25.0))
s[1]=Spheroid(material='mirror', radius=130, roc=-100.0, offset=[0,-50,0], angles=[0, 0, 0], conic=-1)
s[3]=Spheroid(material='mirror', radius=130, roc=-100.0, offset=[0,150,-100], angles=[-arctan2(100,150), 0, 0], conic=-1)
s.update()

Is there a way I can simply trace one straight ray to see where it is going wrong?

jordens commented 6 years ago

Sure. Something like this (tweaking your first snippet):

s = ro.system_from_yaml("""
description: three lens monochromatic focusing f/1 50mm
stop: 2
wavelengths: [550.0e-9]
object: {angle_deg: 9.0, pupil: {radius: 25}}
elements:
- {material: air, radius: 25}
- {material: mirror, radius: 100, roc: -50.0, offset: [0, -25, 150], angles: [-1.1, 0, 0], conic: -1}
- {material: glass/SCHOTT-SF|N-SF5, roc: 247.7, distance: 150, radius: 25, direction: [0, 1, 0]}
- {material: glass/SCHOTT-BK|N-BK7HT, roc: 72.1, distance: 3, radius: 25, direction: [0, 1, 0]}
- {material: air, roc: -83.2, distance: 12, radius: 25, direction: [0, 1, 0]}
- {material: air, distance: 2, radius: 22.0, direction: [0, 1, 0]}
- {material: glass/HIKARI-BaF|E-BAF11, roc: 50.8, distance: 3.8, radius: 25, direction: [0, 1, 0]}
- {material: glass/SCHOTT-SF|N-SF11, roc: -41.7, distance: 20, radius: 25, direction: [0, 1, 0]}
- {material: air, roc: -247.7, distance: 3, radius: 25, direction: [0, 1, 0]}
- {material: air, distance: 100, radius: 1, direction: [0, 1, 0]}
""")
s.object = ro.FiniteConjugate(radius=25.0, pupil=dict(radius=25.0))
s.update()

g = ro.GeometricTrace(s)
g.rays_given(y=[(0., 0)], u=[(0., 0)])
g.propagate()
fig, ax = plt.subplots(figsize=(15, 15))
s.plot(ax)
g.plot(ax)

print(g)
ray 0
 # T         n   track z  rel path  height x  height y  height z   angle x   angle y   angle z
 0 S         1         0         0         0         0         0         0         0         1
 1 S         1     152.1     29.73         0      37.8    -14.29         0  -0.02949   -0.9996
 2 S     1.677     302.1     12.43         0     11.89    0.2854         0    0.1759    0.9844
 3 S     1.519     305.1     15.94         0     12.57     1.104         0    0.2138    0.9769
 4 S         1     317.1     18.85         0     14.67    -1.303         0    0.2332    0.9724
 5 S         1     319.1     20.25         0     15.46         0         0    0.2332    0.9724
 6 S      1.67     322.9      23.4         0     17.08     2.958         0 -0.009322         1
 7 S     1.791     342.9     25.84         0     16.96    -3.603         0   0.02142    0.9998
 8 S         1     345.9     33.61         0     17.09   -0.5899         0   -0.0163    0.9999
 9 S         1     445.9     34.24         0     15.45         0         0   -0.0163    0.9999

image

aglavic commented 6 years ago

Thanks, that helped a lot. So now it starts of correctly, but at the second mirror something strange happens that looks like the intersection does not find the right root: image

Here is the code:

s = system_from_yaml("""
description: three lens monochromatic focusing f/1 50mm
stop: 3
wavelengths: [550.0e-9]
object: {angle_deg: 0.01, pupil: {radius: 25}}
elements:
- {material: air, radius: 25}
- {material: mirror, radius: 35, offset: [0, 0, 50], angles: [-0.7853981633974483, 0, 0]}
- {material: air, offset: [0, 100, 100], radius: 25, angles: [0,0,0]}
- {material: air, offset: [0, 100, 0], radius: 25}
- {material: mirror, radius: 35, offset: [0, 150, -100], angles: [0.7853981633974483, 0, 0]}
- {material: air, offset: [0, -50, 40], radius: 25, angles: [-1.5707963267948966,0,0]}
- {material: air, offset: [0, 0, -20], radius: 25, angles: [0,0,0]}
- {material: air, distance: 0}
""")
s.object=FiniteConjugate(radius=25.0, pupil=dict(radius=25.0))
s[1]=Spheroid(material='mirror', radius=130, roc=-100.0, offset=[0,-50,0], angles=[0, 0, 0], conic=-1)
s[2].angles=[arctan2(100,100), 0, 0]
s[4]=Spheroid(material='mirror', radius=130, roc=-100.0, offset=[0,80,-100], angles=[-arctan2(100,80), 0, 0], conic=-1)
s[5].angles=[arctan2(50,40), 0, 0]
s.update()

g = GeometricTrace(s)
g.rays_given(y=[(0., 0.), (0.,0.), (0.,-0.)], u=[(0., 0.), (0.,0.1), (0.,-0.1)])
g.propagate()
fig, ax = plt.subplots(figsize=(15, 15))
s.plot(ax)
g.plot(ax)

Any Idea what could cause this?

jordens commented 6 years ago

Hmm. Not immediately. That does look like a bug.