Skip to content

Hello! I'm Victor, the author of this project.
I'm looking for funding to be able to keep working on it. If you can, please consider donating or sponsoring.
Thank you! ❤️

Variable Lens Sequence

A configurable system with multiple lenses. We define three types of lenses:

  • A plano convex lens
  • A symmetric biconvex lens
  • A convex plano lens, the same shape as the first lens, just reversed.

The number of lenses of each type are input parameters of the script, and the complete sequence is:

  • X plano convex lenses
  • Y biconvex convex lenses
  • Z convex plano lenses
python
import torch
import torch.nn as nn
import torch.optim as optim
import torchlensmaker as tlm

import math
import itertools
import numpy as np

### DESIGN ###

# Design parameters
square_size = 30
lens_diameter = math.sqrt(2)*square_size
focal_length = 35.

# Number of each lens type
nplano = 2
nbiconvex = 2
nrplano = 1

# Mechanical sizes
lens_min_thickness = 1.2
lens_spacing = 3.

### MODEL ###

# Parametric surfaces 
surface_plano = tlm.Parabola(lens_diameter, tlm.parameter(-0.002))
surface_biconvex = tlm.Parabola(lens_diameter, tlm.parameter(0.002))

# Lenses
lenses_plano = [tlm.PlanoLens(
    surface_plano,
    material="BK7-nd",
    outer_thickness = lens_min_thickness,
    reverse=False,
) for i in range(nplano)]

lenses_biconvex = [tlm.BiLens(
    surface_biconvex,
    material="BK7-nd",
    outer_thickness = lens_min_thickness,
) for i in range(nbiconvex)]

lenses_rplano = [tlm.PlanoLens(
    surface_plano,
    material="BK7-nd",
    outer_thickness = lens_min_thickness,
    reverse=True,
) for i in range(nrplano)]

optics = nn.Sequential(
    tlm.PointSourceAtInfinity(beam_diameter=0.9*lens_diameter),
    tlm.Gap(10.),
    
    *itertools.chain.from_iterable([[tlm.Gap(lens_spacing), lens] for lens in lenses_plano]),
    *itertools.chain.from_iterable([[tlm.Gap(lens_spacing), lens] for lens in lenses_biconvex]),
    *itertools.chain.from_iterable([[tlm.Gap(lens_spacing), lens] for lens in lenses_rplano]),
    
    tlm.Gap(focal_length),
    tlm.FocalPoint(),
)

# print(optics)

print("Lens design")
print("Square size", square_size)
print("Lens diameter", lens_diameter)
print("Configuration", nplano, nbiconvex, nrplano)
print("lens_min_thickness", lens_min_thickness)
print("lens_spacing", lens_spacing)

tlm.show(optics, dim=2)
tlm.show(optics, dim=3)
Lens design
Square size 30
Lens diameter 42.42640687119285
Configuration 2 2 1
lens_min_thickness 1.2
lens_spacing 3.0
python
def regu_equalparam(_):
    a1 = surface_plano.a
    a2 = surface_biconvex.a
    return 500*torch.pow((500*a1)**2 - (500*a2)**2, 2)
    #params = torch.cat([param.view(-1) for param in optics.parameters()])
    #return torch.pow(torch.diff(1000*torch.abs(params)).sum(), 2)

def regu_equalthickness(_):
    t0 = lenses_plano[0].inner_thickness()
    t1 = lenses_biconvex[0].inner_thickness()
    return 100*torch.pow(t0 - t1, 2)

tlm.optimize(
    optics,
    optimizer = optim.Adam(optics.parameters(), lr=1e-4),
    sampling = {"base": 10},
    dim = 2,
    num_iter = 100,
    regularization = regu_equalthickness
).plot()

def print_lens(lens_name, lens):
    # TODO thickness at a specific radial distance
    print(lens_name)
    inner = lens.inner_thickness().item()
    outer = lens.outer_thickness().item()
    print(f"    inner: {inner:.3f} outer: {outer:.3f}")
    
    a1 = lens.surface1.surface.parameters()
    a2 = lens.surface2.surface.parameters()
    print("    surface1", [p.tolist() for p in a1.values()])
    print("    surface2", [p.tolist() for p in a2.values()])

print_lens("Plano-convex", lenses_plano[0])
print_lens("Bi-convex", lenses_biconvex[0])
print_lens("Reverse plano-convex", lenses_rplano[0])
[  1/100] L= 84.721 | grad norm= 180027.30859716702
[  6/100] L= 10.012 | grad norm= 48586.85753360243
[ 11/100] L=  8.915 | grad norm= 45467.74241772557
[ 16/100] L= 13.302 | grad norm= 62370.50388286968
[ 21/100] L=  5.419 | grad norm= 25824.07799286678
[ 26/100] L=  4.480 | grad norm= 15842.663121738253
[ 31/100] L=  5.782 | grad norm= 28089.624034440483
[ 36/100] L=  4.030 | grad norm= 11638.0559620909
[ 41/100] L=  3.772 | grad norm= 9785.112736666892
[ 46/100] L=  3.947 | grad norm= 14470.964210249063
[ 51/100] L=  3.429 | grad norm= 4729.6565752732095
[ 56/100] L=  3.399 | grad norm= 5939.238324531459
[ 61/100] L=  3.278 | grad norm= 5355.6202899457285
[ 66/100] L=  3.096 | grad norm= 2785.695079791156
[ 71/100] L=  3.018 | grad norm= 5088.090779793749
[ 76/100] L=  2.871 | grad norm= 2992.8820186164903
[ 81/100] L=  2.756 | grad norm= 2519.2939043645542
[ 86/100] L=  2.629 | grad norm= 2378.915438707358
[ 91/100] L=  2.498 | grad norm= 2648.0570671543073
[ 96/100] L=  2.369 | grad norm= 2893.2923444452654
[100/100] L=  2.259 | grad norm= 2461.6447550702096

png

Plano-convex
    inner: 2.714 outer: 1.200
    surface1 []
    surface2 [-0.0033651619031351706]
Bi-convex
    inner: 2.716 outer: 1.200
    surface1 [0.0016848444901051824]
    surface2 [0.0016848444901051824]
Reverse plano-convex
    inner: 2.714 outer: 1.200
    surface1 [-0.0033651619031351706]
    surface2 []
python
tlm.show2d(optics)
tlm.show3d(optics)
python
from IPython.display import display

tlm.show_part(tlm.export.lens_to_part(lenses_plano[0]))
tlm.show_part(tlm.export.lens_to_part(lenses_biconvex[0]))

part display not supported in vitepress

part display not supported in vitepress