Tutorial 4f: Three-Mirror Anastigmat (TMA)

This tutorial aims at demonstrating Optiland’s capabilities in reflective systems design using freeform mirrors.

Setup the telescope

The starting telescope is voluntarily defocused, it will be optimized after.

[1]:
import numpy as np

from optiland import analysis, optic, optimization

focal_length = 100  # [mm]

lens = optic.Optic(name="TMA")
lens.set_aperture(aperture_type="EPD", value=10)
lens.fields.set_type(field_type="angle")
lens.fields.add(y=0)
lens.fields.add(y=+1.5)
lens.fields.add(y=-1.5)
lens.wavelengths.add(value=0.486)
lens.wavelengths.add(value=0.587, is_primary=True)
lens.wavelengths.add(value=0.656)

# The telescope is made of three freeform surfaces (Zernike)
lens.surfaces.add(index=0, radius=np.inf, thickness=np.inf)
lens.surfaces.add(
    index=1,
    radius=-100,
    thickness=-20,
    conic=0,
    material="mirror",
    rx=np.radians(-15.0),
    is_stop=True,
    surface_type="zernike",
    coefficients=[],
)
lens.surfaces.add(
    index=2,
    radius=-100,
    thickness=+20,
    conic=0,
    material="mirror",
    rx=np.radians(-10.0),
    dy=-11.5,
    surface_type="zernike",
    coefficients=[],
)
lens.surfaces.add(
    index=3,
    radius=-100,
    thickness=-22,
    conic=0,
    material="mirror",
    rx=np.radians(-1.0),
    dy=-15,
    surface_type="zernike",
    coefficients=[],
)
lens.surfaces.add(index=4, dy=-19.3)

lens.update_paraxial()
lens.draw(title=lens.name)
lens.info()
# lens.draw3D()
spot = analysis.SpotDiagram(lens)
spot.view()
../_images/examples_Tutorial_4f_Three_Mirror_Anastigmat_1_0.png
╒════╤═══════════════════════════╤══════════╤═════════════╤════════════╤═════════╤═════════════════╕
│    │ Type                      │   Radius │   Thickness │ Material   │   Conic │   Semi-aperture │
╞════╪═══════════════════════════╪══════════╪═════════════╪════════════╪═════════╪═════════════════╡
│  0 │ Planar                    │      inf │         inf │ Air        │       0 │         5       │
│  1 │ Stop - Zernike Polynomial │     -100 │         -20 │ Mirror     │       0 │         5       │
│  2 │ Zernike Polynomial        │     -100 │          20 │ Mirror     │       0 │         3.52372 │
│  3 │ Zernike Polynomial        │     -100 │         -22 │ Mirror     │       0 │        12.6569  │
│  4 │ Planar                    │      inf │         nan │ Air        │       0 │         3.9344  │
╘════╧═══════════════════════════╧══════════╧═════════════╧════════════╧═════════╧═════════════════╛
../_images/examples_Tutorial_4f_Three_Mirror_Anastigmat_1_2.png

Optimization

The optimization variables are the radii, thicknesses, decenters & tilts, and the freeform coefficients.

We define contraints on the mirrors’ decenters to prevent vignetting.

Variables

[2]:
problem = optimization.OptimizationProblem()

# radii
problem.add_variable(lens, "radius", surface_number=1, min_val=-1000, max_val=1000)
problem.add_variable(lens, "radius", surface_number=2, min_val=-1000, max_val=1000)
problem.add_variable(lens, "radius", surface_number=3, min_val=-1000, max_val=1000)

# thicknesses
problem.add_variable(lens, "thickness", surface_number=1, min_val=-35, max_val=-15)
problem.add_variable(lens, "thickness", surface_number=2, min_val=+15, max_val=+35)
problem.add_variable(lens, "thickness", surface_number=3, min_val=-35, max_val=-15)

# decenters
problem.add_variable(
    lens,
    "decenter",
    axis="y",
    surface_number=2,
    min_val=-15,
    max_val=-10,
)
problem.add_variable(
    lens,
    "decenter",
    axis="y",
    surface_number=3,
    min_val=-20,
    max_val=-11,
)
problem.add_variable(
    lens,
    "decenter",
    axis="y",
    surface_number=4,
    min_val=-28,
    max_val=-22,
)

# tilts
problem.add_variable(
    lens,
    "tilt",
    axis="x",
    surface_number=1,
    min_val=np.radians(-20.0),
    max_val=np.radians(-12.0),
)
problem.add_variable(
    lens,
    "tilt",
    axis="x",
    surface_number=2,
    min_val=np.radians(-15.0),
    max_val=np.radians(-08.0),
)
problem.add_variable(
    lens,
    "tilt",
    axis="x",
    surface_number=3,
    min_val=np.radians(-10.0),
    max_val=np.radians(+10.0),
)

