Systems#

The System class models an optical system as an ordered sequence of elements along the optical axis. It works like torch.nn.Sequential; propagation between elements is handled automatically.

Creating a System#

Pass elements to the constructor. Each element’s z determines its position on the axis:

import torch
import torchoptics
from torchoptics import Field, System
from torchoptics.elements import AmplitudeModulator, Lens
from torchoptics.profiles import circle, gaussian

torchoptics.set_default_spacing(10e-6)
torchoptics.set_default_wavelength(700e-9)

shape = 1000
f = 200e-3

system = System(
    Lens(shape, f, z=1 * f),
    Lens(shape, f, z=3 * f),
)

Printing the system shows each element’s parameters:

print(system)
System(
  (0): Lens(shape=(1000, 1000), z=2.00e-01, spacing=(1.00e-05, 1.00e-05), offset=(0.00e+00, 0.00e+00), focal_length=2.00e-01)
  (1): Lens(shape=(1000, 1000), z=6.00e-01, spacing=(1.00e-05, 1.00e-05), offset=(0.00e+00, 0.00e+00), focal_length=2.00e-01)
)

Forward Pass#

The forward() method propagates a field through every element in order of increasing z, returning the field immediately after the last element:

input_field = Field(gaussian(shape, waist_radius=1e-3), z=0)
output_field = system(input_field)

Elements whose z position is strictly less than the field’s starting z are skipped; elements at exactly the field’s starting z are applied.

Measuring at Output Planes#

The measure methods propagate through the system and then to a specified output plane.

measure_at_z() — measure at a z position with the input grid:

input_field = Field(gaussian(shape, waist_radius=1e-3), z=0)
output = system.measure_at_z(input_field, z=4 * f)
output.visualize(title="4f System Output")
../_images/systems-2.png

measure() — full control over the output grid:

output = system.measure(
    input_field, shape=(512, 512), z=4 * f, spacing=5e-6, offset=(0, 0),
)

measure_at_plane() — target a PlanarGrid:

from torchoptics import PlanarGrid

output_plane = PlanarGrid(shape=400, z=4 * f, spacing=8e-6)
output = system.measure_at_plane(input_field, output_plane)

Indexing#

Systems support indexing, slicing, and iteration:

first = system[0]             # Single element
sub = system[0:2]             # New System from slice
n = len(system)               # Element count

for element in system:
    print(element)
Lens(shape=(1000, 1000), z=2.00e-01, spacing=(1.00e-05, 1.00e-05), offset=(0.00e+00, 0.00e+00), focal_length=2.00e-01)
Lens(shape=(1000, 1000), z=6.00e-01, spacing=(1.00e-05, 1.00e-05), offset=(0.00e+00, 0.00e+00), focal_length=2.00e-01)

Trainable Systems#

When elements contain Parameter tensors, the entire system is differentiable end-to-end. See Inverse Design for training loops and optimization.