pyoptics.lens_optimization

Damped least squares optimization of raytrace.System lenses.

A LensOptimizer varies curvatures and thicknesses of a system and minimizes the sum of squares of the residuals of a list of operands, with scipy’s trust region least squares (finite difference Jacobian). Each operand is a function of the system that returns residuals, already divided by their tolerance, so that 1 means “at the tolerance”. Factories for the usual operands:

  • ray_errors(): transverse ray aberrations relative to the chief ray in the image plane on rings of the pupil (the spot sizes; zero defocus, spherical aberration, coma, astigmatism, field curvature),

  • distortion(): chief ray image height against the nominal magnification,

  • image_telecentricity(), object_telecentricity(): chief ray slopes in image and object space,

  • paraxial_target(): any attribute of raytrace.Paraxial (magnification, efl, NA…),

  • clearances(): penalties for overlapping lenses, too thin centres and edges and negative air spaces at the rim of the beam.

Constraints that should hold exactly are kept by the optimizer rather than by operands: a total length (object to image) by solving the image distance (or another thickness), and an object_na by setting the semi diameter of the stop from the paraxial marginal ray in every step.

Lens design is a non-convex problem and a local optimizer starting from a first order layout easily runs into total internal reflection or rays that miss surfaces at full aperture and field. LensOptimizer.run() therefore takes stages of increasing aperture and field (a curriculum, as in end-to-end lens design by gradient descent, e.g. X. Yang, Q. Fu and W. Heidrich, “Curriculum learning for ab initio deep learned refractive optics”, Nature Communications (2024)): the system is first corrected at a small NA and field, which then grow to the specification.

Rays are traced without vignetting (clip=False); a ray that fails (it misses a surface or is totally reflected) gives a large residual.

References

K. Levenberg, “A method for the solution of certain non-linear problems in least squares”, Q. Appl. Math. 2, 164-168 (1944).

D. W. Marquardt, “An algorithm for least-squares estimation of nonlinear parameters”, J. Soc. Ind. Appl. Math. 11, 431-441 (1963).

M. A. Branch, T. F. Coleman and Y. Li, “A subspace, interior, and conjugate gradient method for large-scale bound-constrained minimization problems”, SIAM J. Sci. Comput. 21, 1-23 (1999).

X. Yang, Q. Fu and W. Heidrich, “Curriculum learning for ab initio deep learned refractive optics”, Nat. Commun. 15 (2024).

R. Kingslake and R. B. Johnson, “Lens Design Fundamentals”, 2nd ed., Academic Press (2010).

Classes

LensOptimizer

Least squares optimization of curvatures and thicknesses.

OptimizationResult

Result of LensOptimizer.run(): the optimized system (at the full aperture and field), the final cost (half the sum of squares of the residuals), per stage (aperture fraction, field fraction, cost, function evaluations) and whether the last stage converged.

Functions

clearances

Penalties for lenses that are too thin and air spaces that close.

distortion

Chief ray image height minus magnification*object height (finite objects).

image_telecentricity

Image space chief ray slopes dy/dz.

object_telecentricity

Object space chief ray slope (finite objects): zero for an entrance pupil at infinity.

paraxial_target

Attribute name of System.paraxial() (e.g. "magnification", "efl", "na") against a target value.

ray_errors

Transverse ray aberrations relative to the chief ray.

class pyoptics.lens_optimization.OptimizationResult(system, cost, stages, success)

Bases: tuple

Result of LensOptimizer.run(): the optimized system (at the full aperture and field), the final cost (half the sum of squares of the residuals), per stage (aperture fraction, field fraction, cost, function evaluations) and whether the last stage converged.

cost

Alias for field number 1

stages

Alias for field number 2

success

Alias for field number 3

system

Alias for field number 0

pyoptics.lens_optimization.ray_errors(fields=(0.0, 0.5, 0.75, 1.0), rings=(0.4, 0.7, 0.9, 1.0), spokes=6, tolerance=0.0001)[source]

Transverse ray aberrations relative to the chief ray.

Parameters:
fieldssequence, optional

Fields as fractions of the maximum field of the system (y fields).

rings, spokesoptional

Normalized pupil radii of the rings and the number of rays per unit radius on each (half) ring.

tolerancenumber, optional

Tolerance in the length unit of the system (default 0.1 um for mm).

Returns:
operandcallable

operand(system) returns the x and y errors of all rays / tolerance.

pyoptics.lens_optimization.distortion(magnification, fields=(0.5, 0.75, 1.0), tolerance=1e-05, aim=False)[source]

Chief ray image height minus magnification*object height (finite objects).

Parameters:
magnificationnumber

Nominal lateral magnification.

fieldssequence, optional

Fields as fractions of the maximum object height.

tolerancenumber, optional

Tolerance in the length unit (default 10 nm for mm).

aimbool, optional

Use the real chief ray through the centre of the stop (ray aiming for this ray only) instead of the system’s chief ray (which, without ray aiming, is aimed at the paraxial entrance pupil).

