Note
Go to the end to download the full example code.
Gauss’s ABCD formalism: a composite system as a matrix product#
Carl Friedrich Gauss showed that any centered optical system, however many lenses and refracting surfaces it contains, can be fully characterized in the paraxial approximation by linear algebra rather than surface-by-surface trigonometry: every elementary optical element is a linear map on the ray state \((y, \theta)\) – height above the axis and angle to it –
and a compound system’s matrix is simply the ordered product of its
elements’ matrices, \(M = M_n \cdots M_2 M_1\) (rightmost applied
first). Every function in physicskit.optics.ray –
free_space(),
thin_lens(), and the rest – returns one
elementary Gauss/ABCD matrix, and
system_matrix() multiplies them
in traversal order, exactly Gauss’s composition rule. Below, a simple
imaging system – free-space propagation, a thin lens, then more
free-space propagation – is built two ways: once by composing
system_matrix() from named
elements, and once by directly multiplying the three elementary matrices
by hand, to confirm they agree exactly.
import matplotlib.pyplot as plt
import numpy as np
from physicskit.optics.ray import OpticalElement, OpticalSystem, free_space, thin_lens
from physicskit.optics.visualizers import plot_ray_trace
A simple imaging system: object distance, thin lens, image distance#
Gauss’s composition rule: the whole system is just matrix multiplication.
Building the system matrix by hand, as an explicit product of the three
element matrices, must agree exactly with
system_matrix().
M_direct = free_space(d2) @ thin_lens(f) @ free_space(d1)
M_system = system.system_matrix()
matrices_agree = np.allclose(M_direct, M_system)
fig, ax = plot_ray_trace(system, y0=0.01, theta0=0.05)
ax.set_title("A ray traced through free_space -> thin_lens -> free_space")
print("hand-built product M = free_space(d2) @ thin_lens(f) @ free_space(d1):")
print(M_direct)
print("\nOpticalSystem.system_matrix() (elements multiplied in traversal order):")
print(M_system)
print(f"\nmatrices agree: {matrices_agree}")
trajectory = system.trace_ray(y0=0.01, theta0=0.05)
print(f"\nray height/angle at each plane:\n{trajectory}")

hand-built product M = free_space(d2) @ thin_lens(f) @ free_space(d1):
[[-5.00000000e-01 2.77555756e-17]
[-1.00000000e+01 -2.00000000e+00]]
OpticalSystem.system_matrix() (elements multiplied in traversal order):
[[ -0.5 0. ]
[-10. -2. ]]
matrices agree: True
ray height/angle at each plane:
[[ 0.01 0.05 ]
[ 0.025 0.05 ]
[ 0.025 -0.2 ]
[-0.005 -0.2 ]]
A ray fan: imaging, not just tracing, one ray#
A single traced ray only shows what the ABCD matrix does to one input
state. Tracing a fan of rays launched from the same off-axis object
point y0 but at many different angles theta0 – each just another
call to trace_ray() through the
very same system – shows the imaging property that composed ABCD
matrix actually encodes: since d2 here was chosen to satisfy the thin
lens conjugate relation for d1 and f, every ray of the fan,
however steep, is bent by the lens so as to reconverge at the same
height at the image plane.
object_height = 0.01
angles = np.linspace(-0.08, 0.08, 9)
positions = np.concatenate(([0.0], np.cumsum([el.length for el in system.elements])))
fig2, ax2 = plt.subplots(figsize=(7, 3.5))
for theta0 in angles:
fan_trajectory = system.trace_ray(y0=object_height, theta0=theta0)
ax2.plot(positions, fan_trajectory[:, 0], color="C0", alpha=0.7)
image_heights = np.array([system.trace_ray(y0=object_height, theta0=th)[-1, 0] for th in angles])
ax2.axvline(positions[-1], color="r", ls="--", lw=1, label="image plane")
ax2.axhline(0.0, color="k", lw=0.5)
ax2.set_xlabel("position")
ax2.set_ylabel("ray height y")
ax2.legend(fontsize=8)
ax2.set_title(f"Ray fan from one object point: all {len(angles)} rays reconverge at the image plane")
fig2.tight_layout()
print(f"\nray-fan image heights at the final plane (should all coincide): {image_heights}")
print(f"spread of image heights across the fan: {image_heights.max() - image_heights.min():.2e}")

ray-fan image heights at the final plane (should all coincide): [-0.005 -0.005 -0.005 -0.005 -0.005 -0.005 -0.005 -0.005 -0.005]
spread of image heights across the fan: 3.47e-18
Total running time of the script: (0 minutes 0.057 seconds)