Beam focusing
In this tutorial we simulate the focusing of a Gaussian beam by a thin lens and compare the result with the diffraction limit.
Theory
Gaussian beam
A Gaussian beam has the field distribution
where is the waist radius — the distance from the axis at which the intensity drops by a factor of .
Diffraction limit
When a beam of waist is focused by a lens of focal length , the amplitude waist in the focal plane is
which corresponds to an intensity full width at half maximum of
This is the diffraction limit for a Gaussian beam.
Step 1: Imports and parameters
import math
import torch
import matplotlib.pyplot as plt
from svetlanna import SimulationParameters, Wavefront
from svetlanna.elements import ThinLens, FreeSpace, RoundAperture
from svetlanna.units import ureg
params = SimulationParameters.from_ranges(
x_range=(-1.5*ureg.mm, 1.5*ureg.mm), x_points=2048,
y_range=(-1.5*ureg.mm, 1.5*ureg.mm), y_points=2048,
wavelength=632.8*ureg.nm, # HeNe laser
)
w0 = 0.5*ureg.mm # beam waist radius
f = 100*ureg.mm # focal length
wavelength = 632.8*ureg.nmThe grid step is , so the focal spot is sampled by roughly 30 points — enough to measure its width reliably.
Step 2: Creating the Gaussian beam
wf = Wavefront.gaussian_beam(params, waist_radius=w0)
print(f"Shape: {wf.shape}")
print(f"Dtype: {wf.dtype}")
print(f"Max intensity: {wf.max_intensity:.3f}")Output:
Shape: torch.Size([2048, 2048])
Dtype: torch.complex64
Max intensity: 1.000Step 3: The optical setup
Aperture → lens → propagation to the focal plane.
# aperture, truncating the beam
aperture = RoundAperture(params, radius=1.2*ureg.mm)
# thin lens
lens = ThinLens(params, focal_length=f)
# propagation over one focal length
propagate = FreeSpace(params, distance=f, method="zpASM")Step 4: Propagating through the setup
wf_after_aperture = aperture(wf)
wf_after_lens = lens(wf_after_aperture)
wf_focus = propagate(wf_after_lens)
print(f"Intensity in the focus: {wf_focus.max_intensity:.2e}")Output:
Intensity in the focus: 1.53e+02Step 5: Analysing the result
fwhm_x, fwhm_y = wf_focus.fwhm(params)
print(f"Measured FWHM: {fwhm_x*1e6:.2f} × {fwhm_y*1e6:.2f} um")
w_focus = float(wavelength) * float(f) / (math.pi * float(w0))
fwhm_theory = w_focus * math.sqrt(2 * math.log(2))
print(f"Theoretical FWHM: {fwhm_theory*1e6:.2f} um")Output:
Measured FWHM: 45.43 × 45.43 um
Theoretical FWHM: 47.43 umThe 4 % difference comes from two sources: fwhm() counts only the grid points that lie
strictly above half maximum (so it under-reports by up to one grid step), and the aperture
truncates the tails of the Gaussian. Refining the grid brings the measurement closer to the
analytical value.
Step 6: Visualisation
extent = [-1.5, 1.5, -1.5, 1.5]
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes[0, 0].imshow(wf.intensity.cpu(), cmap='hot', extent=extent)
axes[0, 0].set_title('Input Gaussian beam')
axes[0, 0].set_xlabel('x, mm'); axes[0, 0].set_ylabel('y, mm')
axes[0, 1].imshow(wf_after_aperture.intensity.cpu(), cmap='hot', extent=extent)
axes[0, 1].set_title('After the aperture')
axes[0, 1].set_xlabel('x, mm')
axes[0, 2].imshow(wf_after_lens.phase.cpu(), cmap='twilight', extent=extent)
axes[0, 2].set_title('Phase after the lens')
axes[0, 2].set_xlabel('x, mm')
axes[1, 0].imshow(wf_focus.intensity.cpu(), cmap='hot', extent=extent)
axes[1, 0].set_title('Focal plane')
axes[1, 0].set_xlabel('x, mm'); axes[1, 0].set_ylabel('y, mm')
center, window = 1024, 60
axes[1, 1].imshow(
wf_focus.intensity[center-window:center+window,
center-window:center+window].cpu(),
cmap='hot',
)
axes[1, 1].set_title('Focal spot (zoom)')
profile = wf_focus.intensity[center, :].cpu()
x_um = params.x.cpu() * 1e6
axes[1, 2].plot(x_um, profile / profile.max())
axes[1, 2].set_xlim(-150, 150)
axes[1, 2].set_xlabel('x, um')
axes[1, 2].set_ylabel('Normalised intensity')
axes[1, 2].set_title('Profile in the focus')
axes[1, 2].grid(True)
plt.tight_layout()
plt.show()Step 7: Effect of the aperture
How does the aperture size change the focus?
aperture_radii = [0.4, 0.6, 0.8, 1.2] # mm
print("Aperture radius | FWHM | Max I")
print("-" * 40)
for r in aperture_radii:
aperture = RoundAperture(params, radius=r*ureg.mm)
wf_out = propagate(lens(aperture(wf)))
fwhm_x, _ = wf_out.fwhm(params)
print(f"{r:.1f} mm | {fwhm_x*1e6:5.1f} um | {wf_out.max_intensity:.3e}")Output:
Aperture radius | FWHM | Max I
----------------------------------------
0.4 mm | 83.5 um | 3.436e+01
0.6 mm | 60.1 um | 8.957e+01
0.8 mm | 51.3 um | 1.309e+02
1.2 mm | 45.4 um | 1.527e+02A hard aperture much smaller than the beam acts as the dominant diffracting element: the spot broadens and the peak intensity drops. Once the aperture stops clipping the beam (radius ), the focus converges to the diffraction limit.
Full code
import math
import torch
from svetlanna import SimulationParameters, Wavefront
from svetlanna.elements import ThinLens, FreeSpace, RoundAperture
from svetlanna.units import ureg
params = SimulationParameters.from_ranges(
x_range=(-1.5*ureg.mm, 1.5*ureg.mm), x_points=2048,
y_range=(-1.5*ureg.mm, 1.5*ureg.mm), y_points=2048,
wavelength=632.8*ureg.nm,
)
w0, f = 0.5*ureg.mm, 100*ureg.mm
wf = Wavefront.gaussian_beam(params, waist_radius=w0)
aperture = RoundAperture(params, radius=1.2*ureg.mm)
lens = ThinLens(params, focal_length=f)
propagate = FreeSpace(params, distance=f, method="zpASM")
wf_focus = propagate(lens(aperture(wf)))
fwhm_x, fwhm_y = wf_focus.fwhm(params)
print(f"FWHM: {fwhm_x*1e6:.2f} × {fwhm_y*1e6:.2f} um")
print(f"Max I: {wf_focus.max_intensity:.2e}")Conclusions
- SVETlANNa reproduces diffraction-limited focusing accurately.
- The measured spot size matches the analytical prediction once the grid is fine enough.
- The aperture controls both the spot size and the peak intensity.
What next?
- 4f system — optical filtering
- Phase grating on an SLM — programmable phase masks
- Optical elements — every available element