Skip to Content
DocsGuidesWavefronts

Wavefronts

The Wavefront class represents the complex amplitude of an electromagnetic field E(x,y)E(x, y). It is the object every optical element consumes and produces.

A torch.Tensor subclass

Wavefront inherits from torch.Tensor, so all tensor operations are available:

from svetlanna import Wavefront # arithmetic wf_sum = wf1 + wf2 # interference wf_scaled = wf * 0.5 # attenuation wf_conj = wf.conj() # complex conjugate # move to the GPU wf_gpu = wf.to("cuda") # indexing wf_slice = wf[100:400, 100:400]

Factory methods

Plane wave

wf = Wavefront.plane_wave(params)

By default a unit-amplitude, zero-phase field:

E(x,y)=1E(x, y) = 1

A tilted wave is obtained with wave_direction:

wf = Wavefront.plane_wave(params, wave_direction=[0.01, 0.0, 1.0])

Gaussian beam

from svetlanna import Wavefront from svetlanna.units import ureg wf = Wavefront.gaussian_beam(params, waist_radius=0.5*ureg.mm)

Gaussian amplitude distribution: E(x,y)=exp⁡(−x2+y2w02)E(x, y) = \exp\left(-\frac{x^2 + y^2}{w_0^2}\right)

Parameters:

ParameterDescriptionDefault
waist_radiusWaist radius w0w_0—
distanceDistance from the waist0.0
dx, dyOffset of the centre0.0

Spherical wave

wf = Wavefront.spherical_wave(params, distance=50*ureg.mm)
r = \sqrt{(x-d_x)^2 + (y-d_y)^2 + z^2}$$ A positive `distance` places the point source behind the plane (a diverging wave); a negative one puts it in front (a converging wave). ### Hermite–Gaussian modes ```python wf = Wavefront.hermite_gauss( params, waist_radius=0.5*ureg.mm, m=2, n=1, distance=0.0 ) ``` `m` and `n` are the mode indices along X and Y. The Gouy phase is scaled by $(m+n+1)$. <Callout type="info"> Laguerre–Gaussian modes are not built in, but they are only a few lines of code — see the [Laguerre–Gaussian tutorial](/docs/tutorials/laguerre-gauss). </Callout> ### From a tensor ```python import torch amplitude = torch.ones(512, 512) phase = torch.zeros(512, 512) complex_field = amplitude * torch.exp(1j * phase) wf = Wavefront(complex_field) ``` --- ## Properties | Property | Description | Formula | |----------|-------------|---------| | `wf.intensity` | Intensity | $I = \|E\|^2$ | | `wf.phase` | Phase, radians | $\phi = \arg(E)$ | | `wf.max_intensity` | Peak intensity | $\max(I)$ | ### Intensity ```python I = wf.intensity # |E|² ``` ### Phase ```python phi = wf.phase # arg(E), wrapped to [-π, π] ``` ### FWHM ```python fwhm_x, fwhm_y = wf.fwhm(params) print(f"FWHM: {fwhm_x*1e6:.2f} × {fwhm_y*1e6:.2f} um") ``` <Callout type="warning"> `fwhm()` measures the extent of the grid points that lie above half maximum, so it is quantised by the grid step and under-reports narrow features. Refine the grid when the feature spans only a handful of points. </Callout> --- ## Operations on wavefronts ### Interference ```python wf_interference = wf1 + wf2 I_interference = wf_interference.intensity ``` ### Amplitude modulation ```python wf_attenuated = wf * 0.5 wf_amplified = wf * 2.0 # a binary transmission mask X, Y = params.meshgrid(x_axis='x', y_axis='y') radius = 0.5*ureg.mm mask = (X**2 + Y**2 < radius**2).float() wf_masked = wf * mask ``` ### Phase modulation ```python import torch # a constant phase shift wf_shifted = wf * torch.exp(1j * torch.tensor(torch.pi / 4)) # a spatially varying phase wf_modulated = wf * torch.exp(1j * phase_mask) ``` --- ## Visualisation ```python import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 2, figsize=(12, 5)) extent = [ params.x[0].item()*1e3, params.x[-1].item()*1e3, params.y[0].item()*1e3, params.y[-1].item()*1e3 ] im0 = axes[0].imshow(wf.intensity.cpu(), cmap='hot', extent=extent, origin='lower') axes[0].set_title('Intensity') axes[0].set_xlabel('x, mm') axes[0].set_ylabel('y, mm') plt.colorbar(im0, ax=axes[0]) im1 = axes[1].imshow(wf.phase.cpu(), cmap='twilight', extent=extent, origin='lower') axes[1].set_title('Phase') axes[1].set_xlabel('x, mm') axes[1].set_ylabel('y, mm') plt.colorbar(im1, ax=axes[1]) plt.tight_layout() plt.show() ``` --- ## Multi-dimensional wavefronts With several wavelengths the wavefront gains a leading axis automatically: ```python import torch from svetlanna import SimulationParameters, Wavefront params = SimulationParameters( x=torch.linspace(-1e-3, 1e-3, 256), y=torch.linspace(-1e-3, 1e-3, 256), wavelength=torch.tensor([630, 532, 465]) * 1e-9 ) wf = Wavefront.gaussian_beam(params, waist_radius=0.3e-3) print(wf.shape) # torch.Size([3, 256, 256]) wf_red = wf[0] # 630 nm wf_green = wf[1] # 532 nm wf_blue = wf[2] # 465 nm ``` <Callout type="warning"> In multi-dimensional simulations every element is applied to each wavelength with the correct dispersion — no extra work on your side. </Callout> --- ## Example: superposition of beams ```python import torch from svetlanna import SimulationParameters, Wavefront from svetlanna.units import ureg params = SimulationParameters.from_ranges( x_range=(-2*ureg.mm, 2*ureg.mm), x_points=512, y_range=(-2*ureg.mm, 2*ureg.mm), y_points=512, wavelength=632.8*ureg.nm ) wf1 = Wavefront.gaussian_beam(params, waist_radius=0.5*ureg.mm, dx=-0.5*ureg.mm) wf2 = Wavefront.gaussian_beam(params, waist_radius=0.5*ureg.mm, dx=0.5*ureg.mm) # put the second beam in quadrature wf2 = wf2 * torch.exp(1j * torch.tensor(torch.pi / 2)) wf_super = wf1 + wf2 print(f"Max intensity: {wf_super.intensity.max():.4f}") ``` --- ## GPU acceleration ```python # move an existing field wf_gpu = wf.to('cuda') # or move the parameters first params.to('cuda') wf = Wavefront.gaussian_beam(params, waist_radius=0.5e-3) # already on the GPU ``` --- ## See also - [SimulationParameters](/docs/guides/simulation-parameters) — the coordinate grid - [Optical elements](/docs/guides/elements) — transforming wavefronts - [Transforms](/docs/guides/transforms) — turning images into wavefronts