# conic constants
problem.add_variable(lens, "conic", surface_number=1, min_val=-10, max_val=10)
problem.add_variable(lens, "conic", surface_number=2, min_val=-10, max_val=10)
problem.add_variable(lens, "conic", surface_number=3, min_val=-10, max_val=10)

# Freeform coefficients
for s in range(1, 4):
    for i in range(4):
        problem.add_variable(
            lens,
            "zernike_coeff",
            surface_number=s,
            coeff_index=i,
            min_val=-1,
            max_val=1,
        )

Operands

[ ]:
# Center M2 on its chief ray
input_data = {
    "optic": lens,
    "surface_number": 2,
    "Hx": 0,
    "Hy": 0,
    "Px": 0,
    "Py": 0,
    "wavelength": lens.wavelengths.primary_wavelength.value,
}
problem.add_operand(
    operand_type="real_y_intercept_lcs",
    target=0.0,
    weight=1,
    input_data=input_data,
)

# Center M3 on its chief ray
input_data = {
    "optic": lens,
    "surface_number": 3,
    "Hx": 0,
    "Hy": 0,
    "Px": 0,
    "Py": 0,
    "wavelength": lens.wavelengths.primary_wavelength.value,
}
problem.add_operand(
    operand_type="real_y_intercept_lcs",
    target=0.0,
    weight=1,
    input_data=input_data,
)

# Image surface - Real ray heights operands, in the lcs of the image surface
# Center the image surface on its chief ray
input_data = {
    "optic": lens,
    "surface_number": 4,
    "Hx": 0,
    "Hy": 0,
    "Px": 0,
    "Py": 0,
    "wavelength": lens.wavelengths.primary_wavelength.value,
}
problem.add_operand(
    operand_type="real_y_intercept_lcs",
    target=focal_length * np.tan(np.deg2rad(lens.fields.y_fields[0])),
    weight=1,
    input_data=input_data,
)
input_data = {
    "optic": lens,
    "surface_number": 4,
    "Hx": 0,
    "Hy": 1,
    "Px": 0,
    "Py": 0,
    "wavelength": lens.wavelengths.primary_wavelength.value,
}
problem.add_operand(
    operand_type="real_y_intercept_lcs",
    target=focal_length * np.tan(np.deg2rad(lens.fields.y_fields[1])),
    weight=1,
    input_data=input_data,
)
input_data = {
    "optic": lens,
    "surface_number": 4,
    "Hx": 0,
    "Hy": -1,
    "Px": 0,
    "Py": 0,
    "wavelength": lens.wavelengths.primary_wavelength.value,
}
problem.add_operand(
    operand_type="real_y_intercept_lcs",
    target=focal_length * np.tan(np.deg2rad(lens.fields.y_fields[2])),
    weight=1,
    input_data=input_data,
)

# RMS spot size - let's minimize the spot size for each field at the primary wavelength.
# We choose a 'uniform' distribution, so the number of rays actually means the rays on
# one axis. Therefore we trace ≈16^2 rays here.
for field in lens.fields.get_field_coords():
    input_data = {
        "optic": lens,
        "surface_number": 4,
        "Hx": field[0],
        "Hy": field[1],
        "num_rays": 16,
        "wavelength": 0.587,
        "distribution": "uniform",
    }
    problem.add_operand(
        operand_type="rms_spot_size",
        target=0.0,
        weight=10,
        input_data=input_data,
    )

