Global Optimization (Differential Evolution)

The following global optimizers are implemented in Optiland: 1. Differential Evolution 2. Dual Annealing 3. SHGO 4. Basin-hopping

These optimizers wrap the scipy.optimize implementations.

[1]:
import numpy as np

from optiland import optic, optimization

Define a starting lens:

[2]:
lens = optic.Optic()

# add surfaces
lens.surfaces.add(index=0, radius=np.inf, thickness=np.inf)
lens.surfaces.add(index=1, radius=40, thickness=5, material="SK16", is_stop=True)
lens.surfaces.add(index=2, radius=-100, thickness=50)
lens.surfaces.add(index=3)

# set aperture
lens.set_aperture(aperture_type="EPD", value=20)

# set fields
lens.fields.set_type(field_type="angle")
lens.fields.add(y=0)
lens.fields.add(y=5)

# set wavelength
lens.wavelengths.add(value=0.55, is_primary=True)

lens.draw()
../../_images/gallery_optimization_global_4_0.png

Define optimization problem:

[3]:
problem = optimization.OptimizationProblem()

Add operands (targets for optimization):

[ ]:
"""
Add a focal length operand and wavefront error operands for all fields.

Use Gaussian quadrature distribution for the rays (see distribution documentation for
more information).
"""

# focal length target
input_data = {"optic": lens}
problem.add_operand(operand_type="f2", target=60, weight=1, input_data=input_data)

# wavefront error target
for field in lens.fields.get_field_coords():
    input_data = {
        "optic": lens,
        "Hx": field[0],
        "Hy": field[1],
        "num_rays": 3,
        "wavelength": 0.55,
        "distribution": "gaussian_quad",
    }
    problem.add_operand(
        operand_type="OPD_difference",
        target=0,
        weight=1,
        input_data=input_data,
    )

Define variables - let both radii of curvature vary. We will use differential evolution, which requires bounds for all variables.

[5]:
problem.add_variable(lens, "radius", surface_number=1, min_val=-500, max_val=500)
problem.add_variable(lens, "radius", surface_number=2, min_val=-500, max_val=500)

Let thicknesses to image plane vary:

[6]:
problem.add_variable(lens, "thickness", surface_number=2, min_val=30, max_val=100)

Check initial merit function value and system properties:

[7]:
problem.info()
╒════╤════════════════════════╤═══════════════════╕
│    │   Merit Function Value │   Improvement (%) │
╞════╪════════════════════════╪═══════════════════╡
│  0 │                3716.81 │                 0 │
╘════╧════════════════════════╧═══════════════════╛
╒════╤════════════════╤══════════╤══════════╤═════════╤══════════╤════════════════════╕
│    │ Operand Type   │   Target │   Weight │   Value │    Delta │   Contribution (%) │
╞════╪════════════════╪══════════╪══════════╪═════════╪══════════╪════════════════════╡
│  0 │ f2             │       60 │        1 │ 46.5275 │ -13.4725 │            4.88345 │
│  1 │ OPD difference │        0 │        1 │ 56.0052 │  56.0052 │           84.3891  │
│  2 │ OPD difference │        0 │        1 │ 19.968  │  19.968  │           10.7275  │
╘════╧════════════════╧══════════╧══════════╧═════════╧══════════╧════════════════════╛
╒════╤═════════════════╤═══════════╤═════════╤══════════════╤══════════════╕
│    │ Variable Type   │   Surface │   Value │   Min. Bound │   Max. Bound │
╞════╪═════════════════╪═══════════╪═════════╪══════════════╪══════════════╡
│  0 │ radius          │         1 │      40 │         -500 │          500 │
│  1 │ radius          │         2 │    -100 │         -500 │          500 │
│  2 │ thickness       │         2 │      50 │           30 │          100 │
╘════╧═════════════════╧═══════════╧═════════╧══════════════╧══════════════╛

Define optimizer:

[8]:
optimizer = optimization.DifferentialEvolution(problem)

Run optimization:

[ ]:
# workers=-1 uses all available cores
optimizer.optimize(maxiter=256, disp=False, workers=-1)
             message: Optimization terminated successfully.
             success: True
                 fun: 3.612745542781897
                   x: [-5.386e-01 -2.897e+00  4.682e+00]
                 nit: 69
                nfev: 3462
          population: [[-5.386e-01 -2.897e+00  4.682e+00]
                       [-5.320e-01 -2.821e+00  4.705e+00]
                       ...
                       [-5.352e-01 -2.862e+00  4.696e+00]
                       [-5.356e-01 -2.837e+00  4.676e+00]]
 population_energies: [ 3.613e+00  3.772e+00 ...  3.656e+00  3.675e+00]
                 jac: [ 1.009e+03  2.971e+01  4.932e+01]

Print merit function value and system properties after optimization:

[10]:
problem.info()
╒════╤════════════════════════╤═══════════════════╕
│    │   Merit Function Value │   Improvement (%) │
╞════╪════════════════════════╪═══════════════════╡
│  0 │                3.61275 │           99.9028 │
╘════╧════════════════════════╧═══════════════════╛
╒════╤════════════════╤══════════╤══════════╤══════════╤══════════╤════════════════════╕
│    │ Operand Type   │   Target │   Weight │    Value │    Delta │   Contribution (%) │
╞════╪════════════════╪══════════╪══════════╪══════════╪══════════╪════════════════════╡
│  0 │ f2             │       60 │        1 │ 60.1043  │ 0.104299 │           0.301108 │
│  1 │ OPD difference │        0 │        1 │  1.30422 │ 1.30422  │          47.0832   │
│  2 │ OPD difference │        0 │        1 │  1.37872 │ 1.37872  │          52.6157   │
╘════╧════════════════╧══════════╧══════════╧══════════╧══════════╧════════════════════╛
╒════╤═════════════════╤═══════════╤═══════════╤══════════════╤══════════════╕
│    │ Variable Type   │   Surface │     Value │   Min. Bound │   Max. Bound │
╞════╪═════════════════╪═══════════╪═══════════╪══════════════╪══════════════╡
│  0 │ radius          │         1 │   46.1442 │         -500 │          500 │
│  1 │ radius          │         2 │ -189.732  │         -500 │          500 │
│  2 │ thickness       │         2 │   56.8164 │           30 │          100 │
╘════╧═════════════════╧═══════════╧═══════════╧══════════════╧══════════════╛

Draw final lens:

[11]:
lens.draw(num_rays=5)
../../_images/gallery_optimization_global_22_0.png