383 lines
18 KiB
Python
383 lines
18 KiB
Python
"""
|
|
|
|
Basic discrete calculus for finite difference (fd) simulations.
|
|
|
|
TODO: short description of functional vs operator form
|
|
|
|
Discrete calculus
|
|
=================
|
|
|
|
This documentation and approach is roughly based on W.C. Chew's excellent
|
|
"Electromagnetic Theory on a Lattice" (doi:10.1063/1.355770),
|
|
which covers a superset of this material with similar notation and more detail.
|
|
|
|
|
|
Derivatives and shifted values
|
|
------------------------------
|
|
|
|
Define the discrete forward derivative as
|
|
$$ [\\tilde{\\partial}_x f ]_{m + \\frac{1}{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m + 1} - f_m) $$
|
|
where \\( f \\) is a function defined at discrete locations on the x-axis (labeled using \\( m \\)).
|
|
The value at \\( m \\) occupies a length \\( \\Delta_{x, m} \\) along the x-axis. Note that \\( m \\)
|
|
is an index along the x-axis, _not_ necessarily an x-coordinate, since each length
|
|
\\( \\Delta_{x, m}, \\Delta_{x, m+1}, ...\\) is independently chosen.
|
|
|
|
If we treat `f` as a 1D array of values, with the `i`-th value `f[i]` taking up a length `dx[i]`
|
|
along the x-axis, the forward derivative is
|
|
|
|
deriv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]
|
|
|
|
|
|
Likewise, discrete reverse derivative is
|
|
$$ [\\hat{\\partial}_x f ]_{m - \\frac{1}{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m} - f_{m - 1}) $$
|
|
or
|
|
|
|
deriv_back(f)[i] = (f[i] - f[i - 1]) / dx[i]
|
|
|
|
The derivatives' values are shifted by a half-cell relative to the original function, and
|
|
will have different cell widths if all the `dx[i]` ( \\( \\Delta_{x, m} \\) ) are not
|
|
identical:
|
|
|
|
[figure: derivatives and cell sizes]
|
|
dx0 dx1 dx2 dx3 cell sizes for function
|
|
----- ----- ----------- -----
|
|
______________________________
|
|
| | | |
|
|
f0 | f1 | f2 | f3 | function
|
|
_____|_____|___________|_____|
|
|
| | | |
|
|
| Df0 | Df1 | Df2 | Df3 forward derivative (periodic boundary)
|
|
__|_____|________|________|___
|
|
|
|
dx'3] dx'0 dx'1 dx'2 [dx'3 cell sizes for forward derivative
|
|
-- ----- -------- -------- ---
|
|
dx'0] dx'1 dx'2 dx'3 [dx'0 cell sizes for reverse derivative
|
|
______________________________
|
|
| | | |
|
|
| df1 | df2 | df3 | df0 reverse derivative (periodic boundary)
|
|
__|_____|________|________|___
|
|
|
|
Periodic boundaries are used here and elsewhere unless otherwise noted.
|
|
|
|
In the above figure,
|
|
`f0 =` \\(f_0\\), `f1 =` \\(f_1\\)
|
|
`Df0 =` \\([\\tilde{\\partial}f]_{0 + \\frac{1}{2}}\\)
|
|
`Df1 =` \\([\\tilde{\\partial}f]_{1 + \\frac{1}{2}}\\)
|
|
`df0 =` \\([\\hat{\\partial}f]_{0 - \\frac{1}{2}}\\)
|
|
etc.
|
|
|
|
The fractional subscript \\( m + \\frac{1}{2} \\) is used to indicate values defined
|
|
at shifted locations relative to the original \\( m \\), with corresponding lengths
|
|
$$ \\Delta_{x, m + \\frac{1}{2}} = \\frac{1}{2} * (\\Delta_{x, m} + \\Delta_{x, m + 1}) $$
|
|
Just as \\( m \\) is not itself an x-coordinate, neither is \\( m + \\frac{1}{2} \\);
|
|
carefully note the positions of the various cells in the above figure vs their labels.
|
|
|
|
For the remainder of the `Discrete calculus` section, all figures will show
|
|
constant-length cells in order to focus on the vector derivatives themselves.
|
|
See the `Grid description` section below for additional information on this topic.
|
|
|
|
|
|
Gradients and fore-vectors
|
|
--------------------------
|
|
|
|
Expanding to three dimensions, we can define two gradients
|
|
$$ [\\tilde{\\nabla} f]_{m,n,p} = \\vec{x} [\\tilde{\\partial}_x f]_{m + \\frac{1}{2},n,p} +
|
|
\\vec{y} [\\tilde{\\partial}_y f]_{m,n + \\frac{1}{2},p} +
|
|
\\vec{z} [\\tilde{\\partial}_z f]_{m,n,p + \\frac{1}{2}} $$
|
|
$$ [\\hat{\\nabla} f]_{m,n,p} = \\vec{x} [\\hat{\\partial}_x f]_{m + \\frac{1}{2},n,p} +
|
|
\\vec{y} [\\hat{\\partial}_y f]_{m,n + \\frac{1}{2},p} +
|
|
\\vec{z} [\\hat{\\partial}_z f]_{m,n,p + \\frac{1}{2}} $$
|
|
|
|
or
|
|
|
|
[code: gradients]
|
|
grad_forward(f)[i,j,k] = [Dx_forward(f)[i, j, k],
|
|
Dy_forward(f)[i, j, k],
|
|
Dz_forward(f)[i, j, k]]
|
|
= [(f[i + 1, j, k] - f[i, j, k]) / dx[i],
|
|
(f[i, j + 1, k] - f[i, j, k]) / dy[i],
|
|
(f[i, j, k + 1] - f[i, j, k]) / dz[i]]
|
|
|
|
grad_back(f)[i,j,k] = [Dx_back(f)[i, j, k],
|
|
Dy_back(f)[i, j, k],
|
|
Dz_back(f)[i, j, k]]
|
|
= [(f[i, j, k] - f[i - 1, j, k]) / dx[i],
|
|
(f[i, j, k] - f[i, j - 1, k]) / dy[i],
|
|
(f[i, j, k] - f[i, j, k - 1]) / dz[i]]
|
|
|
|
The three derivatives in the gradient cause shifts in different
|
|
directions, so the x/y/z components of the resulting "vector" are defined
|
|
at different points: the x-component is shifted in the x-direction,
|
|
y in y, and z in z.
|
|
|
|
We call the resulting object a "fore-vector" or "back-vector", depending
|
|
on the direction of the shift. We write it as
|
|
$$ \\tilde{g}_{m,n,p} = \\vec{x} g^x_{m + \\frac{1}{2},n,p} +
|
|
\\vec{y} g^y_{m,n + \\frac{1}{2},p} +
|
|
\\vec{z} g^z_{m,n,p + \\frac{1}{2}} $$
|
|
$$ \\hat{g}_{m,n,p} = \\vec{x} g^x_{m - \\frac{1}{2},n,p} +
|
|
\\vec{y} g^y_{m,n - \\frac{1}{2},p} +
|
|
\\vec{z} g^z_{m,n,p - \\frac{1}{2}} $$
|
|
|
|
|
|
[figure: gradient / fore-vector]
|
|
(m, n+1, p+1) ______________ (m+1, n+1, p+1)
|
|
/: /|
|
|
/ : / |
|
|
/ : / |
|
|
(m, n, p+1)/_____________/ | The forward derivatives are defined
|
|
| : | | at the Dx, Dy, Dz points,
|
|
| :.........|...| but the forward-gradient fore-vector
|
|
Dz / | / is the set of all three
|
|
| Dy | / and is said to be "located" at (m,n,p)
|
|
| / | /
|
|
(m, n, p)|/_____Dx_____|/ (m+1, n, p)
|
|
|
|
|
|
|
|
Divergences
|
|
-----------
|
|
|
|
There are also two divergences,
|
|
|
|
$$ d_{n,m,p} = [\\tilde{\\nabla} \\cdot \\hat{g}]_{n,m,p}
|
|
= [\\tilde{\\partial}_x g^x]_{m,n,p} +
|
|
[\\tilde{\\partial}_y g^y]_{m,n,p} +
|
|
[\\tilde{\\partial}_z g^z]_{m,n,p} $$
|
|
|
|
$$ d_{n,m,p} = [\\hat{\\nabla} \\cdot \\tilde{g}]_{n,m,p}
|
|
= [\\hat{\\partial}_x g^x]_{m,n,p} +
|
|
[\\hat{\\partial}_y g^y]_{m,n,p} +
|
|
[\\hat{\\partial}_z g^z]_{m,n,p} $$
|
|
|
|
or
|
|
|
|
[code: divergences]
|
|
div_forward(g)[i,j,k] = Dx_forward(gx)[i, j, k] +
|
|
Dy_forward(gy)[i, j, k] +
|
|
Dz_forward(gz)[i, j, k]
|
|
= (gx[i + 1, j, k] - gx[i, j, k]) / dx[i] +
|
|
(gy[i, j + 1, k] - gy[i, j, k]) / dy[i] +
|
|
(gz[i, j, k + 1] - gz[i, j, k]) / dz[i]
|
|
|
|
div_back(g)[i,j,k] = Dx_back(gx)[i, j, k] +
|
|
Dy_back(gy)[i, j, k] +
|
|
Dz_back(gz)[i, j, k]
|
|
= (gx[i, j, k] - gx[i - 1, j, k]) / dx[i] +
|
|
(gy[i, j, k] - gy[i, j - 1, k]) / dy[i] +
|
|
(gz[i, j, k] - gz[i, j, k - 1]) / dz[i]
|
|
|
|
where `g = [gx, gy, gz]` is a fore- or back-vector field.
|
|
|
|
Since we applied the forward divergence to the back-vector (and vice-versa), the resulting scalar value
|
|
is defined at the back-vector's (fore-vectors) location \\( (m,n,p) \\) and not at the locations of its components
|
|
\\( (m \\pm \\frac{1}{2},n,p) \\) etc.
|
|
|
|
[figure: divergence]
|
|
^^
|
|
(m-1/2, n+1/2, p+1/2) _____||_______ (m+1/2, n+1/2, p+1/2)
|
|
/: || ,, /|
|
|
/ : || // / | The divergence at (m, n, p) (the center
|
|
/ : // / | of this cube) of a fore-vector field
|
|
(m-1/2, n-1/2, p+1/2)/_____________/ | is the sum of the outward-pointing
|
|
| : | | fore-vector components, which are
|
|
<==|== :.........|.====> located at the face centers.
|
|
| / | /
|
|
| / // | / Note that in a nonuniform grid, each
|
|
| / // || | / dimension is normalized by the cell width.
|
|
(m-1/2, n-1/2, p-1/2)|/___//_______|/ (m+1/2, n-1/2, p-1/2)
|
|
'' ||
|
|
VV
|
|
|
|
|
|
Curls
|
|
-----
|
|
|
|
The two curls are then
|
|
|
|
$$ \\begin{align*}
|
|
\\hat{h}_{m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}} &= \\\\
|
|
[\\tilde{\\nabla} \\times \\tilde{g}]_{m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}} &=
|
|
\\vec{x} (\\tilde{\\partial}_y g^z_{m,n,p + \\frac{1}{2}} - \\tilde{\\partial}_z g^y_{m,n + \\frac{1}{2},p}) \\\\
|
|
&+ \\vec{y} (\\tilde{\\partial}_z g^x_{m + \\frac{1}{2},n,p} - \\tilde{\\partial}_x g^z_{m,n,p + \\frac{1}{2}}) \\\\
|
|
&+ \\vec{z} (\\tilde{\\partial}_x g^y_{m,n + \\frac{1}{2},p} - \\tilde{\\partial}_y g^z_{m + \\frac{1}{2},n,p})
|
|
\\end{align*} $$
|
|
|
|
and
|
|
|
|
$$ \\tilde{h}_{m - \\frac{1}{2}, n - \\frac{1}{2}, p - \\frac{1}{2}} =
|
|
[\\hat{\\nabla} \\times \\hat{g}]_{m - \\frac{1}{2}, n - \\frac{1}{2}, p - \\frac{1}{2}} $$
|
|
|
|
where \\( \\hat{g} \\) and \\( \\tilde{g} \\) are located at \\((m,n,p)\\)
|
|
with components at \\( (m \\pm \\frac{1}{2},n,p) \\) etc.,
|
|
while \\( \\hat{h} \\) and \\( \\tilde{h} \\) are located at \\((m \\pm \\frac{1}{2}, n \\pm \\frac{1}{2}, p \\pm \\frac{1}{2})\\)
|
|
with components at \\((m, n \\pm \\frac{1}{2}, p \\pm \\frac{1}{2})\\) etc.
|
|
|
|
|
|
[code: curls]
|
|
curl_forward(g)[i,j,k] = [Dy_forward(gz)[i, j, k] - Dz_forward(gy)[i, j, k],
|
|
Dz_forward(gx)[i, j, k] - Dx_forward(gz)[i, j, k],
|
|
Dx_forward(gy)[i, j, k] - Dy_forward(gx)[i, j, k]]
|
|
|
|
curl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],
|
|
Dz_back(gx)[i, j, k] - Dx_back(gz)[i, j, k],
|
|
Dx_back(gy)[i, j, k] - Dy_back(gx)[i, j, k]]
|
|
|
|
|
|
For example, consider the forward curl, at (m, n, p), of a back-vector field `g`, defined
|
|
on a grid containing (m + 1/2, n + 1/2, p + 1/2).
|
|
The curl will be a fore-vector, so its z-component will be defined at (m, n, p + 1/2).
|
|
Take the nearest x- and y-components of `g` in the xy plane where the curl's z-component
|
|
is located; these are
|
|
|
|
[curl components]
|
|
(m, n + 1/2, p + 1/2) : x-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)
|
|
(m + 1, n + 1/2, p + 1/2) : x-component of back-vector at (m + 3/2, n + 1/2, p + 1/2)
|
|
(m + 1/2, n , p + 1/2) : y-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)
|
|
(m + 1/2, n + 1 , p + 1/2) : y-component of back-vector at (m + 1/2, n + 3/2, p + 1/2)
|
|
|
|
These four xy-components can be used to form a loop around the curl's z-component; its magnitude and sign
|
|
is set by their loop-oriented sum (i.e. two have their signs flipped to complete the loop).
|
|
|
|
[figure: z-component of curl]
|
|
: |
|
|
: ^^ |
|
|
:....||.<.....| (m, n+1, p+1/2)
|
|
/ || /
|
|
| v || | ^
|
|
| / | /
|
|
(m, n, p+1/2) |/_____>______|/ (m+1, n, p+1/2)
|
|
|
|
|
|
|
|
Maxwell's Equations
|
|
===================
|
|
|
|
If we discretize both space (m,n,p) and time (l), Maxwell's equations become
|
|
|
|
$$ \\begin{align*}
|
|
\\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}} &= -\\tilde{\\partial}_t \\hat{B}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}}
|
|
+ \\hat{M}_{l-1, \\vec{r} + \\frac{1}{2}} \\\\
|
|
\\hat{\\nabla} \\times \\hat{H}_{l,\\vec{r} + \\frac{1}{2}} &= \\hat{\\partial}_t \\tilde{D}_{l, \\vec{r}}
|
|
+ \\tilde{J}_{l-\\frac{1}{2},\\vec{r}} \\\\
|
|
\\tilde{\\nabla} \\cdot \\hat{B}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &= 0 \\\\
|
|
\\hat{\\nabla} \\cdot \\tilde{D}_{l,\\vec{r}} &= \\rho_{l,\\vec{r}}
|
|
\\end{align*} $$
|
|
|
|
with
|
|
|
|
$$ \\begin{align*}
|
|
\\hat{B}_\\vec{r} &= \\mu_{\\vec{r} + \\frac{1}{2}} \\cdot \\hat{H}_{\\vec{r} + \\frac{1}{2}} \\\\
|
|
\\tilde{D}_\\vec{r} &= \\epsilon_\\vec{r} \\cdot \\tilde{E}_\\vec{r}
|
|
\\end{align*} $$
|
|
|
|
where the spatial subscripts are abbreviated as \\( \\vec{r} = (m, n, p) \\) and
|
|
\\( \\vec{r} + \\frac{1}{2} = (m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}) \\),
|
|
\\( \\tilde{E} \\) and \\( \\hat{H} \\) are the electric and magnetic fields,
|
|
\\( \\tilde{J} \\) and \\( \\hat{M} \\) are the electric and magnetic current distributions,
|
|
and \\( \\epsilon \\) and \\( \\mu \\) are the dielectric permittivity and magnetic permeability.
|
|
|
|
The above is Yee's algorithm, written in a form analogous to Maxwell's equations.
|
|
The time derivatives can be expanded to form the update equations:
|
|
|
|
[code: Maxwell's equations]
|
|
H[i, j, k] -= (curl_forward(E[t])[i, j, k] - M[t, i, j, k]) / mu[i, j, k]
|
|
E[i, j, k] += (curl_back( H[t])[i, j, k] + J[t, i, j, k]) / epsilon[i, j, k]
|
|
|
|
Note that the E-field fore-vector and H-field back-vector are offset by a half-cell, resulting
|
|
in distinct locations for all six E- and H-field components:
|
|
|
|
[figure: Yee cell]
|
|
(m, n+1, p+1) _________________________ (m+1, n+1, p+1)
|
|
/: /|
|
|
/ : / |
|
|
/ : / | Locations of the
|
|
/ : / | E- and H-field components
|
|
/ : / | for the E fore-vector at
|
|
/ : / | r = (m, n, p) and its associated
|
|
(m, n, p+1)/________________________/ | H back-vector at r + 1/2 =
|
|
| : | | (m + 1/2, n + 1/2, p + 1/2)
|
|
| : | | (the large cube's center)
|
|
| Hx : | |
|
|
| /: :.................|......| (m+1, n+1, p)
|
|
|/ : / | /
|
|
Ez..........Hy | /
|
|
| Ey.......:..Hz | / This is the Yee discretization
|
|
| / : / | / scheme ("Yee cell").
|
|
| / : / | /
|
|
|/ :/ | /
|
|
r=(m, n, p)|___________Ex___________|/ (m+1, n, p)
|
|
|
|
|
|
Each component forms its own grid, offset from the others:
|
|
|
|
[figure: E-fields for adjacent cells]
|
|
________Ex(p+1, m+1)_____
|
|
/: /|
|
|
/ : / |
|
|
/ : / |
|
|
Ey(p+1) Ey(m+1, p+1)
|
|
/ : / |
|
|
/ Ez(n+1) / Ez(m+1, n+1)
|
|
/__________Ex(p+1)_______/ |
|
|
| : | |
|
|
| : | | This figure shows which fore-vector
|
|
| : | | each e-field component belongs to.
|
|
| :.........Ex(n+1).|......| Indices are shortened; e.g. Ex(p+1)
|
|
| / | / means "Ex for the fore-vector located
|
|
Ez / Ez(m+1)/ at (m, n, p+1)".
|
|
| Ey | /
|
|
| / | Ey(m+1)
|
|
| / | /
|
|
|/ | /
|
|
r=(m, n, p)|___________Ex___________|/
|
|
|
|
|
|
The divergence equations can be derived by taking the divergence of the curl equations
|
|
and combining them with charge continuity,
|
|
$$ \\hat{\\nabla} \\cdot \\tilde{J} + \\hat{\\partial}_t \\rho = 0 $$
|
|
implying that the discrete Maxwell's equations do not produce spurious charges.
|
|
|
|
|
|
Wave equation
|
|
-------------
|
|
|
|
Taking the backward curl of the \\( \\tilde{\\nabla} \\times \\tilde{E} \\) equation and
|
|
replacing the resulting \\( \\hat{\\nabla} \\times \\hat{H} \\) term using its respective equation,
|
|
and setting \\( \\hat{M} \\) to zero, we can form the discrete wave equation:
|
|
|
|
$$
|
|
\\begin{align*}
|
|
\\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}} &=
|
|
-\\tilde{\\partial}_t \\hat{B}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}}
|
|
+ \\hat{M}_{l-1, \\vec{r} + \\frac{1}{2}} \\\\
|
|
\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}} &=
|
|
-\\tilde{\\partial}_t \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} \\\\
|
|
\\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &=
|
|
\\hat{\\nabla} \\times (-\\tilde{\\partial}_t \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}}) \\\\
|
|
\\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &=
|
|
-\\tilde{\\partial}_t \\hat{\\nabla} \\times \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} \\\\
|
|
\\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &=
|
|
-\\tilde{\\partial}_t \\hat{\\partial}_t \\epsilon_\\vec{r} \\tilde{E}_{l, \\vec{r}} + \\hat{\\partial}_t \\tilde{J}_{l-\\frac{1}{2},\\vec{r}} \\\\
|
|
\\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l, \\vec{r}})
|
|
+ \\tilde{\\partial}_t \\hat{\\partial}_t \\epsilon_\\vec{r} \\cdot \\tilde{E}_{l, \\vec{r}}
|
|
&= \\tilde{\\partial}_t \\tilde{J}_{l - \\frac{1}{2}, \\vec{r}}
|
|
\\end{align*}
|
|
$$
|
|
|
|
|
|
|
|
Grid description
|
|
================
|
|
|
|
The
|
|
|
|
TODO: explain dxes
|
|
|
|
"""
|
|
|
|
from .types import fdfield_t, vfdfield_t, dx_lists_t, fdfield_updater_t
|
|
from .vectorization import vec, unvec
|
|
from . import operators, functional, types, vectorization
|
|
|