problem.info()
╒════╤════════════════════════╤═══════════════════╕
│    │   Merit Function Value │   Improvement (%) │
╞════╪════════════════════════╪═══════════════════╡
│  0 │                6.19557 │                 0 │
╘════╧════════════════════════╧═══════════════════╛
╒════╤══════════════════════╤══════════╤═════════════╤═════════════╤══════════╤═════════╤═════════╤════════════════╕
│    │ Operand Type         │   Target │ Min Bound   │ Max Bound   │   Weight │   Value │   Delta │   Contrib. [%] │
╞════╪══════════════════════╪══════════╪═════════════╪═════════════╪══════════╪═════════╪═════════╪════════════════╡
│  0 │ real y intercept lcs │    0     │             │             │        1 │  -0.043 │  -0.043 │           0.03 │
│  1 │ real y intercept lcs │    0     │             │             │        1 │  -0.086 │  -0.086 │           0.12 │
│  2 │ real y intercept lcs │    0     │             │             │        1 │  -0.443 │  -0.443 │           3.17 │
│  3 │ real y intercept lcs │    2.619 │             │             │        1 │   1.22  │  -1.399 │          31.59 │
│  4 │ real y intercept lcs │   -2.619 │             │             │        1 │  -2.124 │   0.494 │           3.94 │
│  5 │ rms spot size        │    0     │             │             │       10 │   0.107 │   0.107 │          18.62 │
│  6 │ rms spot size        │    0     │             │             │       10 │   0.077 │   0.077 │           9.58 │
│  7 │ rms spot size        │    0     │             │             │       10 │   0.143 │   0.143 │          32.95 │
╘════╧══════════════════════╧══════════╧═════════════╧═════════════╧══════════╧═════════╧═════════╧════════════════╛
╒════╤═════════════════╤═══════════╤══════════════╤══════════════╤══════════════╕
│    │ Variable Type   │   Surface │        Value │   Min. Bound │   Max. Bound │
╞════╪═════════════════╪═══════════╪══════════════╪══════════════╪══════════════╡
│  0 │ radius          │         1 │ -100         │ -1000        │  1000        │
│  1 │ radius          │         2 │ -100         │ -1000        │  1000        │
│  2 │ radius          │         3 │ -100         │ -1000        │  1000        │
│  3 │ thickness       │         1 │  -20         │   -35        │   -15        │
│  4 │ thickness       │         2 │   20         │    15        │    35        │
│  5 │ thickness       │         3 │  -22         │   -35        │   -15        │
│  6 │ decenter        │         2 │  -11.5       │   -15        │   -10        │
│  7 │ decenter        │         3 │  -15         │   -20        │   -11        │
│  8 │ decenter        │         4 │  -19.3       │   -28        │   -22        │
│  9 │ tilt            │         1 │   -0.261799  │    -0.349066 │    -0.20944  │
│ 10 │ tilt            │         2 │   -0.174533  │    -0.261799 │    -0.139626 │
│ 11 │ tilt            │         3 │   -0.0174533 │    -0.174533 │     0.174533 │
│ 12 │ conic           │         1 │    0         │   -10        │    10        │
│ 13 │ conic           │         2 │    0         │   -10        │    10        │
│ 14 │ conic           │         3 │    0         │   -10        │    10        │
│ 15 │ zernike_coeff   │         1 │    0         │    -1        │     1        │
│ 16 │ zernike_coeff   │         1 │    0         │    -1        │     1        │
│ 17 │ zernike_coeff   │         1 │    0         │    -1        │     1        │
│ 18 │ zernike_coeff   │         1 │    0         │    -1        │     1        │
│ 19 │ zernike_coeff   │         2 │    0         │    -1        │     1        │
│ 20 │ zernike_coeff   │         2 │    0         │    -1        │     1        │
│ 21 │ zernike_coeff   │         2 │    0         │    -1        │     1        │
│ 22 │ zernike_coeff   │         2 │    0         │    -1        │     1        │
│ 23 │ zernike_coeff   │         3 │    0         │    -1        │     1        │
│ 24 │ zernike_coeff   │         3 │    0         │    -1        │     1        │
│ 25 │ zernike_coeff   │         3 │    0         │    -1        │     1        │
│ 26 │ zernike_coeff   │         3 │    0         │    -1        │     1        │
╘════╧═════════════════╧═══════════╧══════════════╧══════════════╧══════════════╛

Optimization

[4]:
# Local optimizer
optimizer = optimization.OptimizerGeneric(problem)
res = optimizer.optimize(tol=1e-9)

# Global optimizer
# optimizer = optimization.DifferentialEvolution(problem)
# res = optimizer.optimize(maxiter=100, workers=-1)