Returns:
operandcallable
pyoptics.lens_optimization.image_telecentricity(fields=(0.5, 0.75, 1.0), tolerance=0.001, aim=False)[source]

Image space chief ray slopes dy/dz.

Parameters:
fieldssequence, optional

Fields as fractions of the maximum field.

tolerancenumber, optional

Tolerance in radians (default 1 mrad).

aimbool, optional

Use the real chief ray through the centre of the stop (see distortion()). Without ray aiming the system’s chief ray is aimed at the paraxial entrance pupil, and the pupil aberrations of a high NA lens make its slopes differ from those of the real chief ray by tens of mrad.

Returns:
operandcallable
pyoptics.lens_optimization.object_telecentricity(tolerance=0.0001, aim=False, fields=(0.5, 0.75, 1.0))[source]

Object space chief ray slope (finite objects): zero for an entrance pupil at infinity.

Parameters:
tolerancenumber, optional

Tolerance in radians.

aimbool, optional

False (default): the paraxial chief ray slope at the maximum field. True: the slopes of the real chief rays through the centre of the stop at fields (see image_telecentricity()).

fieldssequence, optional

Fields as fractions of the maximum field (with aim).

Returns:
operandcallable
pyoptics.lens_optimization.paraxial_target(name, value, tolerance)[source]

Attribute name of System.paraxial() (e.g. “magnification”, “efl”, “na”) against a target value.

Parameters:
namestr

Attribute of raytrace.Paraxial.

valuenumber

Target.

tolerancenumber

Tolerance.

Returns:
operandcallable
pyoptics.lens_optimization.clearances(min_air=1.0, min_center=None, min_edge=3.0, margin=1.05, tolerance=0.01, min_object_distance=None, min_image_distance=None)[source]

Penalties for lenses that are too thin and air spaces that close.

Thicknesses are checked on the axis and at the rim of the beam, the paraxial |y| + |ybar| (at the maximum field) times margin, the larger of the two surfaces of each space. During the stages of LensOptimizer.run() the rim is that of the full aperture and field.

Parameters:
min_airnumber, optional

Smallest air space, on axis and at the rim (also object and image distance).

min_centernumber, optional

Smallest centre thickness of a lens (default: min_edge).

min_edgenumber, optional

Smallest edge thickness of a lens.

marginnumber, optional

Rim height relative to the paraxial beam.

tolerancenumber, optional

A violation by tolerance gives a residual of 1 (default 10 um for mm); satisfied constraints give 0.

min_object_distance, min_image_distancenumber, optional

Smallest distances from the object to the first vertex and from the last vertex to the image (working distance); default min_air.

Returns:
operandcallable
class pyoptics.lens_optimization.LensOptimizer(system, operands, curvatures=None, thicknesses=None, object_distance=False, length=None, solve=-1, object_na=None, aspheres=(), asphere_orders=(4, 6))[source]

Bases: object

Least squares optimization of curvatures and thicknesses.

Parameters:
systemraytrace.System

Starting system (copied).

operandssequence of callables

Functions of the system that return residuals (see the factories of this module).

curvaturessequence of int, optional

Indices of the surfaces whose curvature varies. Default: all refracting and reflecting surfaces (stops and dummy surfaces that do not change the medium, ideal lenses and coordinate breaks stay).

thicknessessequence of int, optional

Indices of the surfaces whose thickness (to the next surface) varies. Default: all air spaces, except the solved one.

object_distancebool, optional

Vary the object distance (finite objects).

lengthnumber, optional

Keep the distance from object (finite objects; else the first vertex) to image at this value by solving the thickness of surface solve.

solveint, optional

Surface whose thickness is solved for the length (default: the last one, the image distance).

object_nanumber, optional

Keep the object space NA n*|u| of the paraxial marginal ray (finite objects) by setting the semi diameter of the stop.

aspheressequence of int, optional

Surfaces whose aspheric coefficients vary (even aspheres, see raytrace.Surface).

asphere_orderssequence of int, optional

Powers of r that vary on these surfaces (default r**4 and r**6). Internally the variables are the sags A_k r**k at the rim of the paraxial full field beam.

property x

Current values of the variables.

system_at(x)[source]

Copy of the system with the variables x.

Parameters:
xarray

Variables in the order of x.

Returns:
raytrace.System
residuals(x=None)[source]

All operand residuals at x (default: the current variables).

Parameters:
xarray, optional

Variables.

Returns:
array
cost()[source]

Half the sum of squares of the residuals of the current system.

run(stages=((1.0, 1.0),), max_nfev=100, **kwargs)[source]

Optimize, in stages of growing aperture and field.

Parameters:
stagessequence of (aperture_fraction, field_fraction), optional

Fractions of object_na and of the maximum field per stage (default: one stage at the full values). The aperture fraction needs object_na.

max_nfevint, optional

Function evaluations per stage (without those of the Jacobian).

**kwargs

Passed to scipy.optimize.least_squares (e.g. x_scale=”jac” instead of the default scales: the median curvature and thickness, 100 wavelengths for the aspheric sags).

Returns:
OptimizationResult