Lenses and Lens Lists#
Lenses are obviously an integral component in every mode matching setup.
Beam Corset can model lenses as infinitely thin using the ThinLens class or with finite thickness using the ThickLens class. Collections of lenses can saved loaded and managed using the LensList class.
Thin Lenses#
The simplest lens model in Beam Corset is the thin lens, used to model lenses which are significantly thinner than their focal length. Thin lenses are simply specified directly by their focal. In addition to that, we can also specify physical margins to account for the physical size of the lens and prevent overlaps. Finally, we can also give them a distinct name that will show up in plots and can be used to refer to them in lens lists. Thin lenses are modeled using the ThinLens class.
[1]:
from corset import ThinLens
thin_lens = ThinLens(200e-3) # margins default to 0
thin_lens_all_args = ThinLens(focal_length=100e-3, left_margin=5e-3, right_margin=5e-3, name="TL100")
print(repr(thin_lens))
print(repr(thin_lens_all_args))
ThinLens(focal_length=0.2, left_margin=0.0, right_margin=0.0, name=None)
ThinLens(focal_length=0.1, left_margin=0.005, right_margin=0.005, name='TL100')
The left and right margins are relative to the focal plane of the lens. They may be negative individually but they must add up to at least zero meaning that all lenses can not occupy negative space.
The margins and name are also shown in optical setup plots.
[2]:
from corset import Beam, OpticalSetup
beam = Beam.from_gauss(focus=0, waist=200e-6, wavelength=1064e-9)
setup = OpticalSetup(beam, [(0.1, thin_lens), (0.2, thin_lens_all_args)])
setup
[2]:
Lenses without a name are labeled with their focal length in millimeters.
Thick Lenses#
Lenses may also be modeled as thick lenses, in this case we specify them by their input and output radius of curvature, their refractive index and their thickness. The signs of the radius of curvature follow the standard optics convention where positive radii correspond to surfaces that are convex when viewed from the input side of the lens. This means that a biconvex lens will have a positive input radius of curvature and a negative output radius of curvature.
Like thin lenses, we can also give thick lenses physical margins and names. The physical margins extend from the plane centered between the two surfaces of the lens and are independent from the optical thickness.
Since the beam propagation model requires that the beam exits an element in the same axial position as it entered it, the ray transfer matrices for thick lenses include a negative amount of free space propagation before and after the lens. This way the sum of all propagation matrices that the element is made up of, is zero. One notable consequence of this treatment is that the calculated and plotted beam radius is not accurate for axial positions that are inside thick lenses. This also means that there may be a small discontinuity in beam radius across thick lenses in the plotted beam profiles.
Thick lenses are modeled using the ThickLens class. For convenience and interoperability with thin lenses, the thick lens class also has a property focal_length that yields the approximate focal length calculated using the lensmaker’s equation. Thick lenses cannot be described using a single focal length so this value should only be used as an estimate and not for calculations.
[3]:
from corset import ThickLens
thick_lens = ThickLens(in_roc=50e-3, out_roc=-50e-3, thickness=5e-3, refractive_index=1.5)
print(repr(thick_lens))
print(f"{thick_lens.focal_length = }")
ThickLens(in_roc=0.05, out_roc=-0.05, thickness=0.005, refractive_index=1.5, left_margin=0.0, right_margin=0.0, name=None)
thick_lens.focal_length = 0.05084745762711864
We can create flat interfaces by setting an infinite radius of curvature, i.e. float(‘inf’) or np.inf. However, instead of using infinity directly, you should use the alias ThickLens.FLAT to improve readability and convey intent.
[4]:
plano_convex = ThickLens(ThickLens.FLAT, 50e-3, 10e-3, 1.5)
print(repr(plano_convex))
ThickLens(in_roc=inf, out_roc=0.05, thickness=0.01, refractive_index=1.5, left_margin=0.0, right_margin=0.0, name=None)
Lens Lists#
Since you will likely use the same set of lenses for most setups, it makes sense to build a database of these lenses to avoid having to look up and specify their parameters every time you want to use them. For this, we can use a LensList which is a list of lenses with some extra convenience features.
[5]:
from corset import LensList
my_lenses = LensList([
ThinLens(50e-3, name="TL50"),
ThinLens(100e-3, name="TL100"),
ThickLens(ThickLens.FLAT, -100e-3, 10e-3, 1.5, name="PX200"),
ThickLens(300e-3, -300e-3, 5e-3, 1.5, name="BX300"),
])
my_lenses
[5]:
| name | type | focal_length | left_margin | right_margin | in_roc | out_roc | thickness | refractive_index | lens | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TL50 | thin | 0.050000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL50 |
| 1 | TL100 | thin | 0.100000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL100 |
| 2 | PX200 | thick | 0.200000 | 0.0 | 0.0 | inf | -0.1 | 0.010 | 1.5 | PX200 |
| 3 | BX300 | thick | 0.300836 | 0.0 | 0.0 | 0.3 | -0.3 | 0.005 | 1.5 | BX300 |
Lens lists will display as a DataFrame in Jupyter notebooks, showing the lenses parameters. To allow both thin and thick lenses in the same list, the DataFrame will show NaN for parameters that do not apply to thin lenses, and display the approximate focal length calculated using the lensmaker’s equation for thick lenses.
Lens lists can be saved to and loaded from CSV files using the save_csv() and load_csv() methods. These forward to pandas.DataFrame.to_csv() and pandas.read_csv() respectively, so they accept the same kinds of inputs like URLs to easily load lens lists from online repositories.
[6]:
from IPython.display import Pretty
my_lenses.save_csv("my_lenses.csv")
Pretty("my_lenses.csv")
[6]:
name,type,focal_length,left_margin,right_margin,in_roc,out_roc,thickness,refractive_index
TL50,thin,0.05,0.0,0.0,,,,
TL100,thin,0.1,0.0,0.0,,,,
PX200,thick,0.2,0.0,0.0,inf,-0.1,0.01,1.5
BX300,thick,0.30083565459610023,0.0,0.0,0.3,-0.3,0.005,1.5
When loading lenses from CSV files, the lens type is determined from the type filed and must be consistent with the parameters specified.
[7]:
LensList.load_csv("my_lenses.csv") == my_lenses
[7]:
True
Beam Corset also includes some built-in lens databases that can be loaded using the LensList.load() class method.
[8]:
LensList.load("quantum_control/Thorlabs_BX_L1064_M10")
[8]:
| name | type | focal_length | left_margin | right_margin | in_roc | out_roc | thickness | refractive_index | lens | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TBX25.4 | thick | 0.025771 | 0.005 | 0.005 | 0.0245 | -0.0245 | 0.0090 | 1.506635 | TBX25.4 |
| 1 | TBX50 | thick | 0.050815 | 0.005 | 0.005 | 0.0506 | -0.0506 | 0.0052 | 1.506635 | TBX50 |
| 2 | TBX100 | thick | 0.101660 | 0.005 | 0.005 | 0.1024 | -0.1024 | 0.0036 | 1.506635 | TBX100 |
| 3 | TBX150 | thick | 0.152499 | 0.005 | 0.005 | 0.1540 | -0.1540 | 0.0031 | 1.506635 | TBX150 |
| 4 | TBX200 | thick | 0.203373 | 0.005 | 0.005 | 0.2056 | -0.2056 | 0.0028 | 1.506635 | TBX200 |
| 5 | TBX250 | thick | 0.254165 | 0.005 | 0.005 | 0.2571 | -0.2571 | 0.0026 | 1.506635 | TBX250 |
| 6 | TBX300 | thick | 0.304974 | 0.005 | 0.005 | 0.3086 | -0.3086 | 0.0025 | 1.506635 | TBX300 |
| 7 | TBX400 | thick | 0.406707 | 0.005 | 0.005 | 0.4117 | -0.4117 | 0.0024 | 1.506635 | TBX400 |
| 8 | TBX500 | thick | 0.508342 | 0.005 | 0.005 | 0.5147 | -0.5147 | 0.0023 | 1.506635 | TBX500 |
| 9 | TBX1000 | thick | 1.016679 | 0.005 | 0.005 | 1.0298 | -1.0298 | 0.0022 | 1.506635 | TBX1000 |
Use LensList.available() to list the available databases.
In addition to normal scalar indexing, we can also index into the list lens name. To ensure that this is always unambiguous, lens names must be unique within a lens list. This is enforced by the constructor.
[9]:
print(repr(my_lenses[0]))
print(repr(my_lenses["PX200"]))
ThinLens(focal_length=0.05, left_margin=0.0, right_margin=0.0, name='TL50')
ThickLens(in_roc=inf, out_roc=-0.1, thickness=0.01, refractive_index=1.5, left_margin=0.0, right_margin=0.0, name='PX200')
We can easily create a subset of a lens list by indexing with a list of names or indices.
[10]:
my_lenses[[0, 3]]
[10]:
| name | type | focal_length | left_margin | right_margin | in_roc | out_roc | thickness | refractive_index | lens | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TL50 | thin | 0.050000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL50 |
| 1 | BX300 | thick | 0.300836 | 0.0 | 0.0 | 0.3 | -0.3 | 0.005 | 1.5 | BX300 |
[11]:
my_lenses[["TL50", "BX300"]]
[11]:
| name | type | focal_length | left_margin | right_margin | in_roc | out_roc | thickness | refractive_index | lens | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TL50 | thin | 0.050000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL50 |
| 1 | BX300 | thick | 0.300836 | 0.0 | 0.0 | 0.3 | -0.3 | 0.005 | 1.5 | BX300 |
Lens lists can be concatenated with other lens lists or normal lists using the + operator.
[12]:
new_list = my_lenses + [ThinLens(focal_length=25e-3, name="TL25")]
new_list
[12]:
| name | type | focal_length | left_margin | right_margin | in_roc | out_roc | thickness | refractive_index | lens | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TL50 | thin | 0.050000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL50 |
| 1 | TL100 | thin | 0.100000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL100 |
| 2 | PX200 | thick | 0.200000 | 0.0 | 0.0 | inf | -0.1 | 0.010 | 1.5 | PX200 |
| 3 | BX300 | thick | 0.300836 | 0.0 | 0.0 | 0.3 | -0.3 | 0.005 | 1.5 | BX300 |
| 4 | TL25 | thin | 0.025000 | 0.0 | 0.0 | NaN | NaN | NaN | NaN | TL25 |