pyoptics.fdtd¶
Finite-difference time-domain (FDTD) solver for Maxwell’s equations in 1D and 2D.
The FDTD method (Yee 1966) samples E and H on staggered grids in space and time and leapfrogs them through the curl equations. It is rigorous for any structure that can be drawn on the grid: waveguides, resonators, gratings, scatterers of any size, metals. A single run with a short pulse gives the response over a whole band.
Building blocks¶
Grid: domain size, cell size and time step (normalized units: c = 1, lengths and wavelengths in the same unit, times in that unit over c).Shapes (
Slab,Rect,Circle,Annulus,Polygon, unions and differences) andMedium(permittivity, conductivity), drawn on the grid with sub-cell averaging of the permittivity.Boundaries:
PML(convolutional PML), “pec” walls, “periodic”.Sources:
PointSource,LineSource(soft currents),TFSF(plane wave in a total-field / scattered-field box), with the time profilesGaussianPulseandContinuousWave.Simulationputs them together;pyoptics.fdtd.plotdraws structures, fields and animations.
Polarization: Ez and Hz¶
2D simulations are invariant along z and split into two independent polarizations, named here after the field component along z:
mode="Ez": E along z (out of the plane), fields Ez, Hx, Hy,mode="Hz": H along z, fields Ex, Ey, Hz.
The literature names them inconsistently. In the FDTD literature (Taflove
and Hagness) Ez is “TM” (H transverse to z) and Hz “TE”. For waveguides,
gratings and scattering, the names refer to the plane of incidence
instead: E perpendicular to it (here: E along z) is “TE” or s, so there Ez
is “TE” and Hz “TM”. pyoptics.litho.rcwa and
pyoptics.analytical.cylinder_field use the latter: their TE case is the
Ez mode here.
Example¶
>>> from pyoptics import fdtd
>>> grid = fdtd.Grid((8.0, 8.0), 1/30)
>>> sim = fdtd.Simulation(grid, structure=[(fdtd.Circle((0, 0), 1.0), fdtd.Medium.from_index(1.5))],
... sources=[fdtd.TFSF((-2, 2, -2, 2), fdtd.GaussianPulse(1.0))])
>>> dft = fdtd.DFTField(1.0, components=["Ez"])
>>> sim.monitors.append(dft)
>>> sim.run(time=40)
This is the first milestone of the design in proposals/fdtd.md: NumPy kernels and non-dispersive media. Planned: numba kernels, dispersive media, mode sources and S-parameters, Bloch boundaries, 3D.
- class pyoptics.fdtd.Grid(size, spacing, center=None)[source]¶
Bases:
objectRegular Yee grid in 1D or 2D.
- Parameters:
- sizenumber or tuple
Extent of the computational domain along each axis, including the absorbing layers. A number gives a 1D grid.
- spacingnumber or tuple
Cell size, the same for all axes if a number. The number of cells is round(size/spacing).
- centernumber or tuple, optional
Centre of the domain, 0 by default.
- index(position, component, mode='Ez')[source]¶
Index tuple of the node of a component closest to a position.
- time_step(courant=0.99)[source]¶
Time step as a fraction courant of the stability limit 1/sqrt(sum(1/spacing**2)).
- property extent¶
(xmin, xmax[, ymin, ymax]) of the domain, for imshow().
- class pyoptics.fdtd.Shape[source]¶
Bases:
objectBase class: a region of space, defined by contains().
- class pyoptics.fdtd.Slab(lo, hi, axis=0)[source]¶
Bases:
ShapeLayer lo <= coordinate < hi along an axis (0: x, 1: y), unbounded in the other directions. Works in 1D and 2D.
- class pyoptics.fdtd.Rect(center, size)[source]¶
Bases:
ShapeAxis-parallel rectangle (2D) given by its centre and size.
- class pyoptics.fdtd.Circle(center, radius)[source]¶
Bases:
ShapeDisc (2D), the cross section of a cylinder along z.
- class pyoptics.fdtd.Annulus(center, r_inner, r_outer)[source]¶
Bases:
ShapeRing between two radii (2D), e.g. a ring resonator.
- class pyoptics.fdtd.Polygon(vertices)[source]¶
Bases:
ShapeSimple polygon (2D, convex or concave) given by its vertices.
- class pyoptics.fdtd.Medium(eps=1.0, sigma=0.0)[source]¶
Bases:
objectNon-dispersive, possibly conductive medium.
- Parameters:
- epsnumber
Relative permittivity (>= 1 for stability with the default time step).
- sigmanumber, optional
Conductivity in normalized units (absorption).
- class pyoptics.fdtd.PML(cells=12, order=3, reflection=1e-08, kappa_max=1.0, alpha_max=0.0)[source]¶
Bases:
objectAbsorbing layer of the given number of cells on both ends of an axis.
- Parameters:
- cellsint
Thickness in cells.
- ordernumber
Polynomial grading order m of sigma and kappa.
- reflectionnumber
Theoretical reflection R0 at normal incidence; sets sigma_max = -(m + 1)*ln(R0)/(2*thickness).
- kappa_maxnumber
Maximum real coordinate stretch (>= 1); > 1 helps with evanescent and grazing waves.
- alpha_maxnumber
Complex frequency shift at the inner edge (in units of 1/time, decreasing linearly to 0 at the wall); absorbs low frequencies less and so avoids late-time drift for long runs.
- class pyoptics.fdtd.GaussianPulse(wavelength, bandwidth=0.2, delay=None, amplitude=1.0)[source]¶
Bases:
objectSine carrier under a Gaussian envelope (no DC component).
s(t) = amplitude*exp(-(t - delay)**2/(2 tau**2))*sin(omega_0 (t - delay))
- Parameters:
- wavelengthnumber
Vacuum wavelength of the carrier.
- bandwidthnumber
Spectral standard deviation relative to the carrier frequency; tau = 1/(2 pi bandwidth f_0).
- delaynumber, optional
Centre of the pulse, 5 tau by default (the pulse starts smoothly).
- property end¶
Time after which the pulse is negligible (< 4e-6 of its peak).
- class pyoptics.fdtd.ContinuousWave(wavelength, ramp=None, amplitude=1.0)[source]¶
Bases:
objectsin(omega t), switched on smoothly over a ramp time (raised cosine).
- Parameters:
- wavelengthnumber
- rampnumber, optional
Rise time, 3 periods by default.
- class pyoptics.fdtd.PointSource(position, profile, component=None, amplitude=1.0)[source]¶
Bases:
_CurrentSourcePoint dipole (a line current along z in 2D, a sheet in 1D).
- Parameters:
- positionnumber or tuple
- profilecallable
Time profile, e.g. GaussianPulse or ContinuousWave.
- componentstr, optional
Field component the current drives: Ez (default in 1D and the Ez mode), Ex or Ey (electric), Hz (magnetic current, default in the Hz mode).
- amplitudenumber
- class pyoptics.fdtd.LineSource(position, profile, axis=0, component=None, extent=None, transverse=None, amplitude=1.0)[source]¶
Bases:
_CurrentSourceCurrent sheet across the domain at a position along an axis (2D), or a sheet in 1D. With periodic boundaries along the line it launches plane waves to both sides.
- Parameters:
- positionnumber
Coordinate of the line along
axis.- profilecallable
Time profile.
- axisint
Normal of the line (0: a line x = position, 1: y = position).
- componentstr, optional
As for PointSource.
- extent(lo, hi), optional
Part of the line that radiates (default: all of it).
- transversecallable, optional
Amplitude across the line as a function of the coordinate along it, e.g. a Gaussian beam profile.
- amplitudenumber
- class pyoptics.fdtd.TFSF(box, profile, n_background=1.0)[source]¶
Bases:
SourcePlane wave travelling towards +x, injected on the boundary of a box (total-field / scattered-field formulation, 2D, both modes).
Inside the box the total field is computed, outside only the scattered field: without scatterers the field outside the box is zero up to round-off. The incident wave comes from an auxiliary 1D grid with the same cell size and time step, whose numerical dispersion along x is identical to that of the 2D grid.
- Parameters:
- box(xmin, xmax, ymin, ymax)
Total field region; it should lie in the background medium near its edges and stay clear of the PML.
- profilecallable
Time profile of the incident field (its E at the source of the auxiliary grid; the wave reaches x = xmin a bit later).
- n_backgroundnumber
Refractive index of the background medium.
- class pyoptics.fdtd.Probe(position, component=None)[source]¶
Bases:
MonitorTime series of one field component at the node nearest to a point.
Attributes after a run: times, values (arrays).
- class pyoptics.fdtd.Snapshots(component=None, every=10, region=None, stride=1, dtype=<class 'numpy.float16'>, keep_last=None, start=0.0, path=None)[source]¶
Bases:
MonitorDecimated frames of one field component.
- Parameters:
- componentstr, optional
Default: Ez in the Ez mode, Hz in the Hz mode.
- everyint
Record every
everysteps.- regiontuple, optional
Crop, ((xmin, xmax), (ymin, ymax)).
- strideint
Spatial subsampling.
- dtypenumpy dtype
Storage type of the frames; float16 halves the memory of float32 and is plenty for plots.
- keep_lastint, optional
Keep only the last frames (ring buffer).
- startnumber
Time at which recording starts.
- pathstr, optional
Stream the frames to this HDF5 file (needs h5py) instead of keeping them in memory.
- class pyoptics.fdtd.SnapshotStack(times, coords, frames, component, mode='Ez')[source]¶
Bases:
objectFrames of one component: times (n,), coords (one array per axis), frames (n, …) and the component name.
- class pyoptics.fdtd.DFTField(wavelengths, components=None, region=None, start=0.0, stride=1)[source]¶
Bases:
MonitorRunning DFT of field components at vacuum wavelengths.
- Parameters:
- wavelengthsnumber or array
- componentssequence of str, optional
Default: all components of the mode.
- regiontuple, optional
((xmin, xmax), (ymin, ymax)); default: the whole grid.
- startnumber
Time from which on to accumulate (e.g. after a CW source has reached the steady state).
- strideint
Spatial subsampling.
- class pyoptics.fdtd.DFTFieldData(wavelengths, fields, coords, mode='Ez')[source]¶
Bases:
objectComplex field amplitudes at some wavelengths: fields[component] has the shape (n_wavelengths, …), coords[component] one array per axis.
- class pyoptics.fdtd.Flux(position, wavelengths, axis=0, extent=None)[source]¶
Bases:
MonitorSpectral power flux through a line (2D) or a point (1D).
The line is normal to
axisatposition, optionally limited toextentalong the other axis; the flux counts positive along +axis. H is interpolated to the E nodes. flux() returns 1/2 Re(sum E H*) dA per wavelength (arbitrary units: divide by the flux of a reference run).
- class pyoptics.fdtd.Simulation(grid, structure=(), sources=(), monitors=(), mode='Ez', boundaries='pml', background=Medium(eps=1, sigma=0), courant=0.99, dt=None, subpixel=None, dtype=<class 'numpy.float64'>)[source]¶
Bases:
objectFDTD simulation in 1D or 2D.
- Parameters:
- gridGrid
- structuresequence of (Shape, Medium)
Drawn in order on top of the background, later ones on top.
- sourcessequence of Source
- monitorssequence of Monitor
- mode“Ez” or “Hz”
2D polarization: Ez has the electric field along z (out of the plane, fields Ez, Hx, Hy), Hz the magnetic field along z (Ex, Ey, Hz). Ignored in 1D, which has Ez and Hy. See the module docstring of pyoptics.fdtd for the TE/TM names.
- boundariesstr, PML or sequence of them
Per axis “pml” (or a PML instance), “pec” or “periodic”; a single entry applies to all axes. Default: PML() everywhere.
- backgroundMedium
- courantnumber
Time step as a fraction of the stability limit.
- dtnumber, optional
Explicit time step (overrides courant).
- subpixelint, optional
Sub-samples per cell and axis for averaging the permittivity across material boundaries (1: staircase). Default 64 in 1D, 8 in 2D. The average is arithmetic, which is the right one for E parallel to the boundaries (1D, and the Ez mode in 2D).
- dtypenumpy dtype
float64 (default) or float32.
- property time¶
Time of the electric field (H is half a step behind).
- rasterize(component)[source]¶
Permittivity and conductivity at the nodes of a component, averaged over subpixel**ndim samples per cell.
- setup()[source]¶
Rasterize the structure, allocate the fields and set up sources and monitors (done by run() if needed).
- run(time=None, steps=None, callback=None, every=None, progress=False)[source]¶
Advance the simulation by a time span or a number of steps.
- Parameters:
- timenumber, optional
Simulated time span (rounded up to whole steps).
- stepsint, optional
- callbackcallable, optional
callback(sim), called every
everysteps (e.g. live plots).- everyint, optional
- progressbool
Print the number of steps and the run time at the end.
- energy()[source]¶
Electromagnetic energy 1/2 sum(eps E**2 + H**2) dV.
E and H are half a step apart, so this oscillates slightly around the true energy. With track_energy = True (set before stepping), the E term is eps E(n)*E(n + 1) instead, which makes it the quantity that the Yee scheme conserves exactly in lossless media inside PEC walls.