Glass Expert Optimization Example
This notebook demonstrates how to use the GlassExpert optimizer in Optiland to optimize an optical system with both continuous (radii, thicknesses) and categorical (glasses) variables.
A description of GlassExpert’s architecture can be found in the documentation (Developer’s Guide: Categorical Optimization with Glass Expert)
[1]:
import optiland.backend as be
from optiland import optic, optimization
from optiland.materials import glasses_selection
1. Define the Optical System
We’ll use a simple Cooke Triplet as the starting point for our optimization.
[2]:
class CookeTripletStartPoint(optic.Optic):
def __init__(self):
super().__init__()
self.surfaces.add(index=0, radius=be.inf, thickness=be.inf) # Object surface
self.surfaces.add(index=1, radius=25.0, thickness=2.5, material="N-BK7")
self.surfaces.add(index=2, radius=-150.0, thickness=7.5)
self.surfaces.add(
index=3, radius=-25.0, thickness=1.5, material="N-F2", is_stop=True
) # Stop surface
self.surfaces.add(index=4, radius=25.0, thickness=5.0)
self.surfaces.add(index=5, radius=150.0, thickness=2.5, material="N-BK7")
self.surfaces.add(index=6, radius=-25.0, thickness=40.0)
self.surfaces.add(index=7) # Image surface
self.set_aperture(aperture_type="EPD", value=10)
self.fields.set_type(field_type="angle")
self.fields.add(y=0)
self.fields.add(y=5)
self.fields.add(y=10)
self.wavelengths.add(value=0.4861) # F
self.wavelengths.add(value=0.5876, is_primary=True) # d
self.wavelengths.add(value=0.6563) # C
lens_system = CookeTripletStartPoint()
# Display initial lens
lens_system.draw(title="Initial Cooke Triplet")
lens_system.info()
╒════╤═════════════════╤═══════════╤══════════╤═════════════╤════════════╤═════════╤═════════════════╕
│ │ Type │ Comment │ Radius │ Thickness │ Material │ Conic │ Semi-aperture │
╞════╪═════════════════╪═══════════╪══════════╪═════════════╪════════════╪═════════╪═════════════════╡
│ 0 │ Planar │ │ inf │ inf │ Air │ 0 │ 7.04289 │
│ 1 │ Standard │ │ 25 │ 2.5 │ N-BK7 │ 0 │ 7.04289 │
│ 2 │ Standard │ │ -150 │ 7.5 │ Air │ 0 │ 6.5123 │
│ 3 │ Stop - Standard │ │ -25 │ 1.5 │ N-F2 │ 0 │ 3.92965 │
│ 4 │ Standard │ │ 25 │ 5 │ Air │ 0 │ 4.11651 │
│ 5 │ Standard │ │ 150 │ 2.5 │ N-BK7 │ 0 │ 5.63609 │
│ 6 │ Standard │ │ -25 │ 40 │ Air │ 0 │ 6.105 │
│ 7 │ Planar │ │ inf │ nan │ Air │ 0 │ 12.4368 │
╘════╧═════════════════╧═══════════╧══════════╧═════════════╧════════════╧═════════╧═════════════════╛
2. Define the Optimization Problem
We’ll set up operands to minimize RMS spot size and maintain a target focal length. Variables will include radii, thicknesses, and the materials of the lenses.
[3]:
problem = optimization.OptimizationProblem()
target_focal_length = 50.0
# Operand: Effective Focal Length (EFL)
problem.add_operand(
input_data={"optic": lens_system},
operand_type="f2",
target=target_focal_length,
weight=1.0,
)
# Operands: RMS Spot Size for each field
for Hx, Hy in lens_system.fields.get_field_coords():
problem.add_operand(
operand_type="rms_spot_size",
target=0.0,
weight=10.0,
input_data={
"optic": lens_system,
"surface_number": -1, # Image surface
"Hx": Hx,
"Hy": Hy,
"num_rays": 16, # Rays on one axis for 'uniform' distribution
"wavelength": lens_system.primary_wavelength,
"distribution": "uniform",
},
)
# Variables: Radii
problem.add_variable(lens_system, "radius", surface_number=1, min_val=10, max_val=100)
problem.add_variable(lens_system, "radius", surface_number=2, min_val=-200, max_val=-20)
problem.add_variable(lens_system, "radius", surface_number=3, min_val=-100, max_val=-10)
problem.add_variable(lens_system, "radius", surface_number=4, min_val=10, max_val=100)
problem.add_variable(lens_system, "radius", surface_number=5, min_val=20, max_val=200)
problem.add_variable(lens_system, "radius", surface_number=6, min_val=-100, max_val=-10)
# Variables: Thicknesses (air and glass)
problem.add_variable(
lens_system, "thickness", surface_number=1, min_val=1, max_val=5
) # Lens 1 thickness
problem.add_variable(
lens_system, "thickness", surface_number=2, min_val=1, max_val=15
) # Air space 1
problem.add_variable(
lens_system, "thickness", surface_number=3, min_val=1, max_val=5
) # Lens 2 thickness
problem.add_variable(
lens_system, "thickness", surface_number=4, min_val=1, max_val=15
) # Air space 2
problem.add_variable(
lens_system, "thickness", surface_number=5, min_val=1, max_val=5
) # Lens 3 thickness
problem.add_variable(
lens_system, "thickness", surface_number=6, min_val=30, max_val=60
) # Back focal length (approx)
# Variables: Materials (using Glass Expert)
available_glasses = glasses_selection(
lambda_min=0.4, lambda_max=0.7, catalogs=["schott", "ohara_common"]
)
problem.add_variable(
lens_system, "material", surface_number=1, glass_selection=available_glasses
)
problem.add_variable(
lens_system, "material", surface_number=3, glass_selection=available_glasses
)
problem.add_variable(
lens_system, "material", surface_number=5, glass_selection=available_glasses
)
problem.info()
╒════╤════════════════════════╤═══════════════════╕
│ │ Merit Function Value │ Improvement (%) │
╞════╪════════════════════════╪═══════════════════╡
│ 0 │ 14065.2 │ 0 │
╘════╧════════════════════════╧═══════════════════╛
╒════╤════════════════╤══════════╤══════════════╤══════════════╤══════════╤═════════╤═════════╤════════════════╕
│ │ Operand Type │ Target │ Min. Bound │ Max. Bound │ Weight │ Value │ Delta │ Contrib. [%] │
╞════╪════════════════╪══════════╪══════════════╪══════════════╪══════════╪═════════╪═════════╪════════════════╡
│ 0 │ f2 │ 50 │ │ │ 1 │ 161.994 │ 111.994 │ 89.18 │
│ 1 │ rms spot size │ 0 │ │ │ 10 │ 2.249 │ 2.249 │ 3.59 │
│ 2 │ rms spot size │ 0 │ │ │ 10 │ 2.251 │ 2.251 │ 3.6 │
│ 3 │ rms spot size │ 0 │ │ │ 10 │ 2.259 │ 2.259 │ 3.63 │
╘════╧════════════════╧══════════╧══════════════╧══════════════╧══════════╧═════════╧═════════╧════════════════╛
╒════╤═════════════════╤═══════════╤════════════════════╤══════════════╤══════════════╕
│ │ Variable Type │ Surface │ Value │ Min. Bound │ Max. Bound │
╞════╪═════════════════╪═══════════╪════════════════════╪══════════════╪══════════════╡
│ 0 │ radius │ 1 │ 25.0 │ 10 │ 100 │
│ 1 │ radius │ 2 │ -150.0 │ -200 │ -20 │
│ 2 │ radius │ 3 │ -25.0 │ -100 │ -10 │
│ 3 │ radius │ 4 │ 25.0 │ 10 │ 100 │
│ 4 │ radius │ 5 │ 150.0 │ 20 │ 200 │
│ 5 │ radius │ 6 │ -25.0 │ -100 │ -10 │
│ 6 │ thickness │ 1 │ 2.5 │ 1 │ 5 │
│ 7 │ thickness │ 2 │ 7.5 │ 1 │ 15 │
│ 8 │ thickness │ 3 │ 1.5000000000000002 │ 1 │ 5 │
│ 9 │ thickness │ 4 │ 5.0 │ 1 │ 15 │
│ 10 │ thickness │ 5 │ 2.5 │ 1 │ 5 │
│ 11 │ thickness │ 6 │ 40.0 │ 30 │ 60 │
│ 12 │ material │ 1 │ N-BK7 │ nan │ nan │
│ 13 │ material │ 3 │ N-F2 │ nan │ nan │
│ 14 │ material │ 5 │ N-BK7 │ nan │ nan │
╘════╧═════════════════╧═══════════╧════════════════════╧══════════════╧══════════════╛
3. Run the Glass Expert Optimization
[4]:
optimizer = optimization.GlassExpert(problem)
# Draw the starting lens
lens_system.draw(title="Starting lens")
print(f"Initial error function value: {problem.initial_value:.1f}")
# Run optimization
res = optimizer.run(
num_neighbours=6,
maxiter=100,
tol=1e-6,
verbose=True,
plot_glass_map=False,
)
# Display the optimized lens
lens_system.draw(title="Optimized lens")
lens_system.info()
Initial error function value: 14065.2
Initial glasses combination: ['N-BK7', 'N-F2', 'N-BK7']
----------------------------------------------------------------------
Global exploration
Selecting Material, Surface 1:
Trying N-SF11 as Material, Surface 1. Error function value: 3.69e-03
Trying BOROFLOAT33 as Material, Surface 1. Error function value: 0.21
Trying N-SK4 as Material, Surface 1. Error function value: 5.03e-03
Trying N-KZFS4 as Material, Surface 1. Error function value: 7.40e-03
Trying N-PK52A as Material, Surface 1. Error function value: 0.19
Trying N-SF2 as Material, Surface 1. Error function value: 4.47e-03
-> Selected N-SF11 as Material, Surface 1.
New combination: ['N-SF11', 'N-F2', 'N-BK7']
Best error function value: 3.69e-03 (-100%).
Selecting Material, Surface 3:
Trying N-SF11 as Material, Surface 3. Error function value: 4.53e-03
Trying BOROFLOAT33 as Material, Surface 3. Error function value: 4.20e-03
Trying N-SK4 as Material, Surface 3. Error function value: 3.96e-03
Trying N-KZFS4 as Material, Surface 3. Error function value: 3.91e-03
Trying N-PK52A as Material, Surface 3. Error function value: 4.42e-03
Trying N-SF2 as Material, Surface 3. Error function value: 3.67e-03
-> Selected N-SF2 as Material, Surface 3.
New combination: ['N-SF11', 'N-SF2', 'N-BK7']
Best error function value: 3.67e-03 (-1%).
Selecting Material, Surface 5:
Trying N-SF11 as Material, Surface 5. Error function value: 3.35e-03
Trying BOROFLOAT33 as Material, Surface 5. Error function value: 4.25e-03
Trying N-SK4 as Material, Surface 5. Error function value: 2.78e-03
Trying N-KZFS4 as Material, Surface 5. Error function value: 2.55e-03
Trying N-PK52A as Material, Surface 5. Error function value: 3.92e-03
Trying N-SF2 as Material, Surface 5. Error function value: 3.06e-03
-> Selected N-KZFS4 as Material, Surface 5.
New combination: ['N-SF11', 'N-SF2', 'N-KZFS4']
Best error function value: 2.55e-03 (-31%).
----------------------------------------------------------------------
Local exploration
Selecting Material, Surface 1:
Trying SF11 as Material, Surface 1. Error function value: 2.54e-03
Trying SFL6 as Material, Surface 1. Error function value: 3.99e-03
Trying N-SF6 as Material, Surface 1. Error function value: 3.99e-03
Trying N-SF6HT as Material, Surface 1. Error function value: 3.99e-03
Trying N-SF6HTultra as Material, Surface 1. Error function value: 3.99e-03
Trying SF56A as Material, Surface 1. Error function value: 2.54e-03
-> Selected SF56A as Material, Surface 1.
New combination: ['SF56A', 'N-SF2', 'N-KZFS4']
Best error function value: 2.54e-03 (-0%).
Selecting Material, Surface 3:
Trying SF2 as Material, Surface 3. Error function value: 2.54e-03
Trying N-SF19 as Material, Surface 3. Error function value: 2.59e-03
Trying N-KZFS8 as Material, Surface 3. Error function value: 4.07e-03
Trying N-LAF7 as Material, Surface 3. Error function value: 4.45e-03
Trying LAFN7 as Material, Surface 3. Error function value: 4.45e-03
Trying N-LASF45 as Material, Surface 3. Error function value: 3.18e-03
No better glass found, keeping N-SF2.
Combination: ['SF56A', 'N-SF2', 'N-KZFS4']
Best error function value: 2.54e-03 (0%).
Selecting Material, Surface 5:
Trying N-KZFS4HT as Material, Surface 5. Error function value: 2.54e-03
Trying KZFSN4 as Material, Surface 5. Error function value: 2.54e-03
Trying N-LAF2 as Material, Surface 5. Error function value: 1.85e-03
Trying N-BAF51 as Material, Surface 5. Error function value: 2.12e-03
Trying N-LAF33 as Material, Surface 5. Error function value: 1.91e-03
Trying N-BAF4 as Material, Surface 5. Error function value: 3.49e-03
-> Selected N-LAF2 as Material, Surface 5.
New combination: ['SF56A', 'N-SF2', 'N-LAF2']
Best error function value: 1.85e-03 (-27%).
╒════╤═════════════════╤═══════════╤═══════════╤═════════════╤════════════╤═════════╤═════════════════╕
│ │ Type │ Comment │ Radius │ Thickness │ Material │ Conic │ Semi-aperture │
╞════╪═════════════════╪═══════════╪═══════════╪═════════════╪════════════╪═════════╪═════════════════╡
│ 0 │ Planar │ │ inf │ inf │ Air │ 0 │ 7.30434 │
│ 1 │ Standard │ │ 33.3728 │ 3.85319 │ SF56A │ 0 │ 7.30434 │
│ 2 │ Standard │ │ -158.569 │ 7.54508 │ Air │ 0 │ 6.55284 │
│ 3 │ Stop - Standard │ │ -21.4607 │ 4.11158 │ N-SF2 │ 0 │ 3.68193 │
│ 4 │ Standard │ │ 24.3137 │ 5.5196 │ Air │ 0 │ 4.20475 │
│ 5 │ Standard │ │ 155.658 │ 3.1396 │ N-LAF2 │ 0 │ 5.97945 │
│ 6 │ Standard │ │ -20.5099 │ 40.5119 │ Air │ 0 │ 6.50684 │
│ 7 │ Planar │ │ inf │ nan │ Air │ 0 │ 8.81285 │
╘════╧═════════════════╧═══════════╧═══════════╧═════════════╧════════════╧═════════╧═════════════════╛