lens.info()
lens.draw(title=f"Optimized {lens.name}")
problem.info()
╒════╤═══════════════════════════╤══════════╤═════════════╤════════════╤══════════════╤═════════════════╕
│    │ Type                      │   Radius │   Thickness │ Material   │        Conic │   Semi-aperture │
╞════╪═══════════════════════════╪══════════╪═════════════╪════════════╪══════════════╪═════════════════╡
│  0 │ Planar                    │  inf     │    inf      │ Air        │  0           │         5       │
│  1 │ Stop - Zernike Polynomial │ -126.089 │    -30.3923 │ Mirror     │ -0.0117211   │         5       │
│  2 │ Zernike Polynomial        │ -101.251 │     25.9284 │ Mirror     │  2.29e-05    │         3.38547 │
│  3 │ Zernike Polynomial        │ -152.841 │    -28.926  │ Mirror     │ -0.000157152 │        15.4754  │
│  4 │ Planar                    │  inf     │    nan      │ Air        │  0           │        11.6735  │
╘════╧═══════════════════════════╧══════════╧═════════════╧════════════╧══════════════╧═════════════════╛
../_images/examples_Tutorial_4f_Three_Mirror_Anastigmat_8_1.png
╒════╤════════════════════════╤═══════════════════╕
│    │   Merit Function Value │   Improvement (%) │
╞════╪════════════════════════╪═══════════════════╡
│  0 │               0.154056 │           97.5134 │
╘════╧════════════════════════╧═══════════════════╛
╒════╤══════════════════════╤══════════╤═════════════╤═════════════╤══════════╤═════════╤═════════╤════════════════╕
│    │ Operand Type         │   Target │ Min Bound   │ Max Bound   │   Weight │   Value │   Delta │   Contrib. [%] │
╞════╪══════════════════════╪══════════╪═════════════╪═════════════╪══════════╪═════════╪═════════╪════════════════╡
│  0 │ real y intercept lcs │    0     │             │             │        1 │   0.033 │   0.033 │           0.72 │
│  1 │ real y intercept lcs │    0     │             │             │        1 │  -0.026 │  -0.026 │           0.45 │
│  2 │ real y intercept lcs │    0     │             │             │        1 │   0.013 │   0.013 │           0.12 │
│  3 │ real y intercept lcs │    2.619 │             │             │        1 │   2.541 │  -0.078 │           3.91 │
│  4 │ real y intercept lcs │   -2.619 │             │             │        1 │  -2.544 │   0.074 │           3.6  │
│  5 │ rms spot size        │    0     │             │             │       10 │   0.015 │   0.015 │          14.21 │
│  6 │ rms spot size        │    0     │             │             │       10 │   0.023 │   0.023 │          34.68 │
│  7 │ rms spot size        │    0     │             │             │       10 │   0.026 │   0.026 │          42.33 │
╘════╧══════════════════════╧══════════╧═════════════╧═════════════╧══════════╧═════════╧═════════╧════════════════╛
╒════╤═════════════════╤═══════════╤════════════════╤══════════════╤══════════════╕
│    │ Variable Type   │   Surface │          Value │   Min. Bound │   Max. Bound │
╞════╪═════════════════╪═══════════╪════════════════╪══════════════╪══════════════╡
│  0 │ radius          │         1 │ -126.089       │ -1000        │  1000        │
│  1 │ radius          │         2 │ -101.251       │ -1000        │  1000        │
│  2 │ radius          │         3 │ -152.841       │ -1000        │  1000        │
│  3 │ thickness       │         1 │  -30.3923      │   -35        │   -15        │
│  4 │ thickness       │         2 │   25.9284      │    15        │    35        │
│  5 │ thickness       │         3 │  -28.926       │   -35        │   -15        │
│  6 │ decenter        │         2 │  -11.4549      │   -15        │   -10        │
│  7 │ decenter        │         3 │  -15.1013      │   -20        │   -11        │
│  8 │ decenter        │         4 │  -22.0291      │   -28        │   -22        │
│  9 │ tilt            │         1 │   -0.242949    │    -0.349066 │    -0.20944  │
│ 10 │ tilt            │         2 │   -0.156667    │    -0.261799 │    -0.139626 │
│ 11 │ tilt            │         3 │   -0.0435276   │    -0.174533 │     0.174533 │
│ 12 │ conic           │         1 │   -0.0117211   │   -10        │    10        │
│ 13 │ conic           │         2 │    2.29e-05    │   -10        │    10        │
│ 14 │ conic           │         3 │   -0.000157152 │   -10        │    10        │
│ 15 │ zernike_coeff   │         1 │   -0.48074     │    -1        │     1        │
│ 16 │ zernike_coeff   │         1 │    0.347677    │    -1        │     1        │
│ 17 │ zernike_coeff   │         1 │    0.00173589  │    -1        │     1        │
│ 18 │ zernike_coeff   │         1 │   -0.0131347   │    -1        │     1        │
│ 19 │ zernike_coeff   │         2 │    0.0629088   │    -1        │     1        │
│ 20 │ zernike_coeff   │         2 │    0.178917    │    -1        │     1        │
│ 21 │ zernike_coeff   │         2 │   -0.0327047   │    -1        │     1        │
│ 22 │ zernike_coeff   │         2 │   -0.0270168   │    -1        │     1        │
│ 23 │ zernike_coeff   │         3 │    0.0304076   │    -1        │     1        │
│ 24 │ zernike_coeff   │         3 │   -0.0353778   │    -1        │     1        │
│ 25 │ zernike_coeff   │         3 │    0.00636789  │    -1        │     1        │
│ 26 │ zernike_coeff   │         3 │   -0.0956292   │    -1        │     1        │
╘════╧═════════════════╧═══════════╧════════════════╧══════════════╧══════════════╛
[7]:
spot = analysis.SpotDiagram(lens)
spot.view()
../_images/examples_Tutorial_4f_Three_Mirror_Anastigmat_9_0.png
[8]:
from optiland.mtf import GeometricMTF

geo_mtf = GeometricMTF(lens)
geo_mtf.view()
../_images/examples_Tutorial_4f_Three_Mirror_Anastigmat_10_0.png