FDTD for Electric Fields¶
This article explains the concept behind the finite difference time domain technique, derives the equations and implements them in a Python script. The simulation result is shown in Figure 1.
Figure 1. E-field reflection at an air/glass interface
Maxwell’s Law tells us that a time-varying electric displacement field, D [C/m^2], induces a magnetic field strength, H [A/m]:
In 1D, the curl operator reduces to a spatial derivative. The finite difference approximation is used to get the subsequent D value given the H value and the previous D value:
where the sampling limit formula has been used, \(c \Delta t = ℓ/2\) (c being the speed of light in a vacuum). Note that the fields are interleaved; while D is aligned in space but misaligned in time, H is aligned in time but misaligned in space. A similar treatment is applied to the magnetic flux density, B [T], and the electric field, E [V/m]
FDTD uses a constitutive relation between two (or more) physical properties to iteratively solve for field data. At each time step, Property A will be a function of the previous state(s) of Property A and also of Property B. Similarly, Property B will be a function of its own previous state(s) and of Property A. With electric field simulations, these two properties are either the electric and magnetic fields, or the electric and displacement fields. This explanation uses the latter. The electric displacement field [C/m^2] equals the electrical permittivity [F/m] multiplied by the electric field [V/m]:
test
where \(\epsilon_r\) is the material-specific relative permittivity [unitless, 0 to 1] and \(\epsilon_0\) is the permittivity of free space. \(\epsilon_r\) is oftentimes a number from a lookup table, an estimate suitable for the frequency range of the simulation. We will use the Debye Relaxation Model,
where \(\tau\) is the Debye relaxation time, \(\epsilon_{\infty}\) is the permittivity at the high frequency limit and \(\epsilon_{\Delta}\) is the DC permittivity minus the high-frequency value. These values are available in the literature for many different materials. The Debye Model is expanded to account for conductivity, \(\sigma\),
This expanded \(\epsilon_r\) serves as a useful general case for FDTD. If the Debye constants are not known, an operator can simply set \(\epsilon_{\Delta}\) to zero and \(\epsilon_{\infty}\) to an estimate suitable for the frequency range of the simulation. Likewise, the conductivity term may be skipped by using \(\sigma = 0\).
To go from the complex frequency domain to the time domain we use the Z-Transform, the discrete version of the Laplace Transform. The Z-Transform is often used in communications as a delay operator: if \(y(n) = x(n-1)\) then \(Y(z) = z^{-1}X(z)\). The following Z-Transform pair, taken from a lookup table, is needed:
\(a(\omega)\) is taken to the z-domain, multiplied by E(z) and rearranged to produce an displacement field contribution:
\(b(\omega)\) is given a similar treatment:
By substitution into the original displacement field equation,
Rearranging to solve for E(z),
We now have D(z) in terms of E(z) and previous values of itself. And, we have E(z) in terms of D(z) and previous values of itself. The criteria have been met for setting up an FDTD simulation. The equations are programmed into fdtd.py, below. This file contains the FDTD computations while ancillary functions, e.g. for plotting, are in util.py. The two files must be in the same directory before running fdtd.py.
1import numpy as np
2from util import scalarFields2Arrays, Medium, Source, render
3
4EP0 = 8.8541878128e-12 # permittivity of free space, F/m
5C0 = 299792458 # speed of light, m/s
6
7def fdtd(Nts, dt, dz, mdms, srcs, ylim_E=[-1,1], ylim_H=[-1,1], dispNth=1):
8 Ncells = np.sum([m.L for m in mdms])
9 epR,sigma,chi1,t0 = scalarFields2Arrays(mdms,'epR','sigma','chi1','t0')
10 axE,axH,pE,pH = None,None,None,None
11 Ex,Hy,Dx,Ix,Sx = [np.zeros(Ncells) for _ in range(5)]
12 Ex1_beforeLast, Ex1_last, ExN_beforeLast, ExN_last = 0,0,0,0
13
14 for n in range(1, Nts+1): # for each time step
15 t = n*dt
16
17 # D from H
18 for k in range(Ncells):
19 Dx[k] += 0.5*(Hy[k-1] - Hy[k])
20
21 # E from D
22 for k in range(Ncells-1):
23 Ex[k] = (Dx[k] - Ix[k] - np.exp(-dt/t0[k])*Sx[k])
24 Ex[k] = Ex[k] / (epR[k] + sigma[k]*dt/EP0 + chi1[k]*dt/t0[k])
25 Ix[k] = Ix[k] + sigma[k]*dt/EP0*Ex[k]
26 Sx[k] = np.exp(-dt/t0[k])*Sx[k] + chi1[k]*dt/t0[k]*Ex[k]
27
28 # Sources
29 for src in srcs:
30 i = int(round(src.z01*Ncells))
31 Ex[i] += src.Ex(t)
32
33 # Absorbing boundary condition
34 if True:
35 Ex[0] = Ex1_beforeLast
36 Ex1_beforeLast = Ex1_last
37 Ex1_last = Ex[1]
38
39 Ex[Ncells-1] = ExN_beforeLast
40 ExN_beforeLast = ExN_last
41 ExN_last = Ex[Ncells-2]
42
43 # H from E
44 for k in range(Ncells-1):
45 Hy[k] += 0.5*(Ex[k] - Ex[k+1])
46
47 if not (n-1) % dispNth: # render on every dispNth step
48 axE,axH,pE,pH = render(axE, axH, pE, pH, Ex, Hy, ylim_E, ylim_H, mdms)
49# plt.savefig('images/%d.png' % n)
50
51if __name__ == '__main__':
52 N = 160 # number of time steps
53 dz = .01 # length of each cell
54 dt = 2*dz/C0 # time step
55 ms = [Medium(50, epR=1, sigma=.002)] # air
56 ms.append(Medium(25, epR=10, sigma=.001, chi1=2, t0=1e-9)) # glass
57 srcs = [Source(40*dt, 1/3, Source.gaussian, A=7, sigma=5*dt)]
58 fdtd(N, dt, dz, ms, srcs)