From 693e3af8fa767294f79f822885df2f7a580ae4c8 Mon Sep 17 00:00:00 2001
From: Forgejo Actions
From the "Frequency domain" section of meanas.fdmath, we have
resulting in
Maxwell's equations are then
With \(\Delta_t \to 0\), this simplifies to
and then
This vanishes in the interior of the total-field and scattered-field regions
and is supported only at their shared boundary, where the mask discontinuity
makes A and Q fail to commute. The returned current is therefore the
@@ -2416,11 +2422,15 @@ the meanas.fdmath.types submodule for details.
Wave operator - $$ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon $$
+ +del x (1/mu * del x) - omega**2 * epsilon
for use with the E-field, with wave equation - $$ (\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E = -\imath \omega J $$
+ +(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * J
To make this matrix symmetric, use the preconditioners from e_full_preconditioners().
pmc.size == epsilon.size
Wave operator
- $$ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu $$
+
+\[ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu \]
+
del x (1/epsilon * del x) - omega**2 * mu
for use with the H-field, with wave equation
- $$ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M $$
+
+\[ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M \]
+
(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * M
@@ -2855,25 +2869,30 @@ The PMC is applied per-field-component (i.e. pmc.size == epsilon.size
Wave operator for [E, H] field representation. This operator implements Maxwell's
- equations without cancelling out either E or H. The operator is
-$$ \begin{bmatrix}
- -\imath \omega \epsilon & \nabla \times \
- \nabla \times & \imath \omega \mu
- \end{bmatrix} $$
+ equations without cancelling out either E or H. The operator is
+\[
+\begin{bmatrix}
+ -\imath \omega \epsilon & \nabla \times \\
+ \nabla \times & \imath \omega \mu
+\end{bmatrix}
+\]
+
[[-i * omega * epsilon, del x ],
[del x, i * omega * mu]]
-for use with a field vector of the form cat(vec(E), vec(H)):
-$$ \begin{bmatrix}
- -\imath \omega \epsilon & \nabla \times \
- \nabla \times & \imath \omega \mu
+
for use with a field vector of the form cat(vec(E), vec(H)):
+\[
+\begin{bmatrix}
+ -\imath \omega \epsilon & \nabla \times \\
+ \nabla \times & \imath \omega \mu
\end{bmatrix}
- \begin{bmatrix} E \
+ \begin{bmatrix} E \\
H
\end{bmatrix}
- = \begin{bmatrix} J \
+ = \begin{bmatrix} J \\
-M
- \end{bmatrix} $$
+\end{bmatrix}
+\]
Parameters:
@@ -3408,6 +3427,7 @@ usual antisymmetry of the cross product,
\[
H \times E = -(E \times H),
\]
+
once the same staggered field placement is used on both sides.
@@ -3527,6 +3547,7 @@ Then the TFSF current operator is the commutator
\[
\frac{A Q - Q A}{-i \omega}.
\]
+
Inside regions where Q is locally constant, A and Q commute and the
source vanishes. Only cells at the TF/SF boundary contribute nonzero current,
which is exactly the desired distributed source for injecting the chosen
diff --git a/api/fdmath/index.html b/api/fdmath/index.html
index 61228f3..d687628 100644
--- a/api/fdmath/index.html
+++ b/api/fdmath/index.html
@@ -1903,8 +1903,9 @@ series of other matrices).
which covers a superset of this material with similar notation and more detail.
Scalar derivatives and cell shifts¶
Define the discrete forward derivative as
- $$ [\tilde{\partial}x f] - f_m) $$
- where }{2}} = \frac{1}{\Delta_{x, m}} (f_{m + 1\(f\) is a function defined at discrete locations on the x-axis (labeled using \(m\)).
+
+
\[ [\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.
@@ -1913,8 +1914,9 @@ 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 ]) $$
- or}{2}} = \frac{1}{\Delta_{x, m}} (f_{m} - f_{m - 1
+
+\[ [\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
@@ -1949,7 +1951,9 @@ Periodic boundaries are used here and elsewhere unless otherwise noted.
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}) $$
+
+\[ \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.
If the positions labeled with \(m\) are considered the "base" or "original" grid,
@@ -1961,12 +1965,18 @@ See the Grid description section below for additional information o
and generalization to three dimensions.
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] +
- \vec{y} [\tilde{\partial}}{2},n,py f] +
- \vec{z} [\tilde{\partial}}{2},pz f] $$
- $$ [\hat{\nabla} f]}{2}{m,n,p} = \vec{x} [\hat{\partial}_x f] +
- \vec{y} [\hat{\partial}}{2},n,py f] +
- \vec{z} [\hat{\partial}}{2},pz f] $$}{2}
+
+\[
+ [\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],
@@ -1989,12 +1999,18 @@ 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 +
+
+
\[
+ \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}}{2},n,p{m,n,p} = \vec{x} g^x +
+ \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}} $$}{2},n,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)
/: /|
@@ -2010,14 +2026,20 @@ on the direction of the shift. We write it as
Divergences¶
There are also two divergences,
-$$ d_{n,m,p} = [\tilde{\nabla} \cdot \hat{g}]{n,m,p}
- = [\tilde{\partial}_x g^x] +
- [\tilde{\partial}y g^y] +
- [\tilde{\partial}z g^z] $$
-$$ d_{n,m,p} = [\hat{\nabla} \cdot \tilde{g}]{n,m,p}
- = [\hat{\partial}_x g^x] +
- [\hat{\partial}y g^y] +
- [\hat{\partial}z g^z] $$
+\[
+ 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] +
@@ -2056,16 +2078,22 @@ is defined at the back-vector's (fore-vector's) location Curls¶
The two curls are then
-$$ \begin{aligned}
- \hat{h}{m + \frac{1}{2}, n + \frac{1}{2}, p + \frac{1}{2}} &= \
- [\tilde{\nabla} \times \tilde{g}] &=
- \vec{x} (\tilde{\partial}}{2}, n + \frac{1}{2}, p + \frac{1}{2}y g^z}{2}} - \tilde{\partialz g^y) \
- &+ \vec{y} (\tilde{\partial}}{2},pz g^x}{2},n,p} - \tilde{\partialx g^z) \
- &+ \vec{z} (\tilde{\partial}}{2}x g^y}{2},p} - \tilde{\partialy g^z)
- \end{aligned} $$}{2},n,p
+\[
+ \begin{aligned}
+ \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{aligned}
+ \]
+
and
-$$ \tilde{h}{m - \frac{1}{2}, n - \frac{1}{2}, p - \frac{1}{2}} =
- [\hat{\nabla} \times \hat{g}] $$}{2}, n - \frac{1}{2}, p - \frac{1}{2}
+\[
+ \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})\)
@@ -2103,19 +2131,25 @@ curl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],
Maxwell's Equations¶
If we discretize both space (m,n,p) and time (l), Maxwell's equations become
-$$ \begin{aligned}
- \tilde{\nabla} \times \tilde{E}{l,\vec{r}} &= -\tilde{\partial}_t \hat{B}
- - \hat{M}}{2}, \vec{r} + \frac{1}{2}{l, \vec{r} + \frac{1}{2}} \
- \hat{\nabla} \times \hat{H}}{2},\vec{r} + \frac{1}{2}} &= \hat{\partialt \tilde{D}
- + \tilde{J}}{l-\frac{1}{2},\vec{r}} \
- \tilde{\nabla} \cdot \hat{B} &= 0 \
- \hat{\nabla} \cdot \tilde{D}}{2}, \vec{r} + \frac{1}{2}{l,\vec{r}} &= \rho
- \end{aligned} $$}
+\[
+ \begin{aligned}
+ \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, \vec{r} + \frac{1}{2}} \\
+ \hat{\nabla} \times \hat{H}_{l-\frac{1}{2},\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{aligned}
+ \]
+
with
-$$ \begin{aligned}
- \hat{B}{\vec{r}} &= \mu} + \frac{1}{2}} \cdot \hat{H{\vec{r} + \frac{1}{2}} \
- \tilde{D}
- \end{aligned} $$}} &= \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}
+\[
+ \begin{aligned}
+ \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{aligned}
+ \]
+
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,
@@ -2177,94 +2211,108 @@ r - 1/2 = | / | /
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.
+
+\[ \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{aligned}
- \tilde{\nabla} \times \tilde{E}_{l,\vec{r}} &=
+ \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}} &=
+ \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 (\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}}) &=
+ \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}}) &=
+ \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}}
+ &= \tilde{\partial}_t \tilde{J}_{l - \frac{1}{2}, \vec{r}}
\end{aligned}
\]
+
Frequency domain¶
We can substitute in a time-harmonic fields
\[
\begin{aligned}
- \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\
- \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t}
+ \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\
+ \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t}
\end{aligned}
\]
+
resulting in
\[
\begin{aligned}
- \tilde{\partial}_t &\Rightarrow (e^{ \imath \omega \Delta_t} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{-\imath \omega \Delta_t / 2} = -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\
- \hat{\partial}_t &\Rightarrow (1 - e^{-\imath \omega \Delta_t}) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{ \imath \omega \Delta_t / 2} = -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\
- \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t
+ \tilde{\partial}_t &\Rightarrow (e^{ \imath \omega \Delta_t} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{-\imath \omega \Delta_t / 2} = -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\
+ \hat{\partial}_t &\Rightarrow (1 - e^{-\imath \omega \Delta_t}) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{ \imath \omega \Delta_t / 2} = -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\
+ \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t
\end{aligned}
\]
+
This gives the frequency-domain wave equation,
\[
\hat{\nabla} \times (\mu^{-1}_{\vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{\vec{r}})
-\Omega^2 \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}} = -\imath \Omega \tilde{J}_{\vec{r}} e^{\imath \omega \Delta_t / 2} \\
\]
+
Plane waves and Dispersion relation¶
With uniform material distribution and no sources
\[
\begin{aligned}
- \mu_{\vec{r} + \frac{1}{2}} &= \mu \\
- \epsilon_{\vec{r}} &= \epsilon \\
- \tilde{J}_{\vec{r}} &= 0 \\
+ \mu_{\vec{r} + \frac{1}{2}} &= \mu \\
+ \epsilon_{\vec{r}} &= \epsilon \\
+ \tilde{J}_{\vec{r}} &= 0 \\
\end{aligned}
\]
+
the frequency domain wave equation simplifies to
\[ \hat{\nabla} \times \tilde{\nabla} \times \tilde{E}_{\vec{r}} - \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]
+
Since \(\hat{\nabla} \cdot \tilde{E}_{\vec{r}} = 0\), we can simplify
\[
\begin{aligned}
\hat{\nabla} \times \tilde{\nabla} \times \tilde{E}_{\vec{r}}
- &= \tilde{\nabla}(\hat{\nabla} \cdot \tilde{E}_{\vec{r}}) - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\
- &= - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\
- &= - \tilde{\nabla}^2 \tilde{E}_{\vec{r}}
+ &= \tilde{\nabla}(\hat{\nabla} \cdot \tilde{E}_{\vec{r}}) - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\
+ &= - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\
+ &= - \tilde{\nabla}^2 \tilde{E}_{\vec{r}}
\end{aligned}
\]
+
and we get
-\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]
+\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]
+
We can convert this to three scalar-wave equations of the form
\[ (\tilde{\nabla}^2 + K^2) \phi_{\vec{r}} = 0 \]
+
with \(K^2 = \Omega^2 \mu \epsilon\). Now we let
-\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]
+\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]
+
resulting in
\[
\begin{aligned}
- \tilde{\partial}_x &\Rightarrow (e^{ \imath k_x \Delta_x} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{ \imath k_x \Delta_x / 2} = \imath K_x e^{ \imath k_x \Delta_x / 2}\\
- \hat{\partial}_x &\Rightarrow (1 - e^{-\imath k_x \Delta_x}) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{-\imath k_x \Delta_x / 2} = \imath K_x e^{-\imath k_x \Delta_x / 2}\\
- K_x &= 2 \sin(k_x \Delta_x / 2) / \Delta_x \\
+ \tilde{\partial}_x &\Rightarrow (e^{ \imath k_x \Delta_x} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{ \imath k_x \Delta_x / 2} = \imath K_x e^{ \imath k_x \Delta_x / 2}\\
+ \hat{\partial}_x &\Rightarrow (1 - e^{-\imath k_x \Delta_x}) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{-\imath k_x \Delta_x / 2} = \imath K_x e^{-\imath k_x \Delta_x / 2}\\
+ K_x &= 2 \sin(k_x \Delta_x / 2) / \Delta_x \\
\end{aligned}
\]
+
with similar expressions for the y and z dimnsions (and \(K_y, K_z\)).
This implies
\[
\tilde{\nabla}^2 = -(K_x^2 + K_y^2 + K_z^2) \phi_{\vec{r}} \\
K_x^2 + K_y^2 + K_z^2 = \Omega^2 \mu \epsilon = \Omega^2 / c^2
\]
+
where \(c = \sqrt{\mu \epsilon}\).
Assuming real \((k_x, k_y, k_z), \omega\) will be real only if
-\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]
+\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]
+
If \(\Delta_x = \Delta_y = \Delta_z\), this simplifies to \(c \Delta_t < \Delta_x / \sqrt{3}\).
This last form can be interpreted as enforcing causality; the distance that light
travels in one timestep (i.e., \(c \Delta_t\)) must be less than the diagonal
@@ -2461,15 +2509,16 @@ mu = [mu_xx, mu_yy, mu_zz]
or
where the off-diagonal terms (e.g. epsilon_xy) are assumed to be zero.
High-accuracy volumetric integration of shapes on multiple grids can be performed by the gridlock module.
diff --git a/api/fdtd/index.html b/api/fdtd/index.html index 6511297..2b544da 100644 --- a/api/fdtd/index.html +++ b/api/fdtd/index.html @@ -809,6 +809,50 @@ + + + temporal_phasor
+
+
+
+
+ temporal_phasor_scale
+
+
+
+
+ real_injection_scale
+
+
+
+
+ reconstruct_real
+
+
+
+
reconstruct_real_e
+
+
+
+
+ reconstruct_real_h
+
+
+
+
+ reconstruct_real_j
+
+
+
+
temporal_phasor
+
+
+
+
+ temporal_phasor_scale
+
+
+
+
+ real_injection_scale
+
+
+
+
+ reconstruct_real
+
+
+
+
reconstruct_real_e
+
+
+
+
+ reconstruct_real_h
+
+
+
+
+ reconstruct_real_j
+
+
+
+
From the discussion of "Plane waves and the Dispersion relation" in meanas.fdmath,
we have
or, if \(\Delta_x = \Delta_y = \Delta_z\), then \(c \Delta_t < \frac{\Delta_x}{\sqrt{3}}\).
Based on this, we can set
dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2)
@@ -1309,54 +1464,58 @@ we have
The dx_min, dy_min, dz_min should be the minimum value across both the base and derived grids.
Poynting Vector and Energy Conservation¶
Let
-\[ \begin{aligned}
- \tilde{S}_{l, l', \vec{r}} &=& &\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}} \\
- &=& &\vec{x} (\tilde{E}^y_{l,m+1,n,p} \hat{H}^z_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^z_{l,m+1,n,p} \hat{H}^y_{l', \vec{r} + \frac{1}{2}}) \\
- & &+ &\vec{y} (\tilde{E}^z_{l,m,n+1,p} \hat{H}^x_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^x_{l,m,n+1,p} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \\
- & &+ &\vec{z} (\tilde{E}^x_{l,m,n,p+1} \hat{H}^y_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^y_{l,m,n,p+1} \hat{H}^z_{l', \vec{r} + \frac{1}{2}})
+\[
+\begin{aligned}
+ \tilde{S}_{l, l', \vec{r}} &=& &\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}} \\
+ &=& &\vec{x} (\tilde{E}^y_{l,m+1,n,p} \hat{H}^z_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^z_{l,m+1,n,p} \hat{H}^y_{l', \vec{r} + \frac{1}{2}}) \\
+ & &+ &\vec{y} (\tilde{E}^z_{l,m,n+1,p} \hat{H}^x_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^x_{l,m,n+1,p} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \\
+ & &+ &\vec{z} (\tilde{E}^x_{l,m,n,p+1} \hat{H}^y_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^y_{l,m,n,p+1} \hat{H}^z_{l', \vec{r} + \frac{1}{2}})
\end{aligned}
\]
+
where \(\vec{r} = (m, n, p)\) and \(\otimes\) is a modified cross product
in which the \(\tilde{E}\) terms are shifted as indicated.
By taking the divergence and rearranging terms, we can show that
\[
\begin{aligned}
\hat{\nabla} \cdot \tilde{S}_{l, l', \vec{r}}
- &= \hat{\nabla} \cdot (\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}}) \\
- &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{l, \vec{r}} -
+ &= \hat{\nabla} \cdot (\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}}) \\
+ &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{l, \vec{r}} -
\tilde{E}_{l, \vec{r}} \cdot \hat{\nabla} \times \hat{H}_{l', \vec{r} + \frac{1}{2}} \\
- &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot
+ &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot
(-\tilde{\partial}_t \mu_{\vec{r} + \frac{1}{2}} \hat{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} -
\hat{M}_{l, \vec{r} + \frac{1}{2}}) -
\tilde{E}_{l, \vec{r}} \cdot (\hat{\partial}_t \tilde{\epsilon}_{\vec{r}} \tilde{E}_{l'+\frac{1}{2}, \vec{r}} +
\tilde{J}_{l', \vec{r}}) \\
- &= \hat{H}_{l'} \cdot (-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) -
+ &= \hat{H}_{l'} \cdot (-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) -
\tilde{E}_l \cdot (\epsilon / \Delta_t )(\tilde{E}_{l'+\frac{1}{2}} - \tilde{E}_{l'-\frac{1}{2}})
- \hat{H}_{l'} \cdot \hat{M}_{l} - \tilde{E}_l \cdot \tilde{J}_{l'} \\
\end{aligned}
\]
+
where in the last line the spatial subscripts have been dropped to emphasize
the time subscripts \(l, l'\), i.e.
\[
\begin{aligned}
- \tilde{E}_l &= \tilde{E}_{l, \vec{r}} \\
- \hat{H}_l &= \tilde{H}_{l, \vec{r} + \frac{1}{2}} \\
- \tilde{\epsilon} &= \tilde{\epsilon}_{\vec{r}} \\
+ \tilde{E}_l &= \tilde{E}_{l, \vec{r}} \\
+ \hat{H}_l &= \tilde{H}_{l, \vec{r} + \frac{1}{2}} \\
+ \tilde{\epsilon} &= \tilde{\epsilon}_{\vec{r}} \\
\end{aligned}
\]
+
etc.
For \(l' = l + \frac{1}{2}\) we get
\[
\begin{aligned}
\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}}
- &= \hat{H}_{l + \frac{1}{2}} \cdot
+ &= \hat{H}_{l + \frac{1}{2}} \cdot
(-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) -
\tilde{E}_l \cdot (\epsilon / \Delta_t)(\tilde{E}_{l+1} - \tilde{E}_l)
- \hat{H}_{l'} \cdot \hat{M}_l - \tilde{E}_l \cdot \tilde{J}_{l + \frac{1}{2}} \\
- &= (-\mu / \Delta_t)(\hat{H}^2_{l + \frac{1}{2}} - \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}) -
+ &= (-\mu / \Delta_t)(\hat{H}^2_{l + \frac{1}{2}} - \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}) -
(\epsilon / \Delta_t)(\tilde{E}_{l+1} \cdot \tilde{E}_l - \tilde{E}^2_l)
- \hat{H}_{l'} \cdot \hat{M}_l - \tilde{E}_l \cdot \tilde{J}_{l + \frac{1}{2}} \\
- &= -(\mu \hat{H}^2_{l + \frac{1}{2}}
+ &= -(\mu \hat{H}^2_{l + \frac{1}{2}}
+\epsilon \tilde{E}_{l+1} \cdot \tilde{E}_l) / \Delta_t \\
+(\mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}
+\epsilon \tilde{E}^2_l) / \Delta_t \\
@@ -1364,11 +1523,12 @@ For \(l' = l + \frac{1}{2}\) we get
- \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\
\end{aligned}
\]
+
and for \(l' = l - \frac{1}{2}\),
\[
\begin{aligned}
\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}}
- &= (\mu \hat{H}^2_{l - \frac{1}{2}}
+ &= (\mu \hat{H}^2_{l - \frac{1}{2}}
+\epsilon \tilde{E}_{l-1} \cdot \tilde{E}_l) / \Delta_t \\
-(\mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}
+\epsilon \tilde{E}^2_l) / \Delta_t \\
@@ -1376,28 +1536,31 @@ For \(l' = l + \frac{1}{2}\) we get
- \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\
\end{aligned}
\]
+
These two results form the discrete time-domain analogue to Poynting's theorem.
They hint at the expressions for the energy, which can be calculated at the same
time-index as either the E or H field:
\[
\begin{aligned}
- U_l &= \epsilon \tilde{E}^2_l + \mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} \\
- U_{l + \frac{1}{2}} &= \epsilon \tilde{E}_l \cdot \tilde{E}_{l + 1} + \mu \hat{H}^2_{l + \frac{1}{2}} \\
+ U_l &= \epsilon \tilde{E}^2_l + \mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} \\
+ U_{l + \frac{1}{2}} &= \epsilon \tilde{E}_l \cdot \tilde{E}_{l + 1} + \mu \hat{H}^2_{l + \frac{1}{2}} \\
\end{aligned}
\]
+
Rewriting the Poynting theorem in terms of the energy expressions,
\[
\begin{aligned}
(U_{l+\frac{1}{2}} - U_l) / \Delta_t
- &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\
+ &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\
- \hat{H}_{l+\frac{1}{2}} \cdot \hat{M}_l \\
- \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\
(U_l - U_{l-\frac{1}{2}}) / \Delta_t
- &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\
+ &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\
- \hat{H}_{l-\frac{1}{2}} \cdot \hat{M}_l \\
- \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\
\end{aligned}
\]
+
This result is exact and should practically hold to within numerical precision. No time-
or spatial-averaging is necessary.
Note that each value of \(J\) contributes to the energy twice (i.e. once per field update)
@@ -1408,16 +1571,46 @@ extract the frequency-domain response by performing an on-the-fly Fourier transf
of the time-domain fields.
accumulate_phasor in meanas.fdtd.phasor performs the phase accumulation for one
or more target frequencies, while leaving source normalization and simulation-loop
-policy to the caller. Convenience wrappers accumulate_phasor_e,
-accumulate_phasor_h, and accumulate_phasor_j apply the usual Yee time offsets.
+policy to the caller. temporal_phasor(...) and temporal_phasor_scale(...)
+apply the same Fourier sum to a scalar waveform, which is useful when a pulsed
+source envelope must be normalized before being applied to a point source or
+mode source. real_injection_scale(...) is the corresponding helper for the
+common real-valued injection pattern numpy.real(scale * waveform). Convenience
+wrappers accumulate_phasor_e, accumulate_phasor_h, and accumulate_phasor_j
+apply the usual Yee time offsets. reconstruct_real(...) and the corresponding
+reconstruct_real_e/h/j(...) wrappers perform the inverse operation, converting
+phasors back into real-valued field snapshots at explicit Yee-aligned times.
+For scalar omega, the reconstruction helpers accept either a plain field
+phasor or the batched (1, *sample_shape) form used by the accumulator helpers.
The helpers accumulate
\[ \Delta_t \sum_l w_l e^{-i \omega t_l} f_l \]
+
with caller-provided sample times and weights. In this codebase the matching
electric-current convention is typically E -= dt * J / epsilon in FDTD and
-i \omega J on the right-hand side of the FDFD wave equation.
+For FDTD/FDFD equivalence, this phasor path is the primary comparison workflow.
+It isolates the guided +\omega response that the frequency-domain solver
+targets directly, regardless of whether the underlying FDTD run used real- or
+complex-valued fields.
+For exact pulsed FDTD/FDFD equivalence it is often simplest to keep the
+injected source, fields, and CPML auxiliary state complex-valued. The
+real_injection_scale(...) helper is instead for the more ordinary one-run
+real-valued source path, where the intended positive-frequency waveform is
+injected through numpy.real(scale * waveform) and any remaining negative-
+frequency leakage is controlled by the pulse bandwidth and run length.
+reconstruct_real(...) is for a different question: given a phasor, what late
+real-valued field snapshot should it produce? That raw-snapshot comparison is
+stricter and noisier because a monitor plane generally contains both the guided
+field and the remaining orthogonal content,
+\[ E_{\text{monitor}} = E_{\text{guided}} + E_{\text{residual}} . \]
+
+Phasor/modal comparisons mostly validate the guided +\omega term. Raw
+real-field comparisons expose both terms at once, so they should be treated as
+secondary diagnostics rather than the main solver-equivalence benchmark.
The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse
shape. It can be written
\[ f_r(t) = (1 - \frac{1}{2} (\omega (t - \tau))^2) e^{-(\frac{\omega (t - \tau)}{2})^2} \]
+
with \(\tau > \frac{2 * \pi}{\omega}\) as a minimum delay to avoid a discontinuity at
t=0 (assuming the source is off for t<0 this gives \(\sim 10^{-3}\) error at t=0).
Boundary conditions¶
@@ -1447,6 +1640,7 @@ coordinate system in both places:
\[
E \leftarrow E - \Delta_t J / \epsilon
\]
+
which matches the FDFD right-hand side
\[
-i \omega J.
@@ -2390,19 +2584,20 @@ boundaries of the corresponding U cell. For example,
assert_close(sum(planes), du_half_h2e[mask])
(see meanas.tests.test_fdtd.test_poynting_planes)
-The full relationship is
-$$
+
The full relationship is
+\[
\begin{aligned}
(U_{l+\frac{1}{2}} - U_l) / \Delta_t
- &= -\hat{\nabla} \cdot \tilde{S}{l, l + \frac{1}{2}} \
- - \hat{H}}{2}} \cdot \hat{Ml \
- - \tilde{E}_l \cdot \tilde{J} \
+ &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\
+ - \hat{H}_{l+\frac{1}{2}} \cdot \hat{M}_l \\
+ - \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\
(U_l - U_{l-\frac{1}{2}}) / \Delta_t
- &= -\hat{\nabla} \cdot \tilde{S}}{2}{l, l - \frac{1}{2}} \
- - \hat{H}}{2}} \cdot \hat{Ml \
- - \tilde{E}_l \cdot \tilde{J} \
+ &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\
+ - \hat{H}_{l-\frac{1}{2}} \cdot \hat{M}_l \\
+ - \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\
\end{aligned}
-$$}{2}
+\]
+
These equalities are exact and should practically hold to within numerical precision.
No time- or spatial-averaging is necessary. (See meanas.fdtd docs for derivation.)
@@ -3579,6 +3774,18 @@ the sampling policy. The accumulated quantity is
accumulate_phasor_h(..., step=l) for H_{l + 1/2}
accumulate_phasor_j(..., step=l) for J_{l + 1/2}
+temporal_phasor(...) and temporal_phasor_scale(...) apply the same Fourier
+sum to a 1D scalar waveform. They are useful for normalizing a pulsed source
+before that scalar waveform is applied to a point source or spatial mode source.
+real_injection_scale(...) is a companion helper for the common real-valued
+injection pattern numpy.real(scale * waveform), where waveform is the
+analytic positive-frequency signal and the injected real current should still
+produce a desired phasor response.
+reconstruct_real(...) and its E/H/J wrappers perform the inverse operation:
+they turn one or more phasors back into real-valued field snapshots at explicit
+Yee-aligned sample times. For a scalar target frequency they accept either a
+plain field phasor or the batched (1, *sample_shape) form used elsewhere in
+this module.
These helpers do not choose warmup/accumulation windows or pulse-envelope
normalization. They also do not impose a current sign convention. In this
codebase, electric-current injection normally appears as E -= dt * J / epsilon,
@@ -3650,6 +3857,154 @@ factor was derived from a discrete sum that already includes dt, pa
+
+ temporal_phasor
+
+
+¶
+temporal_phasor(
+ samples: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ *,
+ start_step: int = 0,
+ offset_steps: float = 0.0,
+) -> NDArray[numpy.complexfloating]
+
+
+
+
+ Fourier-project a 1D temporal waveform onto one or more angular frequencies.
+The returned quantity is
+dt * sum(exp(-1j * omega * t_step) * samples[step_index])
+
+where t_step = (start_step + step_index + offset_steps) * dt.
+
+
+
+
+
+
+
+
+
+
+ temporal_phasor_scale
+
+
+¶
+temporal_phasor_scale(
+ samples: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ *,
+ start_step: int = 0,
+ offset_steps: float = 0.0,
+ target: ArrayLike = 1.0,
+) -> NDArray[numpy.complexfloating]
+
+
+
+
+ Return the scalar multiplier that gives a desired temporal phasor response.
+The returned scale satisfies
+temporal_phasor(scale * samples, omegas, dt, ...) == target
+
+for each target frequency. The result keeps a leading frequency axis even
+when omegas is scalar.
+
+
+
+
+
+
+
+
+
+
+ real_injection_scale
+
+
+¶
+real_injection_scale(
+ samples: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ *,
+ start_step: int = 0,
+ offset_steps: float = 0.0,
+ target: ArrayLike = 1.0,
+) -> NDArray[numpy.complexfloating]
+
+
+
+
+ Return the scale for a real-valued injection built from an analytic waveform.
+If the time-domain source is applied as
+numpy.real(scale * samples[step])
+
+then the desired positive-frequency phasor is obtained by compensating for
+the 1/2 factor between the real-valued source and its analytic component:
+scale = 2 * target / temporal_phasor(samples, ...)
+
+This helper normalizes only the intended positive-frequency component. Any
+residual negative-frequency leakage is controlled by the waveform design and
+the accumulation window.
+
+
+
+
+
+
+
+
+
+
+ reconstruct_real
+
+
+¶
+reconstruct_real(
+ phasors: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ step: int,
+ *,
+ offset_steps: float = 0.0,
+) -> NDArray[numpy.floating]
+
+
+
+
+ Reconstruct a real-valued field snapshot from one or more phasors.
+The returned quantity is
+real(phasor * exp(1j * omega * t_step))
+
+where t_step = (step + offset_steps) * dt.
+For multi-frequency inputs, the leading frequency axis is preserved. For a
+scalar omega, callers may pass either (1, *sample_shape) or
+sample_shape; the return shape matches that choice.
+
+
+
+
+
+
+
+
+
accumulate_phasor_e
@@ -3740,6 +4095,90 @@ factor was derived from a discrete sum that already includes dt, pa
+
+
+
+
+ reconstruct_real_e
+
+
+¶
+reconstruct_real_e(
+ phasors: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ step: int,
+) -> NDArray[numpy.floating]
+
+
+
+
+ Reconstruct a real E-field snapshot taken at integer timestep step.
+
+
+
+
+
+
+
+
+
+
+ reconstruct_real_h
+
+
+¶
+reconstruct_real_h(
+ phasors: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ step: int,
+) -> NDArray[numpy.floating]
+
+
+
+
+ Reconstruct a real H-field snapshot corresponding to H_{step + 1/2}.
+
+
+
+
+
+
+
+
+
+
+ reconstruct_real_j
+
+
+¶
+reconstruct_real_j(
+ phasors: ArrayLike,
+ omegas: float
+ | complex
+ | Sequence[float | complex]
+ | NDArray,
+ dt: float,
+ step: int,
+) -> NDArray[numpy.floating]
+
+
+
+
+ Reconstruct a real current snapshot corresponding to J_{step + 1/2}.
+
+
+
+
+
+
diff --git a/api/waveguides/index.html b/api/waveguides/index.html
index 64260c7..468141f 100644
--- a/api/waveguides/index.html
+++ b/api/waveguides/index.html
@@ -1402,132 +1402,144 @@ a structure with some (x, y) cross-section extending uniformly into the z dimens
with a diagonal \(\epsilon\) tensor, we have
\[
\begin{aligned}
-\nabla \times \vec{E}(x, y, z) &= -\imath \omega \mu \vec{H} \\
-\nabla \times \vec{H}(x, y, z) &= \imath \omega \epsilon \vec{E} \\
-\vec{E}(x,y,z) &= (\vec{E}_t(x, y) + E_z(x, y)\vec{z}) e^{-\imath \beta z} \\
-\vec{H}(x,y,z) &= (\vec{H}_t(x, y) + H_z(x, y)\vec{z}) e^{-\imath \beta z} \\
+\nabla \times \vec{E}(x, y, z) &= -\imath \omega \mu \vec{H} \\
+\nabla \times \vec{H}(x, y, z) &= \imath \omega \epsilon \vec{E} \\
+\vec{E}(x,y,z) &= (\vec{E}_t(x, y) + E_z(x, y)\vec{z}) e^{-\imath \beta z} \\
+\vec{H}(x,y,z) &= (\vec{H}_t(x, y) + H_z(x, y)\vec{z}) e^{-\imath \beta z} \\
\end{aligned}
\]
+
Expanding the first two equations into vector components, we get
\[
\begin{aligned}
--\imath \omega \mu_{xx} H_x &= \partial_y E_z - \partial_z E_y \\
--\imath \omega \mu_{yy} H_y &= \partial_z E_x - \partial_x E_z \\
--\imath \omega \mu_{zz} H_z &= \partial_x E_y - \partial_y E_x \\
-\imath \omega \epsilon_{xx} E_x &= \partial_y H_z - \partial_z H_y \\
-\imath \omega \epsilon_{yy} E_y &= \partial_z H_x - \partial_x H_z \\
-\imath \omega \epsilon_{zz} E_z &= \partial_x H_y - \partial_y H_x \\
+-\imath \omega \mu_{xx} H_x &= \partial_y E_z - \partial_z E_y \\
+-\imath \omega \mu_{yy} H_y &= \partial_z E_x - \partial_x E_z \\
+-\imath \omega \mu_{zz} H_z &= \partial_x E_y - \partial_y E_x \\
+\imath \omega \epsilon_{xx} E_x &= \partial_y H_z - \partial_z H_y \\
+\imath \omega \epsilon_{yy} E_y &= \partial_z H_x - \partial_x H_z \\
+\imath \omega \epsilon_{zz} E_z &= \partial_x H_y - \partial_y H_x \\
\end{aligned}
\]
+
Substituting in our expressions for \(\vec{E}\), \(\vec{H}\) and discretizing:
\[
\begin{aligned}
--\imath \omega \mu_{xx} H_x &= \tilde{\partial}_y E_z + \imath \beta E_y \\
--\imath \omega \mu_{yy} H_y &= -\imath \beta E_x - \tilde{\partial}_x E_z \\
--\imath \omega \mu_{zz} H_z &= \tilde{\partial}_x E_y - \tilde{\partial}_y E_x \\
-\imath \omega \epsilon_{xx} E_x &= \hat{\partial}_y H_z + \imath \beta H_y \\
-\imath \omega \epsilon_{yy} E_y &= -\imath \beta H_x - \hat{\partial}_x H_z \\
-\imath \omega \epsilon_{zz} E_z &= \hat{\partial}_x H_y - \hat{\partial}_y H_x \\
+-\imath \omega \mu_{xx} H_x &= \tilde{\partial}_y E_z + \imath \beta E_y \\
+-\imath \omega \mu_{yy} H_y &= -\imath \beta E_x - \tilde{\partial}_x E_z \\
+-\imath \omega \mu_{zz} H_z &= \tilde{\partial}_x E_y - \tilde{\partial}_y E_x \\
+\imath \omega \epsilon_{xx} E_x &= \hat{\partial}_y H_z + \imath \beta H_y \\
+\imath \omega \epsilon_{yy} E_y &= -\imath \beta H_x - \hat{\partial}_x H_z \\
+\imath \omega \epsilon_{zz} E_z &= \hat{\partial}_x H_y - \hat{\partial}_y H_x \\
\end{aligned}
\]
+
Rewrite the last three equations as
\[
\begin{aligned}
-\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\
-\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\
-\imath \omega E_z &= \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y - \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\
+\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\
+\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\
+\imath \omega E_z &= \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y - \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\
\end{aligned}
\]
+
Now apply \(\imath \beta \tilde{\partial}_x\) to the last equation,
then substitute in for \(\imath \beta H_x\) and \(\imath \beta H_y\):
\[
\begin{aligned}
-\imath \beta \tilde{\partial}_x \imath \omega E_z &= \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y
+\imath \beta \tilde{\partial}_x \imath \omega E_z &= \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y
- \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\
- &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z)
+ &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z)
- \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\
- &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x)
+ &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x)
- \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (-\imath \omega \epsilon_{yy} E_y) \\
-\imath \beta \tilde{\partial}_x E_z &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+\imath \beta \tilde{\partial}_x E_z &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) \\
\end{aligned}
\]
+
With a similar approach (but using \(\imath \beta \tilde{\partial}_y\) instead), we can get
\[
\begin{aligned}
-\imath \beta \tilde{\partial}_y E_z &= \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+\imath \beta \tilde{\partial}_y E_z &= \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) \\
\end{aligned}
\]
+
We can combine this equation for \(\imath \beta \tilde{\partial}_y E_z\) with
the unused \(\imath \omega \mu_{xx} H_x\) and \(\imath \omega \mu_{yy} H_y\) equations to get
\[
\begin{aligned}
--\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \imath \beta \tilde{\partial}_y E_z \\
--\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \tilde{\partial}_y (
+-\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \imath \beta \tilde{\partial}_y E_z \\
+-\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \tilde{\partial}_y (
\frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y)
)\\
\end{aligned}
\]
+
and
\[
\begin{aligned}
--\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \imath \beta \tilde{\partial}_x E_z \\
--\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \tilde{\partial}_x (
+-\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \imath \beta \tilde{\partial}_x E_z \\
+-\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \tilde{\partial}_x (
\frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y)
)\\
\end{aligned}
\]
+
However, based on our rewritten equation for \(\imath \beta H_x\) and the so-far unused
equation for \(\imath \omega \mu_{zz} H_z\) we can also write
\[
\begin{aligned}
--\imath \omega \mu_{xx} (\imath \beta H_x) &= -\imath \omega \mu_{xx} (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\
- &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + \imath \omega \mu_{xx} \hat{\partial}_x (
+-\imath \omega \mu_{xx} (\imath \beta H_x) &= -\imath \omega \mu_{xx} (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\
+ &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + \imath \omega \mu_{xx} \hat{\partial}_x (
\frac{1}{-\imath \omega \mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x)) \\
- &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y
+ &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y
-\mu_{xx} \hat{\partial}_x \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\
\end{aligned}
\]
+
and, similarly,
\[
\begin{aligned}
--\imath \omega \mu_{yy} (\imath \beta H_y) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x
+-\imath \omega \mu_{yy} (\imath \beta H_y) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x
+\mu_{yy} \hat{\partial}_y \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\
\end{aligned}
\]
+
By combining both pairs of expressions, we get
\[
\begin{aligned}
\beta^2 E_x - \tilde{\partial}_x (
\frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y)
- ) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x
+ ) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x
+\mu_{yy} \hat{\partial}_y \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\
-\beta^2 E_y + \tilde{\partial}_y (
\frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x)
+ \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y)
- ) &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y
+ ) &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y
-\mu_{xx} \hat{\partial}_x \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\
\end{aligned}
\]
+
Using these, we can construct the eigenvalue problem
\[
\beta^2 \begin{bmatrix} E_x \\
E_y \end{bmatrix} =
- (\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\
- 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} +
+ (\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\
+ 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} +
\begin{bmatrix} -\mu_{yy} \hat{\partial}_y \\
\mu_{xx} \hat{\partial}_x \end{bmatrix} \mu_{zz}^{-1}
- \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} +
+ \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} +
\begin{bmatrix} \tilde{\partial}_x \\
\tilde{\partial}_y \end{bmatrix} \epsilon_{zz}^{-1}
- \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix})
+ \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix})
\begin{bmatrix} E_x \\
E_y \end{bmatrix}
\]
+
In the literature, \(\beta\) is usually used to denote the lossless/real part of the propagation constant,
but in meanas it is allowed to be complex.
An equivalent eigenvalue problem can be formed using the \(H_x\) and \(H_y\) fields, if those are more convenient.
@@ -1580,15 +1592,16 @@ mu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +
for use with a field vector of the form cat([E_x, E_y]).
More precisely, the operator is
\[
-\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\
- 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} +
+\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\
+ 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} +
\begin{bmatrix} -\mu_{yy} \hat{\partial}_y \\
\mu_{xx} \hat{\partial}_x \end{bmatrix} \mu_{zz}^{-1}
- \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} +
+ \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} +
\begin{bmatrix} \tilde{\partial}_x \\
\tilde{\partial}_y \end{bmatrix} \epsilon_{zz}^{-1}
- \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix}
+ \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix}
\]
+
\(\tilde{\partial}_x\) and \(\hat{\partial}_x\) are the forward and backward derivatives along x,
and each \(\epsilon_{xx}\), \(\mu_{yy}\), etc. is a diagonal matrix containing the vectorized material
property distribution.
@@ -1735,15 +1748,16 @@ epsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +
for use with a field vector of the form cat([H_x, H_y]).
More precisely, the operator is
\[
-\omega^2 \begin{bmatrix} \epsilon_{yy} \mu_{xx} & 0 \\
- 0 & \epsilon_{xx} \mu_{yy} \end{bmatrix} +
+\omega^2 \begin{bmatrix} \epsilon_{yy} \mu_{xx} & 0 \\
+ 0 & \epsilon_{xx} \mu_{yy} \end{bmatrix} +
\begin{bmatrix} -\epsilon_{yy} \tilde{\partial}_y \\
\epsilon_{xx} \tilde{\partial}_x \end{bmatrix} \epsilon_{zz}^{-1}
- \begin{bmatrix} -\hat{\partial}_y & \hat{\partial}_x \end{bmatrix} +
+ \begin{bmatrix} -\hat{\partial}_y & \hat{\partial}_x \end{bmatrix} +
\begin{bmatrix} \hat{\partial}_x \\
\hat{\partial}_y \end{bmatrix} \mu_{zz}^{-1}
- \begin{bmatrix} \tilde{\partial}_x \mu_{xx} & \tilde{\partial}_y \mu_{yy} \end{bmatrix}
+ \begin{bmatrix} \tilde{\partial}_x \mu_{xx} & \tilde{\partial}_y \mu_{yy} \end{bmatrix}
\]
+
\(\tilde{\partial}_x\) and \(\hat{\partial}_x\) are the forward and backward derivatives along x,
and each \(\epsilon_{xx}\), \(\mu_{yy}\), etc. is a diagonal matrix containing the vectorized material
property distribution.
@@ -2066,6 +2080,7 @@ and exy2h(...), then normalizes them to unit forward power using
\[
\Re\left[\mathrm{inner\_product}(e, h, \mathrm{conj\_h}=True)\right] = 1,
\]
+
so the returned fields represent a unit-power forward mode under the
discrete Yee-grid Poynting inner product.
@@ -2721,21 +2736,23 @@ identical representatives for nearly symmetric modes.
\[
\imath \omega \epsilon_{zz} E_z = \hat{\partial}_x H_y - \hat{\partial}_y H_x \\
\]
+
as well as the intermediate equations
\[
\begin{aligned}
-\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\
-\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\
+\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\
+\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\
\end{aligned}
\]
+
Combining these, we get
\[
\begin{aligned}
-E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} ((
+E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} ((
\hat{\partial}_y \hat{\partial}_x H_z
-\hat{\partial}_x \hat{\partial}_y H_z)
+ \imath \omega (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y))
- &= \frac{1}{\imath \beta \epsilon_{zz}} (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y)
+ &= \frac{1}{\imath \beta \epsilon_{zz}} (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y)
\end{aligned}
\]
@@ -3654,10 +3671,12 @@ E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} ((
\(\beta\) to changes in the dielectric structure \(\epsilon\).
The output is a vector of the same size as vec(epsilon), with each element specifying the
sensitivity of wavenumber to changes in the corresponding element in vec(epsilon), i.e.
-\[sens_{i} = \frac{\partial\beta}{\partial\epsilon_i}\]
+\[ sens_{i} = \frac{\partial\beta}{\partial\epsilon_i} \]
+
An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
-\[\beta^2 E_{xy} = A_E E_{xy}\]
+\[ \beta^2 E_{xy} = A_E E_{xy} \]
+
where \(A_E\) is the waveguide operator from operator_e(), and \(E_{xy} = \begin{bmatrix} E_x \\
E_y \end{bmatrix}\),
we can differentiate with respect to one of the \(\epsilon\) elements (i.e. at one Yee grid point), \(\epsilon_i\):
@@ -3665,11 +3684,13 @@ we can differentiate with respect to one of the \(\epsi
(2 \beta) \partial_{\epsilon_i}(\beta) E_{xy} + \beta^2 \partial_{\epsilon_i} E_{xy}
= \partial_{\epsilon_i}(A_E) E_{xy} + A_E \partial_{\epsilon_i} E_{xy}
\]We then multiply by \(H_{yx}^\star = \begin{bmatrix}H_y^\star \\ -H_x^\star \end{bmatrix}\) from the left:
However, \(H_{yx}^\star\) is actually a left-eigenvector of \(A_E\). This can be verified by inspecting
the form of operator_h (\(A_H\)) and comparing its conjugate transpose to operator_e (\(A_E\)). Also, note
\(H_{yx}^\star \cdot E_{xy} = H^\star \times E\) recalls the mode orthogonality relation. See doi:10.5194/ars-9-85-201
@@ -3677,11 +3698,13 @@ for a similar approach. Therefore,
and we can simplify to
This expression can be quickly calculated for all \(i\) by writing out the various terms of \(\partial_{\epsilon_i} A_E\) and recognizing that the vector-matrix-vector products (i.e. scalars) \(sens_i = \vec{v}_{left} \partial_{\epsilon_i} (\epsilon_{xyz}) \vec{v}_{right}\), indexed by \(i\), can be expressed as @@ -4161,6 +4184,7 @@ longitudinal Poynting flux,
with the Yee-grid staggering and optional propagation-phase adjustment used by the waveguide helpers in this module.
@@ -4866,6 +4890,7 @@ same sign convention used elsewhere in the package:where the sum is over the full Yee-grid field storage.
The construction uses a two-cell window immediately upstream of the selected slice:
@@ -4881,6 +4906,7 @@ overlap relation involvingE x H_mode + E_mode x H -> Ex Hmy - EyHmx + Emx Hy - Emy Hx (Z-prop) Ex we/B Emx + Ex i/B dy Hmz - Ey (-we/B Emy) - Ey i/B dx Hmz @@ -5219,6 +5245,7 @@ along the propagation axis and applies the phase factor
to each copied slice.
@@ -5254,11 +5281,13 @@ and the fields are assumed to have the formwhere m is the angular wavenumber returned by solve_mode(s). It is often
convenient to introduce the corresponding linear wavenumber
so that the cylindrical problem resembles the straight-waveguide problem with additional metric factors.
Those metric factors live on the staggered radial Yee grids. If the left edge of
@@ -5266,33 +5295,36 @@ the computational window is at r = r_{\min}, define the electric-gr
magnetic-grid radial sample locations by
and from them the diagonal metric matrices
With the same forward/backward derivative notation used in waveguide_2d, the
coordinate-transformed discrete curl equations used here are
The first three equations are the cylindrical analogue of the straight-guide
relations for H_r, H_y, and H_z. The next two are the metric-weighted
versions of the straight-guide identities for \imath \beta H_y and
@@ -5306,6 +5338,7 @@ and then eliminate H_z with
This yields the transverse electric eigenproblem implemented by
cylindrical_operator(...):
Since \beta = m / r_{\min}, the solver implemented in this file returns the
angular wavenumber m, while the operator itself is most naturally written in
terms of the linear quantity \beta. The helpers below reconstruct the full
@@ -5389,17 +5423,18 @@ product used by waveguide_2d.
Cylindrical coordinate waveguide operator of the form
for use with a field vector of the form [E_r, E_y].
This operator can be used to form an eigenvalue problem of the form A @ [E_r, E_y] = beta**2 * [E_r, E_y]
@@ -6288,15 +6323,15 @@ from E_r and E_y in order to then calculate E_z.Returns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
-This operator is created directly from the initial coordinate-transformed equations: -$$ +
This operator is created directly from the initial coordinate-transformed equations:
+Parameters:
@@ -6745,6 +6780,7 @@ enforces unit forward power under the discrete inner productThe angular wavenumber m is first converted into the full three-component
fields, then the overall complex phase and sign are fixed so the result is
reproducible for symmetric modes.
For most users, the tracked examples under examples/ are the right entry
point. They show the intended combinations of tools for solving complete
problems.
Relevant starting examples:
+examples/fdtd.py for broadband pulse excitation and phasor extractionexamples/waveguide.py for guided phasor-domain FDTD/FDFD comparisonexamples/waveguide_real.py for real-valued continuous-wave FDTD compared
+ against real fields reconstructed from an FDFD solution, including guided-core,
+ mode-weighted, and guided-mode / residual comparisonsexamples/fdfd.py for direct frequency-domain waveguide excitationFor solver equivalence, prefer the phasor-based examples first. They compare
+the extracted +\omega content of the FDTD run directly against the FDFD
+solution and are the main accuracy benchmarks in the test suite.
examples/waveguide_real.py answers a different, stricter question: how well a
+late raw real snapshot matches Re(E_\omega e^{i\omega t}) on a monitor plane.
+That diagnostic is useful, but it also includes orthogonal residual structure
+that the phasor comparison intentionally filters out.
The API pages are better read as a toolbox map and derivation reference:
f+For most users, the tracked examples under
+examples/are the right entry point. They show the intended combinations of tools for solving complete problems.Relevant starting examples:
++
+- +
examples/fdtd.pyfor broadband pulse excitation and phasor extraction- +
examples/waveguide.pyfor guided phasor-domain FDTD/FDFD comparison- +
examples/waveguide_real.pyfor real-valued continuous-wave FDTD compared + against real fields reconstructed from an FDFD solution, including guided-core, + mode-weighted, and guided-mode / residual comparisons- +
examples/fdfd.pyfor direct frequency-domain waveguide excitationFor solver equivalence, prefer the phasor-based examples first. They compare +the extracted
++\omegacontent of the FDTD run directly against the FDFD +solution and are the main accuracy benchmarks in the test suite.
examples/waveguide_real.pyanswers a different, stricter question: how well a +late raw real snapshot matchesRe(E_\omega e^{i\omega t})on a monitor plane. +That diagnostic is useful, but it also includes orthogonal residual structure +that the phasor comparison intentionally filters out.The API pages are better read as a toolbox map and derivation reference:
-
- Use the FDTD API for time-domain stepping, CPML, and phasor - extraction.
+- Use the FDTD API for time-domain stepping, CPML, phasor + extraction, and real-field reconstruction from FDFD phasors.
- Use the FDFD API for driven frequency-domain solves and sparse operator algebra.
- Use the Waveguide API for mode solving, port sources, @@ -855,7 +871,7 @@ toolbox overview and API derivations.
iterations- int+int @@ -968,7 +984,7 @@ toolbox overview and API derivations.iterations- int+int @@ -1100,7 +1116,7 @@ toolbox overview and API derivations.how_many- int+int @@ -1208,58 +1224,63 @@ matrix equation (Ax=b) or eigenvalue problem.From the "Frequency domain" section of
meanas.fdmath, we have\[ \begin{aligned} - \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\ - \tilde{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &= \tilde{H}_{\vec{r} + \frac{1}{2}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} \\ - \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} \\ - \tilde{M}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &= \tilde{M}_{\vec{r} + \frac{1}{2}} e^{-\imath \omega l \Delta_t} \\ + \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\ + \tilde{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &= \tilde{H}_{\vec{r} + \frac{1}{2}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} \\ + \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} \\ + \tilde{M}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &= \tilde{M}_{\vec{r} + \frac{1}{2}} e^{-\imath \omega l \Delta_t} \\ \hat{\nabla} \times (\mu^{-1}_{\vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{\vec{r}}) - -\Omega^2 \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}} &= -\imath \Omega \tilde{J}_{\vec{r}} e^{\imath \omega \Delta_t / 2} \\ - \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t + -\Omega^2 \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}} &= -\imath \Omega \tilde{J}_{\vec{r}} e^{\imath \omega \Delta_t / 2} \\ + \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t \end{aligned} \]+resulting in
\[ \begin{aligned} - \tilde{\partial}_t &\Rightarrow -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\ - \hat{\partial}_t &\Rightarrow -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\ + \tilde{\partial}_t &\Rightarrow -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\ + \hat{\partial}_t &\Rightarrow -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\ \end{aligned} \]+Maxwell's equations are then
\[ \begin{aligned} - \tilde{\nabla} \times \tilde{E}_{\vec{r}} &= + \tilde{\nabla} \times \tilde{E}_{\vec{r}} &= \imath \Omega e^{-\imath \omega \Delta_t / 2} \hat{B}_{\vec{r} + \frac{1}{2}} - \hat{M}_{\vec{r} + \frac{1}{2}} \\ - \hat{\nabla} \times \hat{H}_{\vec{r} + \frac{1}{2}} &= + \hat{\nabla} \times \hat{H}_{\vec{r} + \frac{1}{2}} &= -\imath \Omega e^{ \imath \omega \Delta_t / 2} \tilde{D}_{\vec{r}} + \tilde{J}_{\vec{r}} \\ - \tilde{\nabla} \cdot \hat{B}_{\vec{r} + \frac{1}{2}} &= 0 \\ - \hat{\nabla} \cdot \tilde{D}_{\vec{r}} &= \rho_{\vec{r}} + \tilde{\nabla} \cdot \hat{B}_{\vec{r} + \frac{1}{2}} &= 0 \\ + \hat{\nabla} \cdot \tilde{D}_{\vec{r}} &= \rho_{\vec{r}} \end{aligned} \]+With \(\Delta_t \to 0\), this simplifies to
\[ \begin{aligned} - \tilde{E}_{l, \vec{r}} &\to \tilde{E}_{\vec{r}} \\ - \tilde{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &\to \tilde{H}_{\vec{r} + \frac{1}{2}} \\ - \tilde{J}_{l, \vec{r}} &\to \tilde{J}_{\vec{r}} \\ - \tilde{M}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &\to \tilde{M}_{\vec{r} + \frac{1}{2}} \\ - \Omega &\to \omega \\ - \tilde{\partial}_t &\to -\imath \omega \\ - \hat{\partial}_t &\to -\imath \omega \\ + \tilde{E}_{l, \vec{r}} &\to \tilde{E}_{\vec{r}} \\ + \tilde{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &\to \tilde{H}_{\vec{r} + \frac{1}{2}} \\ + \tilde{J}_{l, \vec{r}} &\to \tilde{J}_{\vec{r}} \\ + \tilde{M}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} &\to \tilde{M}_{\vec{r} + \frac{1}{2}} \\ + \Omega &\to \omega \\ + \tilde{\partial}_t &\to -\imath \omega \\ + \hat{\partial}_t &\to -\imath \omega \\ \end{aligned} \]+and then
\[ \begin{aligned} - \tilde{\nabla} \times \tilde{E}_{\vec{r}} &= + \tilde{\nabla} \times \tilde{E}_{\vec{r}} &= \imath \omega \hat{B}_{\vec{r} + \frac{1}{2}} - \hat{M}_{\vec{r} + \frac{1}{2}} \\ - \hat{\nabla} \times \hat{H}_{\vec{r} + \frac{1}{2}} &= + \hat{\nabla} \times \hat{H}_{\vec{r} + \frac{1}{2}} &= -\imath \omega \tilde{D}_{\vec{r}} + \tilde{J}_{\vec{r}} \\ \end{aligned} \]+\[ \hat{\nabla} \times (\mu^{-1}_{\vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{\vec{r}}) -\omega^2 \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}} = -\imath \omega \tilde{J}_{\vec{r}} \\ @@ -1405,7 +1426,7 @@ e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)epsilon- fdfield+fdfield @@ -1421,7 +1442,7 @@ e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)mu- fdfield | None+fdfield | None @@ -1560,7 +1581,7 @@ Seeoperators.eh_full.epsilon- fdfield+fdfield @@ -1576,7 +1597,7 @@ Seeoperators.eh_full.mu- fdfield | None+fdfield | None @@ -1602,7 +1623,7 @@ Seeoperators.eh_full.- Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]+Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] @@ -1612,7 +1633,7 @@ Seeoperators.eh_full.- Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]+Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] @@ -1702,7 +1723,7 @@ For use withe_full-- assumes that there is no magnetic currentmu - fdfield | None+fdfield | None @@ -1839,7 +1860,7 @@ For use with e.g.e_full.mu- fdfield | None+fdfield | None @@ -1927,6 +1948,7 @@ mask selecting the total-field region, then the TFSF source is the commutator\[ \frac{A Q - Q A}{-i \omega} E. \]+This vanishes in the interior of the total-field and scattered-field regions and is supported only at their shared boundary, where the mask discontinuity makes
AandQfail to commute. The returned current is therefore the @@ -1950,7 +1972,7 @@ forcing the scattered-field region.TF_region- fdfield+fdfield @@ -2005,7 +2027,7 @@ forcing the scattered-field region.epsilon- fdfield+fdfield @@ -2021,7 +2043,7 @@ forcing the scattered-field region.mu- fdfield | None+fdfield | None @@ -2169,7 +2191,7 @@ instead.- Callable[[cfdfield, cfdfield], cfdfield_t]+Callable[[cfdfield, cfdfield], cfdfield_t] @@ -2179,7 +2201,7 @@ instead.- Callable[[cfdfield, cfdfield], cfdfield_t]+Callable[[cfdfield, cfdfield], cfdfield_t] @@ -2279,11 +2301,15 @@ themeanas.fdmath.typessubmodule for details.Wave operator - $$ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon $$
+ +\[ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon \]+del x (1/mu * del x) - omega**2 * epsilonfor use with the E-field, with wave equation - $$ (\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E = -\imath \omega J $$
+ +\[ (\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E = -\imath \omega J \]+(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * JTo make this matrix symmetric, use the preconditioners from
@@ -2342,7 +2368,7 @@ thee_full_preconditioners().meanas.fdmath.typessubmodule for details.epsilon- vfdfield | vcfdfield+vfdfield | vcfdfield @@ -2358,7 +2384,7 @@ themeanas.fdmath.typessubmodule for details.mu- vfdfield | None+vfdfield | None @@ -2374,7 +2400,7 @@ themeanas.fdmath.typessubmodule for details.pec- vfdfield | None+vfdfield | None @@ -2392,7 +2418,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -2541,11 +2567,15 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizeWave operator - $$ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu $$
+ +\[ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu \]+del x (1/epsilon * del x) - omega**2 * mufor use with the H-field, with wave equation - $$ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M $$
+ +\[ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M \]+@@ -2603,7 +2633,7 @@ The PMC is applied per-field-component (i.e.(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * Mpmc.size == epsilon.sizeepsilon- vfdfield+vfdfield @@ -2619,7 +2649,7 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizemu- vfdfield | None+vfdfield | None @@ -2635,7 +2665,7 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizepec- vfdfield | None+vfdfield | None @@ -2653,7 +2683,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -2718,25 +2748,30 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizeWave operator for
+ equations without cancelling out either E or H. The operator is +[E, H]field representation. This operator implements Maxwell's - equations without cancelling out either E or H. The operator is -$$ \begin{bmatrix} - -\imath \omega \epsilon & \nabla \times \ - \nabla \times & \imath \omega \mu - \end{bmatrix} $$\[ +\begin{bmatrix} + -\imath \omega \epsilon & \nabla \times \\ + \nabla \times & \imath \omega \mu +\end{bmatrix} +\]+-[[-i * omega * epsilon, del x ], [del x, i * omega * mu]]for use with a field vector of the form
cat(vec(E), vec(H)): -$$ \begin{bmatrix} - -\imath \omega \epsilon & \nabla \times \ - \nabla \times & \imath \omega \mu +for use with a field vector of the form
+cat(vec(E), vec(H)):\[ +\begin{bmatrix} + -\imath \omega \epsilon & \nabla \times \\ + \nabla \times & \imath \omega \mu \end{bmatrix} - \begin{bmatrix} E \ + \begin{bmatrix} E \\ H \end{bmatrix} - = \begin{bmatrix} J \ + = \begin{bmatrix} J \\ -M - \end{bmatrix} $$ +\end{bmatrix} +\]Parameters:
@@ -2792,7 +2827,7 @@ $$ \begin{bmatrix}epsilon- vfdfield+vfdfield @@ -2808,7 +2843,7 @@ $$ \begin{bmatrix}mu- vfdfield | None+vfdfield | None @@ -2824,7 +2859,7 @@ $$ \begin{bmatrix}pec- vfdfield | None+vfdfield | None @@ -2842,7 +2877,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -2961,7 +2996,7 @@ For use withe_full()-- assumes that there is no magnetic currentmu- vfdfield | None+vfdfield | None @@ -2977,7 +3012,7 @@ For use withe_full()-- assumes that there is no magnetic currentpmc- vfdfield | None+vfdfield | None @@ -3095,7 +3130,7 @@ For use with eg.e_full().mu- vfdfield | None+vfdfield | None @@ -3271,6 +3306,7 @@ usual antisymmetry of the cross product,\[ H \times E = -(E \times H), \]+once the same staggered field placement is used on both sides.
@@ -3390,6 +3426,7 @@ Then the TFSF current operator is the commutator\[ \frac{A Q - Q A}{-i \omega}. \]+Inside regions where
Qis locally constant,AandQcommute and the source vanishes. Only cells at the TF/SF boundary contribute nonzero current, which is exactly the desired distributed source for injecting the chosen @@ -3413,7 +3450,7 @@ scattered-field region.TF_region- vfdfield+vfdfield @@ -3467,7 +3504,7 @@ scattered-field region.epsilon- vfdfield+vfdfield @@ -3483,7 +3520,7 @@ scattered-field region.mu- vfdfield | None+vfdfield | None @@ -3571,7 +3608,7 @@ the shifts mirror at the domain boundary; withTruethey wrap periomask- vfdfield+vfdfield @@ -3626,7 +3663,7 @@ the shifts mirror at the domain boundary; withTruethey wrap perioepsilon- vfdfield+vfdfield @@ -3642,7 +3679,7 @@ the shifts mirror at the domain boundary; withTruethey wrap periomu- vfdfield | None+vfdfield | None @@ -3826,7 +3863,7 @@ discussed inmeanas.fdmath.typesepsilon- vfdfield+vfdfield @@ -3842,7 +3879,7 @@ discussed inmeanas.fdmath.typesmu- vfdfield | None+vfdfield | None @@ -3858,7 +3895,7 @@ discussed inmeanas.fdmath.typespec- vfdfield | None+vfdfield | None @@ -3875,7 +3912,7 @@ discussed inmeanas.fdmath.typespmc- vfdfield | None+vfdfield | None @@ -3930,7 +3967,7 @@ discussed inmeanas.fdmath.typesmatrix_solver_opts- dict[str, Any] | None+dict[str, Any] | None @@ -4224,7 +4261,7 @@ customize the PML parameters.shape- Sequence[int]+Sequence[int] @@ -4240,7 +4277,7 @@ customize the PML parameters.thicknesses- Sequence[int]+Sequence[int] @@ -4398,7 +4435,7 @@ customize the PML parameters.axis- int+int @@ -4414,7 +4451,7 @@ customize the PML parameters.polarity- int+int @@ -4463,7 +4500,7 @@ customize the PML parameters.thickness- int+int @@ -4685,7 +4722,7 @@ customize the PML parameters.padded_size- list[int] | int | None+list[int] | int | None @@ -4713,7 +4750,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4723,7 +4760,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4736,7 +4773,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4749,7 +4786,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4762,7 +4799,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4774,7 +4811,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4787,7 +4824,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4916,7 +4953,7 @@ customize the PML parameters.padded_size- list[int] | int | None+list[int] | int | None @@ -4944,7 +4981,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4954,7 +4991,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4966,7 +5003,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4978,7 +5015,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -5028,132 +5065,144 @@ a structure with some (x, y) cross-section extending uniformly into the z dimens with a diagonal \(\epsilon\) tensor, we have+\[ \begin{aligned} -\nabla \times \vec{E}(x, y, z) &= -\imath \omega \mu \vec{H} \\ -\nabla \times \vec{H}(x, y, z) &= \imath \omega \epsilon \vec{E} \\ -\vec{E}(x,y,z) &= (\vec{E}_t(x, y) + E_z(x, y)\vec{z}) e^{-\imath \beta z} \\ -\vec{H}(x,y,z) &= (\vec{H}_t(x, y) + H_z(x, y)\vec{z}) e^{-\imath \beta z} \\ +\nabla \times \vec{E}(x, y, z) &= -\imath \omega \mu \vec{H} \\ +\nabla \times \vec{H}(x, y, z) &= \imath \omega \epsilon \vec{E} \\ +\vec{E}(x,y,z) &= (\vec{E}_t(x, y) + E_z(x, y)\vec{z}) e^{-\imath \beta z} \\ +\vec{H}(x,y,z) &= (\vec{H}_t(x, y) + H_z(x, y)\vec{z}) e^{-\imath \beta z} \\ \end{aligned} \]+Expanding the first two equations into vector components, we get
\[ \begin{aligned} --\imath \omega \mu_{xx} H_x &= \partial_y E_z - \partial_z E_y \\ --\imath \omega \mu_{yy} H_y &= \partial_z E_x - \partial_x E_z \\ --\imath \omega \mu_{zz} H_z &= \partial_x E_y - \partial_y E_x \\ -\imath \omega \epsilon_{xx} E_x &= \partial_y H_z - \partial_z H_y \\ -\imath \omega \epsilon_{yy} E_y &= \partial_z H_x - \partial_x H_z \\ -\imath \omega \epsilon_{zz} E_z &= \partial_x H_y - \partial_y H_x \\ +-\imath \omega \mu_{xx} H_x &= \partial_y E_z - \partial_z E_y \\ +-\imath \omega \mu_{yy} H_y &= \partial_z E_x - \partial_x E_z \\ +-\imath \omega \mu_{zz} H_z &= \partial_x E_y - \partial_y E_x \\ +\imath \omega \epsilon_{xx} E_x &= \partial_y H_z - \partial_z H_y \\ +\imath \omega \epsilon_{yy} E_y &= \partial_z H_x - \partial_x H_z \\ +\imath \omega \epsilon_{zz} E_z &= \partial_x H_y - \partial_y H_x \\ \end{aligned} \]+Substituting in our expressions for \(\vec{E}\), \(\vec{H}\) and discretizing:
\[ \begin{aligned} --\imath \omega \mu_{xx} H_x &= \tilde{\partial}_y E_z + \imath \beta E_y \\ --\imath \omega \mu_{yy} H_y &= -\imath \beta E_x - \tilde{\partial}_x E_z \\ --\imath \omega \mu_{zz} H_z &= \tilde{\partial}_x E_y - \tilde{\partial}_y E_x \\ -\imath \omega \epsilon_{xx} E_x &= \hat{\partial}_y H_z + \imath \beta H_y \\ -\imath \omega \epsilon_{yy} E_y &= -\imath \beta H_x - \hat{\partial}_x H_z \\ -\imath \omega \epsilon_{zz} E_z &= \hat{\partial}_x H_y - \hat{\partial}_y H_x \\ +-\imath \omega \mu_{xx} H_x &= \tilde{\partial}_y E_z + \imath \beta E_y \\ +-\imath \omega \mu_{yy} H_y &= -\imath \beta E_x - \tilde{\partial}_x E_z \\ +-\imath \omega \mu_{zz} H_z &= \tilde{\partial}_x E_y - \tilde{\partial}_y E_x \\ +\imath \omega \epsilon_{xx} E_x &= \hat{\partial}_y H_z + \imath \beta H_y \\ +\imath \omega \epsilon_{yy} E_y &= -\imath \beta H_x - \hat{\partial}_x H_z \\ +\imath \omega \epsilon_{zz} E_z &= \hat{\partial}_x H_y - \hat{\partial}_y H_x \\ \end{aligned} \]+Rewrite the last three equations as
\[ \begin{aligned} -\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\ -\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\ -\imath \omega E_z &= \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y - \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\ +\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\ +\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\ +\imath \omega E_z &= \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y - \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\ \end{aligned} \]+Now apply \(\imath \beta \tilde{\partial}_x\) to the last equation, then substitute in for \(\imath \beta H_x\) and \(\imath \beta H_y\):
\[ \begin{aligned} -\imath \beta \tilde{\partial}_x \imath \omega E_z &= \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y +\imath \beta \tilde{\partial}_x \imath \omega E_z &= \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x H_y - \imath \beta \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y H_x \\ - &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z) + &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z) - \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\ - &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x) + &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x ( \imath \omega \epsilon_{xx} E_x) - \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (-\imath \omega \epsilon_{yy} E_y) \\ -\imath \beta \tilde{\partial}_x E_z &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) +\imath \beta \tilde{\partial}_x E_z &= \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \tilde{\partial}_x \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) \\ \end{aligned} \]+With a similar approach (but using \(\imath \beta \tilde{\partial}_y\) instead), we can get
\[ \begin{aligned} -\imath \beta \tilde{\partial}_y E_z &= \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) +\imath \beta \tilde{\partial}_y E_z &= \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \tilde{\partial}_y \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) \\ \end{aligned} \]+We can combine this equation for \(\imath \beta \tilde{\partial}_y E_z\) with the unused \(\imath \omega \mu_{xx} H_x\) and \(\imath \omega \mu_{yy} H_y\) equations to get
\[ \begin{aligned} --\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \imath \beta \tilde{\partial}_y E_z \\ --\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \tilde{\partial}_y ( +-\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \imath \beta \tilde{\partial}_y E_z \\ +-\imath \omega \mu_{xx} \imath \beta H_x &= -\beta^2 E_y + \tilde{\partial}_y ( \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) )\\ \end{aligned} \]+and
\[ \begin{aligned} --\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \imath \beta \tilde{\partial}_x E_z \\ --\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \tilde{\partial}_x ( +-\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \imath \beta \tilde{\partial}_x E_z \\ +-\imath \omega \mu_{yy} \imath \beta H_y &= \beta^2 E_x - \tilde{\partial}_x ( \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) )\\ \end{aligned} \]+However, based on our rewritten equation for \(\imath \beta H_x\) and the so-far unused equation for \(\imath \omega \mu_{zz} H_z\) we can also write
\[ \begin{aligned} --\imath \omega \mu_{xx} (\imath \beta H_x) &= -\imath \omega \mu_{xx} (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\ - &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + \imath \omega \mu_{xx} \hat{\partial}_x ( +-\imath \omega \mu_{xx} (\imath \beta H_x) &= -\imath \omega \mu_{xx} (-\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z) \\ + &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + \imath \omega \mu_{xx} \hat{\partial}_x ( \frac{1}{-\imath \omega \mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x)) \\ - &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y -\mu_{xx} \hat{\partial}_x \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\ \end{aligned} \]+and, similarly,
\[ \begin{aligned} --\imath \omega \mu_{yy} (\imath \beta H_y) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x +-\imath \omega \mu_{yy} (\imath \beta H_y) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x +\mu_{yy} \hat{\partial}_y \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\ \end{aligned} \]+By combining both pairs of expressions, we get
\[ \begin{aligned} \beta^2 E_x - \tilde{\partial}_x ( \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) - ) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x + ) &= \omega^2 \mu_{yy} \epsilon_{xx} E_x +\mu_{yy} \hat{\partial}_y \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\ -\beta^2 E_y + \tilde{\partial}_y ( \frac{1}{\epsilon_{zz}} \hat{\partial}_x (\epsilon_{xx} E_x) + \frac{1}{\epsilon_{zz}} \hat{\partial}_y (\epsilon_{yy} E_y) - ) &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y + ) &= -\omega^2 \mu_{xx} \epsilon_{yy} E_y -\mu_{xx} \hat{\partial}_x \frac{1}{\mu_{zz}} (\tilde{\partial}_x E_y - \tilde{\partial}_y E_x) \\ \end{aligned} \]+Using these, we can construct the eigenvalue problem
\[ \beta^2 \begin{bmatrix} E_x \\ E_y \end{bmatrix} = - (\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\ - 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} + + (\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\ + 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} + \begin{bmatrix} -\mu_{yy} \hat{\partial}_y \\ \mu_{xx} \hat{\partial}_x \end{bmatrix} \mu_{zz}^{-1} - \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + + \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + \begin{bmatrix} \tilde{\partial}_x \\ \tilde{\partial}_y \end{bmatrix} \epsilon_{zz}^{-1} - \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix}) + \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix}) \begin{bmatrix} E_x \\ E_y \end{bmatrix} \]+In the literature, \(\beta\) is usually used to denote the lossless/real part of the propagation constant, but in
meanasit is allowed to be complex.An equivalent eigenvalue problem can be formed using the \(H_x\) and \(H_y\) fields, if those are more convenient.
@@ -5206,15 +5255,16 @@ mu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +for use with a field vector of the form
cat([E_x, E_y]).More precisely, the operator is
\[ -\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\ - 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} + +\omega^2 \begin{bmatrix} \mu_{yy} \epsilon_{xx} & 0 \\ + 0 & \mu_{xx} \epsilon_{yy} \end{bmatrix} + \begin{bmatrix} -\mu_{yy} \hat{\partial}_y \\ \mu_{xx} \hat{\partial}_x \end{bmatrix} \mu_{zz}^{-1} - \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + + \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + \begin{bmatrix} \tilde{\partial}_x \\ \tilde{\partial}_y \end{bmatrix} \epsilon_{zz}^{-1} - \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix} + \begin{bmatrix} \hat{\partial}_x \epsilon_{xx} & \hat{\partial}_y \epsilon_{yy} \end{bmatrix} \]+\(\tilde{\partial}_x\) and \(\hat{\partial}_x\) are the forward and backward derivatives along x, and each \(\epsilon_{xx}\), \(\mu_{yy}\), etc. is a diagonal matrix containing the vectorized material property distribution.
@@ -5361,15 +5411,16 @@ epsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +for use with a field vector of the form
cat([H_x, H_y]).More precisely, the operator is
\[ -\omega^2 \begin{bmatrix} \epsilon_{yy} \mu_{xx} & 0 \\ - 0 & \epsilon_{xx} \mu_{yy} \end{bmatrix} + +\omega^2 \begin{bmatrix} \epsilon_{yy} \mu_{xx} & 0 \\ + 0 & \epsilon_{xx} \mu_{yy} \end{bmatrix} + \begin{bmatrix} -\epsilon_{yy} \tilde{\partial}_y \\ \epsilon_{xx} \tilde{\partial}_x \end{bmatrix} \epsilon_{zz}^{-1} - \begin{bmatrix} -\hat{\partial}_y & \hat{\partial}_x \end{bmatrix} + + \begin{bmatrix} -\hat{\partial}_y & \hat{\partial}_x \end{bmatrix} + \begin{bmatrix} \hat{\partial}_x \\ \hat{\partial}_y \end{bmatrix} \mu_{zz}^{-1} - \begin{bmatrix} \tilde{\partial}_x \mu_{xx} & \tilde{\partial}_y \mu_{yy} \end{bmatrix} + \begin{bmatrix} \tilde{\partial}_x \mu_{xx} & \tilde{\partial}_y \mu_{yy} \end{bmatrix} \]+\(\tilde{\partial}_x\) and \(\hat{\partial}_x\) are the forward and backward derivatives along x, and each \(\epsilon_{xx}\), \(\mu_{yy}\), etc. is a diagonal matrix containing the vectorized material property distribution.
@@ -5692,6 +5743,7 @@ andexy2h(...), then normalizes them to unit forward power using\[ \Re\left[\mathrm{inner\_product}(e, h, \mathrm{conj\_h}=True)\right] = 1, \]+so the returned fields represent a unit-power forward mode under the discrete Yee-grid Poynting inner product.
@@ -6347,21 +6399,23 @@ identical representatives for nearly symmetric modes.\[ \imath \omega \epsilon_{zz} E_z = \hat{\partial}_x H_y - \hat{\partial}_y H_x \\ \]+as well as the intermediate equations
\[ \begin{aligned} -\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\ -\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\ +\imath \beta H_y &= \imath \omega \epsilon_{xx} E_x - \hat{\partial}_y H_z \\ +\imath \beta H_x &= -\imath \omega \epsilon_{yy} E_y - \hat{\partial}_x H_z \\ \end{aligned} \]+Combining these, we get
\[ \begin{aligned} -E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} (( +E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} (( \hat{\partial}_y \hat{\partial}_x H_z -\hat{\partial}_x \hat{\partial}_y H_z) + \imath \omega (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y)) - &= \frac{1}{\imath \beta \epsilon_{zz}} (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y) + &= \frac{1}{\imath \beta \epsilon_{zz}} (\hat{\partial}_x \epsilon_{xx} E_x + \hat{\partial}_y \epsilon{yy} E_y) \end{aligned} \]@@ -7280,10 +7334,12 @@ E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} (( \(\beta\) to changes in the dielectric structure \(\epsilon\).The output is a vector of the same size as
-vec(epsilon), with each element specifying the sensitivity ofwavenumberto changes in the corresponding element invec(epsilon), i.e.\[sens_{i} = \frac{\partial\beta}{\partial\epsilon_i}\]+\[ sens_{i} = \frac{\partial\beta}{\partial\epsilon_i} \]+An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
-\[\beta^2 E_{xy} = A_E E_{xy}\]+\[ \beta^2 E_{xy} = A_E E_{xy} \]+where \(A_E\) is the waveguide operator from
@@ -7291,11 +7347,13 @@ we can differentiate with respect to one of the \(\epsi (2 \beta) \partial_{\epsilon_i}(\beta) E_{xy} + \beta^2 \partial_{\epsilon_i} E_{xy} = \partial_{\epsilon_i}(A_E) E_{xy} + A_E \partial_{\epsilon_i} E_{xy} \]operator_e(), and \(E_{xy} = \begin{bmatrix} E_x \\ E_y \end{bmatrix}\), we can differentiate with respect to one of the \(\epsilon\) elements (i.e. at one Yee grid point), \(\epsilon_i\):We then multiply by \(H_{yx}^\star = \begin{bmatrix}H_y^\star \\ -H_x^\star \end{bmatrix}\) from the left:
\[ (2 \beta) \partial_{\epsilon_i}(\beta) H_{yx}^\star E_{xy} + \beta^2 H_{yx}^\star \partial_{\epsilon_i} E_{xy} = H_{yx}^\star \partial_{\epsilon_i}(A_E) E_{xy} + H_{yx}^\star A_E \partial_{\epsilon_i} E_{xy} \]+However, \(H_{yx}^\star\) is actually a left-eigenvector of \(A_E\). This can be verified by inspecting the form of
operator_h(\(A_H\)) and comparing its conjugate transpose tooperator_e(\(A_E\)). Also, note \(H_{yx}^\star \cdot E_{xy} = H^\star \times E\) recalls the mode orthogonality relation. See doi:10.5194/ars-9-85-201 @@ -7303,11 +7361,13 @@ for a similar approach. Therefore,\[ H_{yx}^\star A_E \partial_{\epsilon_i} E_{xy} = \beta^2 H_{yx}^\star \partial_{\epsilon_i} E_{xy} \]+and we can simplify to
\[ \partial_{\epsilon_i}(\beta) = \frac{1}{2 \beta} \frac{H_{yx}^\star \partial_{\epsilon_i}(A_E) E_{xy} }{H_{yx}^\star E_{xy}} \]+This expression can be quickly calculated for all \(i\) by writing out the various terms of \(\partial_{\epsilon_i} A_E\) and recognizing that the vector-matrix-vector products (i.e. scalars) \(sens_i = \vec{v}_{left} \partial_{\epsilon_i} (\epsilon_{xyz}) \vec{v}_{right}\), indexed by \(i\), can be expressed as @@ -7514,7 +7574,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lef
mode_numbers- Sequence[int]+Sequence[int] @@ -7599,7 +7659,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lefmode_margin- int+int @@ -7687,7 +7747,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lefmode_number- int+int @@ -7787,6 +7847,7 @@ longitudinal Poynting flux,\[ \frac{1}{2}\int (E_x H_y - E_y H_x) \, dx \, dy \]+with the Yee-grid staggering and optional propagation-phase adjustment used by the waveguide helpers in this module.
@@ -8040,7 +8101,7 @@ solve for an eigenmode propagating through that slice.mode_number- int+int @@ -8093,7 +8154,7 @@ solve for an eigenmode propagating through that slice.axis- int+int @@ -8109,7 +8170,7 @@ solve for an eigenmode propagating through that slice.polarity- int+int @@ -8125,7 +8186,7 @@ solve for an eigenmode propagating through that slice.slices- Sequence[slice]+Sequence[slice] @@ -8142,7 +8203,7 @@ solve for an eigenmode propagating through that slice.epsilon- fdfield+fdfield @@ -8158,7 +8219,7 @@ solve for an eigenmode propagating through that slice.mu- fdfield | None+fdfield | None @@ -8184,7 +8245,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -8194,7 +8255,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -8206,7 +8267,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -8218,7 +8279,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -8231,7 +8292,7 @@ propagation axis- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -8298,7 +8359,7 @@ necessary to position a unidirectional source at the slice location.E- cfdfield+cfdfield @@ -8367,7 +8428,7 @@ necessary to position a unidirectional source at the slice location.axis- int+int @@ -8383,7 +8444,7 @@ necessary to position a unidirectional source at the slice location.polarity- int+int @@ -8399,7 +8460,7 @@ necessary to position a unidirectional source at the slice location.slices- Sequence[slice]+Sequence[slice] @@ -8416,7 +8477,7 @@ necessary to position a unidirectional source at the slice location.mu- fdfield | None+fdfield | None @@ -8492,6 +8553,7 @@ same sign convention used elsewhere in the package:\[ \sum \mathrm{overlap\_e} \; E_\mathrm{other}^* \]+where the sum is over the full Yee-grid field storage.
The construction uses a two-cell window immediately upstream of the selected slice:
@@ -8507,6 +8569,7 @@ overlap relation involving\[ \int ((E \times H_\mathrm{mode}) + (E_\mathrm{mode} \times H)) \cdot dn. \]+E x H_mode + E_mode x H -> Ex Hmy - EyHmx + Emx Hy - Emy Hx (Z-prop) Ex we/B Emx + Ex i/B dy Hmz - Ey (-we/B Emy) - Ey i/B dx Hmz @@ -8586,7 +8649,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)
axis- int+int @@ -8602,7 +8665,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)polarity- int+int @@ -8618,7 +8681,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)slices- Sequence[slice]+Sequence[slice] @@ -8714,7 +8777,7 @@ where it is valid.E- cfdfield+cfdfield @@ -8767,7 +8830,7 @@ where it is valid.axis- int+int @@ -8783,7 +8846,7 @@ where it is valid.polarity- int+int @@ -8799,7 +8862,7 @@ where it is valid.slices- Sequence[slice]+Sequence[slice] @@ -8845,6 +8908,7 @@ along the propagation axis and applies the phase factor+\[ e^{-i \, \mathrm{polarity} \, wavenumber \, \Delta z} \]+to each copied slice.
@@ -8880,11 +8944,13 @@ and the fields are assumed to have the form\[ \vec{E}(r, y, \theta), \vec{H}(r, y, \theta) \propto e^{-\imath m \theta}, \]+where
mis the angular wavenumber returned bysolve_mode(s). It is often convenient to introduce the corresponding linear wavenumber\[ \beta = \frac{m}{r_{\min}}, \]+so that the cylindrical problem resembles the straight-waveguide problem with additional metric factors.
Those metric factors live on the staggered radial Yee grids. If the left edge of @@ -8892,33 +8958,36 @@ the computational window is at
r = r_{\min}, define the electric-gr magnetic-grid radial sample locations by\[ \begin{aligned} -r_a(n) &= r_{\min} + \sum_{j \le n} \Delta r_{e, j}, \\ -r_b\!\left(n + \tfrac{1}{2}\right) &= r_{\min} + \tfrac{1}{2}\Delta r_{e, n} - + \sum_{j < n} \Delta r_{h, j}, +r_a(n) &= r_{\min} + \sum_{j \le n} \Delta r_{e, j}, \\ +r_b\!\left(n + \tfrac{1}{2}\right) &= r_{\min} + \tfrac{1}{2}\Delta r_{e, n} + + \sum_{j < n} \Delta r_{h, j}, \end{aligned} \]+and from them the diagonal metric matrices
\[ \begin{aligned} -T_a &= \operatorname{diag}(r_a / r_{\min}), \\ -T_b &= \operatorname{diag}(r_b / r_{\min}). +T_a &= \operatorname{diag}(r_a / r_{\min}), \\ +T_b &= \operatorname{diag}(r_b / r_{\min}). \end{aligned} \]+With the same forward/backward derivative notation used in
waveguide_2d, the coordinate-transformed discrete curl equations used here are\[ \begin{aligned} --\imath \omega \mu_{rr} H_r &= \tilde{\partial}_y E_z + \imath \beta T_a^{-1} E_y, \\ --\imath \omega \mu_{yy} H_y &= -\imath \beta T_b^{-1} E_r +-\imath \omega \mu_{rr} H_r &= \tilde{\partial}_y E_z + \imath \beta T_a^{-1} E_y, \\ +-\imath \omega \mu_{yy} H_y &= -\imath \beta T_b^{-1} E_r - T_b^{-1} \tilde{\partial}_r (T_a E_z), \\ --\imath \omega \mu_{zz} H_z &= \tilde{\partial}_r E_y - \tilde{\partial}_y E_r, \\ -\imath \beta H_y &= -\imath \omega T_b \epsilon_{rr} E_r - T_b \hat{\partial}_y H_z, \\ -\imath \beta H_r &= \imath \omega T_a \epsilon_{yy} E_y +-\imath \omega \mu_{zz} H_z &= \tilde{\partial}_r E_y - \tilde{\partial}_y E_r, \\ +\imath \beta H_y &= -\imath \omega T_b \epsilon_{rr} E_r - T_b \hat{\partial}_y H_z, \\ +\imath \beta H_r &= \imath \omega T_a \epsilon_{yy} E_y - T_b T_a^{-1} \hat{\partial}_r (T_b H_z), \\ -\imath \omega E_z &= T_a \epsilon_{zz}^{-1} +\imath \omega E_z &= T_a \epsilon_{zz}^{-1} \left(\hat{\partial}_r H_y - \hat{\partial}_y H_r\right). \end{aligned} \]+The first three equations are the cylindrical analogue of the straight-guide relations for
H_z = \frac{1}{-\imath \omega \mu_{zz}} \left(\tilde{\partial}_r E_y - \tilde{\partial}_y E_r\right). \]H_r,H_y, andH_z. The next two are the metric-weighted versions of the straight-guide identities for\imath \beta H_yand @@ -8932,6 +9001,7 @@ and then eliminateH_zwithThis yields the transverse electric eigenproblem implemented by
cylindrical_operator(...):\[ @@ -8941,8 +9011,8 @@ H_z = \frac{1}{-\imath \omega \mu_{zz}} \left( \omega^2 \begin{bmatrix} -T_b^2 \mu_{yy} \epsilon_{xx} & 0 \\ -0 & T_a^2 \mu_{xx} \epsilon_{yy} +T_b^2 \mu_{yy} \epsilon_{xx} & 0 \\ +0 & T_a^2 \mu_{xx} \epsilon_{yy} \end{bmatrix} + \begin{bmatrix} @@ -8951,7 +9021,7 @@ T_b^2 \mu_{yy} \epsilon_{xx} & 0 \\ \end{bmatrix} T_b \mu_{zz}^{-1} \begin{bmatrix} --\tilde{\partial}_y & \tilde{\partial}_x +-\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + \begin{bmatrix} @@ -8960,12 +9030,13 @@ T_b \mu_{zz}^{-1} \end{bmatrix} T_a \epsilon_{zz}^{-1} \begin{bmatrix} -\hat{\partial}_x T_b \epsilon_{xx} & +\hat{\partial}_x T_b \epsilon_{xx} & \hat{\partial}_y T_a \epsilon_{yy} \end{bmatrix} \right) \begin{bmatrix} E_r \\ E_y \end{bmatrix}. \]+Since
\beta = m / r_{\min}, the solver implemented in this file returns the angular wavenumberm, while the operator itself is most naturally written in terms of the linear quantity\beta. The helpers below reconstruct the full @@ -9015,17 +9086,18 @@ product used bywaveguide_2d.Cylindrical coordinate waveguide operator of the form
\[ - (\omega^2 \begin{bmatrix} T_b T_b \mu_{yy} \epsilon_{xx} & 0 \\ - 0 & T_a T_a \mu_{xx} \epsilon_{yy} \end{bmatrix} + + (\omega^2 \begin{bmatrix} T_b T_b \mu_{yy} \epsilon_{xx} & 0 \\ + 0 & T_a T_a \mu_{xx} \epsilon_{yy} \end{bmatrix} + \begin{bmatrix} -T_b \mu_{yy} \hat{\partial}_y \\ T_a \mu_{xx} \hat{\partial}_x \end{bmatrix} T_b \mu_{zz}^{-1} - \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + + \begin{bmatrix} -\tilde{\partial}_y & \tilde{\partial}_x \end{bmatrix} + \begin{bmatrix} \tilde{\partial}_x \\ \tilde{\partial}_y \end{bmatrix} T_a \epsilon_{zz}^{-1} - \begin{bmatrix} \hat{\partial}_x T_b \epsilon_{xx} & \hat{\partial}_y T_a \epsilon_{yy} \end{bmatrix}) + \begin{bmatrix} \hat{\partial}_x T_b \epsilon_{xx} & \hat{\partial}_y T_a \epsilon_{yy} \end{bmatrix}) \begin{bmatrix} E_r \\ E_y \end{bmatrix} \]+for use with a field vector of the form
[E_r, E_y].This operator can be used to form an eigenvalue problem of the form A @ [E_r, E_y] = beta**2 * [E_r, E_y]
@@ -9344,7 +9416,7 @@ the fields, withbeta = angular_wavenumber / rmin).mode_number- int+int @@ -9914,15 +9986,15 @@ from E_r and E_y in order to then calculate E_z.Returns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
-This operator is created directly from the initial coordinate-transformed equations: -$$ +
This operator is created directly from the initial coordinate-transformed equations:
+\[ \begin{aligned} --\imath \omega \mu_{rr} H_r &= \tilde{\partial}y E_z + \imath \beta T_a^{-1} E_y, \ --\imath \omega \mu E_r - - T_b^{-1} \tilde{\partial}} H_y &= -\imath \beta T_b^{-1r (T_a E_z), \ --\imath \omega \mu_y E_r, +-\imath \omega \mu_{rr} H_r &= \tilde{\partial}_y E_z + \imath \beta T_a^{-1} E_y, \\ +-\imath \omega \mu_{yy} H_y &= -\imath \beta T_b^{-1} E_r + - T_b^{-1} \tilde{\partial}_r (T_a E_z), \\ +-\imath \omega \mu_{zz} H_z &= \tilde{\partial}_r E_y - \tilde{\partial}_y E_r, \end{aligned} -$$} H_z &= \tilde{\partial}_r E_y - \tilde{\partial +\]Parameters:
@@ -10371,6 +10443,7 @@ enforces unit forward power under the discrete inner product\[ \frac{1}{2}\int (E_r H_y^* - E_y H_r^*) \, dr \, dy. \]+The angular wavenumber
@@ -10407,7 +10480,8 @@ mathematical background.mis first converted into the full three-component fields, then the overall complex phase and sign are fixed so the result is reproducible for symmetric modes.Timestep¶
From the discussion of "Plane waves and the Dispersion relation" in
-meanas.fdmath, we have\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]+\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]+or, if \(\Delta_x = \Delta_y = \Delta_z\), then \(c \Delta_t < \frac{\Delta_x}{\sqrt{3}}\).
Based on this, we can set
dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2) @@ -10415,54 +10489,58 @@ we haveThe
dx_min,dy_min,dz_minshould be the minimum value across both the base and derived grids.Poynting Vector and Energy Conservation¶
Let
-\[ \begin{aligned} - \tilde{S}_{l, l', \vec{r}} &=& &\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}} \\ - &=& &\vec{x} (\tilde{E}^y_{l,m+1,n,p} \hat{H}^z_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^z_{l,m+1,n,p} \hat{H}^y_{l', \vec{r} + \frac{1}{2}}) \\ - & &+ &\vec{y} (\tilde{E}^z_{l,m,n+1,p} \hat{H}^x_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^x_{l,m,n+1,p} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \\ - & &+ &\vec{z} (\tilde{E}^x_{l,m,n,p+1} \hat{H}^y_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^y_{l,m,n,p+1} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) +\[ +\begin{aligned} + \tilde{S}_{l, l', \vec{r}} &=& &\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}} \\ + &=& &\vec{x} (\tilde{E}^y_{l,m+1,n,p} \hat{H}^z_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^z_{l,m+1,n,p} \hat{H}^y_{l', \vec{r} + \frac{1}{2}}) \\ + & &+ &\vec{y} (\tilde{E}^z_{l,m,n+1,p} \hat{H}^x_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^x_{l,m,n+1,p} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \\ + & &+ &\vec{z} (\tilde{E}^x_{l,m,n,p+1} \hat{H}^y_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^y_{l,m,n,p+1} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \end{aligned} \]+where \(\vec{r} = (m, n, p)\) and \(\otimes\) is a modified cross product in which the \(\tilde{E}\) terms are shifted as indicated.
By taking the divergence and rearranging terms, we can show that
\[ \begin{aligned} \hat{\nabla} \cdot \tilde{S}_{l, l', \vec{r}} - &= \hat{\nabla} \cdot (\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}}) \\ - &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{l, \vec{r}} - + &= \hat{\nabla} \cdot (\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}}) \\ + &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{l, \vec{r}} - \tilde{E}_{l, \vec{r}} \cdot \hat{\nabla} \times \hat{H}_{l', \vec{r} + \frac{1}{2}} \\ - &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot + &= \hat{H}_{l', \vec{r} + \frac{1}{2}} \cdot (-\tilde{\partial}_t \mu_{\vec{r} + \frac{1}{2}} \hat{H}_{l - \frac{1}{2}, \vec{r} + \frac{1}{2}} - \hat{M}_{l, \vec{r} + \frac{1}{2}}) - \tilde{E}_{l, \vec{r}} \cdot (\hat{\partial}_t \tilde{\epsilon}_{\vec{r}} \tilde{E}_{l'+\frac{1}{2}, \vec{r}} + \tilde{J}_{l', \vec{r}}) \\ - &= \hat{H}_{l'} \cdot (-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) - + &= \hat{H}_{l'} \cdot (-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) - \tilde{E}_l \cdot (\epsilon / \Delta_t )(\tilde{E}_{l'+\frac{1}{2}} - \tilde{E}_{l'-\frac{1}{2}}) - \hat{H}_{l'} \cdot \hat{M}_{l} - \tilde{E}_l \cdot \tilde{J}_{l'} \\ \end{aligned} \]+where in the last line the spatial subscripts have been dropped to emphasize the time subscripts \(l, l'\), i.e.
\[ \begin{aligned} - \tilde{E}_l &= \tilde{E}_{l, \vec{r}} \\ - \hat{H}_l &= \tilde{H}_{l, \vec{r} + \frac{1}{2}} \\ - \tilde{\epsilon} &= \tilde{\epsilon}_{\vec{r}} \\ + \tilde{E}_l &= \tilde{E}_{l, \vec{r}} \\ + \hat{H}_l &= \tilde{H}_{l, \vec{r} + \frac{1}{2}} \\ + \tilde{\epsilon} &= \tilde{\epsilon}_{\vec{r}} \\ \end{aligned} \]+etc. For \(l' = l + \frac{1}{2}\) we get
\[ \begin{aligned} \hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} - &= \hat{H}_{l + \frac{1}{2}} \cdot + &= \hat{H}_{l + \frac{1}{2}} \cdot (-\mu / \Delta_t)(\hat{H}_{l + \frac{1}{2}} - \hat{H}_{l - \frac{1}{2}}) - \tilde{E}_l \cdot (\epsilon / \Delta_t)(\tilde{E}_{l+1} - \tilde{E}_l) - \hat{H}_{l'} \cdot \hat{M}_l - \tilde{E}_l \cdot \tilde{J}_{l + \frac{1}{2}} \\ - &= (-\mu / \Delta_t)(\hat{H}^2_{l + \frac{1}{2}} - \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}) - + &= (-\mu / \Delta_t)(\hat{H}^2_{l + \frac{1}{2}} - \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}}) - (\epsilon / \Delta_t)(\tilde{E}_{l+1} \cdot \tilde{E}_l - \tilde{E}^2_l) - \hat{H}_{l'} \cdot \hat{M}_l - \tilde{E}_l \cdot \tilde{J}_{l + \frac{1}{2}} \\ - &= -(\mu \hat{H}^2_{l + \frac{1}{2}} + &= -(\mu \hat{H}^2_{l + \frac{1}{2}} +\epsilon \tilde{E}_{l+1} \cdot \tilde{E}_l) / \Delta_t \\ +(\mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} +\epsilon \tilde{E}^2_l) / \Delta_t \\ @@ -10470,11 +10548,12 @@ For \(l' = l + \frac{1}{2}\) we get - \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\ \end{aligned} \]+and for \(l' = l - \frac{1}{2}\),
\[ \begin{aligned} \hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} - &= (\mu \hat{H}^2_{l - \frac{1}{2}} + &= (\mu \hat{H}^2_{l - \frac{1}{2}} +\epsilon \tilde{E}_{l-1} \cdot \tilde{E}_l) / \Delta_t \\ -(\mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} +\epsilon \tilde{E}^2_l) / \Delta_t \\ @@ -10482,28 +10561,31 @@ For \(l' = l + \frac{1}{2}\) we get - \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\ \end{aligned} \]+These two results form the discrete time-domain analogue to Poynting's theorem. They hint at the expressions for the energy, which can be calculated at the same time-index as either the E or H field:
\[ \begin{aligned} - U_l &= \epsilon \tilde{E}^2_l + \mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} \\ - U_{l + \frac{1}{2}} &= \epsilon \tilde{E}_l \cdot \tilde{E}_{l + 1} + \mu \hat{H}^2_{l + \frac{1}{2}} \\ + U_l &= \epsilon \tilde{E}^2_l + \mu \hat{H}_{l + \frac{1}{2}} \cdot \hat{H}_{l - \frac{1}{2}} \\ + U_{l + \frac{1}{2}} &= \epsilon \tilde{E}_l \cdot \tilde{E}_{l + 1} + \mu \hat{H}^2_{l + \frac{1}{2}} \\ \end{aligned} \]+Rewriting the Poynting theorem in terms of the energy expressions,
\[ \begin{aligned} (U_{l+\frac{1}{2}} - U_l) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\ - \hat{H}_{l+\frac{1}{2}} \cdot \hat{M}_l \\ - \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\ (U_l - U_{l-\frac{1}{2}}) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\ - \hat{H}_{l-\frac{1}{2}} \cdot \hat{M}_l \\ - \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\ \end{aligned} \]+This result is exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary.
Note that each value of \(J\) contributes to the energy twice (i.e. once per field update) @@ -10514,16 +10596,46 @@ extract the frequency-domain response by performing an on-the-fly Fourier transf of the time-domain fields.
accumulate_phasorinmeanas.fdtd.phasorperforms the phase accumulation for one or more target frequencies, while leaving source normalization and simulation-loop -policy to the caller. Convenience wrappersaccumulate_phasor_e, -accumulate_phasor_h, andaccumulate_phasor_japply the usual Yee time offsets. +policy to the caller.temporal_phasor(...)andtemporal_phasor_scale(...)+apply the same Fourier sum to a scalar waveform, which is useful when a pulsed +source envelope must be normalized before being applied to a point source or +mode source.real_injection_scale(...)is the corresponding helper for the +common real-valued injection patternnumpy.real(scale * waveform). Convenience +wrappersaccumulate_phasor_e,accumulate_phasor_h, andaccumulate_phasor_j+apply the usual Yee time offsets.reconstruct_real(...)and the corresponding +reconstruct_real_e/h/j(...)wrappers perform the inverse operation, converting +phasors back into real-valued field snapshots at explicit Yee-aligned times. +For scalaromega, the reconstruction helpers accept either a plain field +phasor or the batched(1, *sample_shape)form used by the accumulator helpers. The helpers accumulate\[ \Delta_t \sum_l w_l e^{-i \omega t_l} f_l \]+with caller-provided sample times and weights. In this codebase the matching electric-current convention is typically
+E -= dt * J / epsilonin FDTD and-i \omega Jon the right-hand side of the FDFD wave equation.For FDTD/FDFD equivalence, this phasor path is the primary comparison workflow. +It isolates the guided
++\omegaresponse that the frequency-domain solver +targets directly, regardless of whether the underlying FDTD run used real- or +complex-valued fields.For exact pulsed FDTD/FDFD equivalence it is often simplest to keep the +injected source, fields, and CPML auxiliary state complex-valued. The +
+real_injection_scale(...)helper is instead for the more ordinary one-run +real-valued source path, where the intended positive-frequency waveform is +injected throughnumpy.real(scale * waveform)and any remaining negative- +frequency leakage is controlled by the pulse bandwidth and run length.+
reconstruct_real(...)is for a different question: given a phasor, what late +real-valued field snapshot should it produce? That raw-snapshot comparison is +stricter and noisier because a monitor plane generally contains both the guided +field and the remaining orthogonal content,\[ E_{\text{monitor}} = E_{\text{guided}} + E_{\text{residual}} . \]+ +Phasor/modal comparisons mostly validate the guided
+\omegaterm. Raw +real-field comparisons expose both terms at once, so they should be treated as +secondary diagnostics rather than the main solver-equivalence benchmark.The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written
\[ f_r(t) = (1 - \frac{1}{2} (\omega (t - \tau))^2) e^{-(\frac{\omega (t - \tau)}{2})^2} \]+with \(\tau > \frac{2 * \pi}{\omega}\) as a minimum delay to avoid a discontinuity at t=0 (assuming the source is off for t<0 this gives \(\sim 10^{-3}\) error at t=0).
Boundary conditions¶
@@ -10553,6 +10665,7 @@ coordinate system in both places:\[ E \leftarrow E - \Delta_t J / \epsilon \]+which matches the FDFD right-hand side
\[ -i \omega J. @@ -10953,7 +11066,7 @@ withNone.axis- int+int @@ -10969,7 +11082,7 @@ withNone.polarity- int+int @@ -11002,7 +11115,7 @@ withNone.thickness- int+int @@ -11124,7 +11237,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -11134,7 +11247,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -11146,7 +11259,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -11158,7 +11271,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -11218,7 +11331,7 @@ withNone.cpml_params- Sequence[Sequence[dict[str, Any] | None]]+Sequence[Sequence[dict[str, Any] | None]] @@ -11273,7 +11386,7 @@ Entries are the dictionaries returned bycpml_params(...); useepsilon- fdfield+fdfield @@ -11496,19 +11609,20 @@ boundaries of the correspondingUcell. For example, assert_close(sum(planes), du_half_h2e[mask])(see
-meanas.tests.test_fdtd.test_poynting_planes)The full relationship is -$$ +
The full relationship is
+\[ \begin{aligned} (U_{l+\frac{1}{2}} - U_l) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}{l, l + \frac{1}{2}} \ - - \hat{H}}{2}} \cdot \hat{Ml \ - - \tilde{E}_l \cdot \tilde{J} \ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\ + - \hat{H}_{l+\frac{1}{2}} \cdot \hat{M}_l \\ + - \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\ (U_l - U_{l-\frac{1}{2}}) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}}{2}{l, l - \frac{1}{2}} \ - - \hat{H}}{2}} \cdot \hat{Ml \ - - \tilde{E}_l \cdot \tilde{J} \ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\ + - \hat{H}_{l-\frac{1}{2}} \cdot \hat{M}_l \\ + - \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\ \end{aligned} -$$}{2} +\]+These equalities are exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary. (See
@@ -11529,7 +11643,7 @@ No time- or spatial-averaging is necessary. (Seemeanas.fdtddocs for derivation.)meanas.fdtddocs fe- fdfield+fdfield @@ -11545,7 +11659,7 @@ No time- or spatial-averaging is necessary. (Seemeanas.fdtddocs fh- fdfield+fdfield @@ -11712,7 +11826,7 @@ energy cell.e0- fdfield+fdfield @@ -11728,7 +11842,7 @@ energy cell.h1- fdfield+fdfield @@ -11744,7 +11858,7 @@ energy cell.e2- fdfield+fdfield @@ -11760,7 +11874,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -11776,7 +11890,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -11879,7 +11993,7 @@ energy cell.h0- fdfield+fdfield @@ -11895,7 +12009,7 @@ energy cell.e1- fdfield+fdfield @@ -11911,7 +12025,7 @@ energy cell.h2- fdfield+fdfield @@ -11927,7 +12041,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -11943,7 +12057,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -12048,7 +12162,7 @@ energy cell.e0- fdfield+fdfield @@ -12064,7 +12178,7 @@ energy cell.h1- fdfield+fdfield @@ -12080,7 +12194,7 @@ energy cell.e2- fdfield+fdfield @@ -12096,7 +12210,7 @@ energy cell.h3- fdfield+fdfield @@ -12112,7 +12226,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -12128,7 +12242,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -12233,7 +12347,7 @@ energy cell.h0- fdfield+fdfield @@ -12249,7 +12363,7 @@ energy cell.e1- fdfield+fdfield @@ -12265,7 +12379,7 @@ energy cell.h2- fdfield+fdfield @@ -12281,7 +12395,7 @@ energy cell.e3- fdfield+fdfield @@ -12297,7 +12411,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -12313,7 +12427,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -12414,7 +12528,7 @@ half-step energy balance) even though it directly changesEonly onj0- fdfield+fdfield @@ -12431,7 +12545,7 @@ current work term.e1- fdfield+fdfield @@ -12532,7 +12646,7 @@ current work term.ee- fdfield+fdfield @@ -12548,7 +12662,7 @@ current work term.hh- fdfield+fdfield @@ -12564,7 +12678,7 @@ current work term.epsilon- fdfield | float | None+fdfield | float | None @@ -12580,7 +12694,7 @@ current work term.mu- fdfield | float | None+fdfield | float | None @@ -12685,6 +12799,18 @@ the sampling policy. The accumulated quantity is@@ -12903,8 +13261,9 @@ series of other matrices). which covers a superset of this material with similar notation and more detail.accumulate_phasor_h(..., step=l)forH_{l + 1/2}- +
accumulate_phasor_j(..., step=l)forJ_{l + 1/2}
temporal_phasor(...)andtemporal_phasor_scale(...)apply the same Fourier +sum to a 1D scalar waveform. They are useful for normalizing a pulsed source +before that scalar waveform is applied to a point source or spatial mode source. +real_injection_scale(...)is a companion helper for the common real-valued +injection patternnumpy.real(scale * waveform), wherewaveformis the +analytic positive-frequency signal and the injected real current should still +produce a desired phasor response. +reconstruct_real(...)and itsE/H/Jwrappers perform the inverse operation: +they turn one or more phasors back into real-valued field snapshots at explicit +Yee-aligned sample times. For a scalar target frequency they accept either a +plain field phasor or the batched(1, *sample_shape)form used elsewhere in +this module.These helpers do not choose warmup/accumulation windows or pulse-envelope normalization. They also do not impose a current sign convention. In this codebase, electric-current injection normally appears as
E -= dt * J / epsilon, @@ -12756,6 +12882,154 @@ factor was derived from a discrete sum that already includesdt, pa++ ++
+temporal_phasor + + +¶+ +temporal_phasor( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, +) -> NDArray[numpy.complexfloating] ++ ++ +Fourier-project a 1D temporal waveform onto one or more angular frequencies.
+The returned quantity is
++dt * sum(exp(-1j * omega * t_step) * samples[step_index]) +where
+ + +t_step = (start_step + step_index + offset_steps) * dt.+ + ++ ++
+temporal_phasor_scale + + +¶+ +temporal_phasor_scale( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, + target: ArrayLike = 1.0, +) -> NDArray[numpy.complexfloating] ++ ++ +Return the scalar multiplier that gives a desired temporal phasor response.
+The returned scale satisfies
++temporal_phasor(scale * samples, omegas, dt, ...) == target +for each target frequency. The result keeps a leading frequency axis even +when
+ + +omegasis scalar.+ + ++ ++
+real_injection_scale + + +¶+ +real_injection_scale( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, + target: ArrayLike = 1.0, +) -> NDArray[numpy.complexfloating] ++ ++ +Return the scale for a real-valued injection built from an analytic waveform.
+If the time-domain source is applied as
++numpy.real(scale * samples[step]) +then the desired positive-frequency phasor is obtained by compensating for +the 1/2 factor between the real-valued source and its analytic component:
++scale = 2 * target / temporal_phasor(samples, ...) +This helper normalizes only the intended positive-frequency component. Any +residual negative-frequency leakage is controlled by the waveform design and +the accumulation window.
+ + ++ + ++ ++
+reconstruct_real + + +¶+ +reconstruct_real( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, + *, + offset_steps: float = 0.0, +) -> NDArray[numpy.floating] ++ ++ +Reconstruct a real-valued field snapshot from one or more phasors.
+The returned quantity is
++real(phasor * exp(1j * omega * t_step)) +where
+t_step = (step + offset_steps) * dt.For multi-frequency inputs, the leading frequency axis is preserved. For a +scalar
+ + +omega, callers may pass either(1, *sample_shape)or +sample_shape; the return shape matches that choice.+ ++
accumulate_phasor_e @@ -12846,6 +13120,90 @@ factor was derived from a discrete sum that already includesdt, pa+ + ++ ++
+reconstruct_real_e + + +¶+ +reconstruct_real_e( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] ++ ++ +Reconstruct a real E-field snapshot taken at integer timestep
+ + +step.+ + ++ ++
+reconstruct_real_h + + +¶+ +reconstruct_real_h( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] ++ ++ +Reconstruct a real H-field snapshot corresponding to
+ + +H_{step + 1/2}.+ + +++
+reconstruct_real_j + + +¶+ +reconstruct_real_j( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] ++ ++ +Reconstruct a real current snapshot corresponding to
+ + +J_{step + 1/2}.Scalar derivatives and cell shifts¶
Define the discrete forward derivative as - $$ [\tilde{\partial}x f] - f_m) $$ - where }{2}} = \frac{1}{\Delta_{x, m}} (f_{m + 1\(f\) is a function defined at discrete locations on the x-axis (labeled using \(m\)). + +
\[ [\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.
@@ -12913,8 +13272,9 @@ along the x-axis, the forward derivative isderiv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]Likewise, discrete reverse derivative is - $$ [\hat{\partial}x f ]) $$ - or}{2}} = \frac{1}{\Delta_{x, m}} (f_{m} - f_{m - 1
+ +\[ [\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 @@ -12949,7 +13309,9 @@ Periodic boundaries are used here and elsewhere unless otherwise noted. 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}) $$
+ +\[ \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. If the positions labeled with \(m\) are considered the "base" or "original" grid, @@ -12961,12 +13323,18 @@ See the
Grid descriptionsection below for additional information o and generalization to three dimensions.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] + - \vec{y} [\tilde{\partial}}{2},n,py f] + - \vec{z} [\tilde{\partial}}{2},pz f] $$ - $$ [\hat{\nabla} f]}{2}{m,n,p} = \vec{x} [\hat{\partial}_x f] + - \vec{y} [\hat{\partial}}{2},n,py f] + - \vec{z} [\hat{\partial}}{2},pz f] $$}{2}
+
+\[ + [\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], @@ -12989,12 +13357,18 @@ 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 + +
+\[ + \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}}{2},n,p{m,n,p} = \vec{x} g^x + + \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}} $$}{2},n,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) /: /| @@ -13010,14 +13384,20 @@ on the direction of the shift. We write it asDivergences¶
There are also two divergences,
-$$ d_{n,m,p} = [\tilde{\nabla} \cdot \hat{g}]{n,m,p} - = [\tilde{\partial}_x g^x] + - [\tilde{\partial}y g^y] + - [\tilde{\partial}z g^z] $$
-$$ d_{n,m,p} = [\hat{\nabla} \cdot \tilde{g}]{n,m,p} - = [\hat{\partial}_x g^x] + - [\hat{\partial}y g^y] + - [\hat{\partial}z g^z] $$
+\[ + 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] + @@ -13056,16 +13436,22 @@ is defined at the back-vector's (fore-vector's) location Curls¶The two curls are then
-$$ \begin{aligned} - \hat{h}{m + \frac{1}{2}, n + \frac{1}{2}, p + \frac{1}{2}} &= \ - [\tilde{\nabla} \times \tilde{g}] &= - \vec{x} (\tilde{\partial}}{2}, n + \frac{1}{2}, p + \frac{1}{2}y g^z}{2}} - \tilde{\partialz g^y) \ - &+ \vec{y} (\tilde{\partial}}{2},pz g^x}{2},n,p} - \tilde{\partialx g^z) \ - &+ \vec{z} (\tilde{\partial}}{2}x g^y}{2},p} - \tilde{\partialy g^z) - \end{aligned} $$}{2},n,p
+\[ + \begin{aligned} + \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{aligned} + \]+and
-$$ \tilde{h}{m - \frac{1}{2}, n - \frac{1}{2}, p - \frac{1}{2}} = - [\hat{\nabla} \times \hat{g}] $$}{2}, n - \frac{1}{2}, p - \frac{1}{2}
+\[ + \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})\) @@ -13103,19 +13489,25 @@ curl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],
Maxwell's Equations¶
If we discretize both space (m,n,p) and time (l), Maxwell's equations become
-$$ \begin{aligned} - \tilde{\nabla} \times \tilde{E}{l,\vec{r}} &= -\tilde{\partial}_t \hat{B} - - \hat{M}}{2}, \vec{r} + \frac{1}{2}{l, \vec{r} + \frac{1}{2}} \ - \hat{\nabla} \times \hat{H}}{2},\vec{r} + \frac{1}{2}} &= \hat{\partialt \tilde{D} - + \tilde{J}}{l-\frac{1}{2},\vec{r}} \ - \tilde{\nabla} \cdot \hat{B} &= 0 \ - \hat{\nabla} \cdot \tilde{D}}{2}, \vec{r} + \frac{1}{2}{l,\vec{r}} &= \rho - \end{aligned} $$}
+\[ + \begin{aligned} + \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, \vec{r} + \frac{1}{2}} \\ + \hat{\nabla} \times \hat{H}_{l-\frac{1}{2},\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{aligned} + \]+with
-$$ \begin{aligned} - \hat{B}{\vec{r}} &= \mu} + \frac{1}{2}} \cdot \hat{H{\vec{r} + \frac{1}{2}} \ - \tilde{D} - \end{aligned} $$}} &= \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}
+\[ + \begin{aligned} + \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{aligned} + \]+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, @@ -13177,94 +13569,108 @@ r - 1/2 = | / | /
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.
+ +\[ \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{aligned} - \tilde{\nabla} \times \tilde{E}_{l,\vec{r}} &= + \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}} &= + \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 (\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}}) &= + \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}}) &= + \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}} + &= \tilde{\partial}_t \tilde{J}_{l - \frac{1}{2}, \vec{r}} \end{aligned} \]+Frequency domain¶
We can substitute in a time-harmonic fields
\[ \begin{aligned} - \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\ - \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} + \tilde{E}_{l, \vec{r}} &= \tilde{E}_{\vec{r}} e^{-\imath \omega l \Delta_t} \\ + \tilde{J}_{l, \vec{r}} &= \tilde{J}_{\vec{r}} e^{-\imath \omega (l - \frac{1}{2}) \Delta_t} \end{aligned} \]+resulting in
\[ \begin{aligned} - \tilde{\partial}_t &\Rightarrow (e^{ \imath \omega \Delta_t} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{-\imath \omega \Delta_t / 2} = -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\ - \hat{\partial}_t &\Rightarrow (1 - e^{-\imath \omega \Delta_t}) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{ \imath \omega \Delta_t / 2} = -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\ - \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t + \tilde{\partial}_t &\Rightarrow (e^{ \imath \omega \Delta_t} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{-\imath \omega \Delta_t / 2} = -\imath \Omega e^{-\imath \omega \Delta_t / 2}\\ + \hat{\partial}_t &\Rightarrow (1 - e^{-\imath \omega \Delta_t}) / \Delta_t = \frac{-2 \imath}{\Delta_t} \sin(\omega \Delta_t / 2) e^{ \imath \omega \Delta_t / 2} = -\imath \Omega e^{ \imath \omega \Delta_t / 2}\\ + \Omega &= 2 \sin(\omega \Delta_t / 2) / \Delta_t \end{aligned} \]+This gives the frequency-domain wave equation,
\[ \hat{\nabla} \times (\mu^{-1}_{\vec{r} + \frac{1}{2}} \cdot \tilde{\nabla} \times \tilde{E}_{\vec{r}}) -\Omega^2 \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}} = -\imath \Omega \tilde{J}_{\vec{r}} e^{\imath \omega \Delta_t / 2} \\ \]+Plane waves and Dispersion relation¶
With uniform material distribution and no sources
\[ \begin{aligned} - \mu_{\vec{r} + \frac{1}{2}} &= \mu \\ - \epsilon_{\vec{r}} &= \epsilon \\ - \tilde{J}_{\vec{r}} &= 0 \\ + \mu_{\vec{r} + \frac{1}{2}} &= \mu \\ + \epsilon_{\vec{r}} &= \epsilon \\ + \tilde{J}_{\vec{r}} &= 0 \\ \end{aligned} \]+the frequency domain wave equation simplifies to
\[ \hat{\nabla} \times \tilde{\nabla} \times \tilde{E}_{\vec{r}} - \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]+Since \(\hat{\nabla} \cdot \tilde{E}_{\vec{r}} = 0\), we can simplify
\[ \begin{aligned} \hat{\nabla} \times \tilde{\nabla} \times \tilde{E}_{\vec{r}} - &= \tilde{\nabla}(\hat{\nabla} \cdot \tilde{E}_{\vec{r}}) - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\ - &= - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\ - &= - \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + &= \tilde{\nabla}(\hat{\nabla} \cdot \tilde{E}_{\vec{r}}) - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\ + &= - \hat{\nabla} \cdot \tilde{\nabla} \tilde{E}_{\vec{r}} \\ + &= - \tilde{\nabla}^2 \tilde{E}_{\vec{r}} \end{aligned} \]+and we get
-\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]+\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]+We can convert this to three scalar-wave equations of the form
\[ (\tilde{\nabla}^2 + K^2) \phi_{\vec{r}} = 0 \]+with \(K^2 = \Omega^2 \mu \epsilon\). Now we let
-\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]+\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]+resulting in
\[ \begin{aligned} - \tilde{\partial}_x &\Rightarrow (e^{ \imath k_x \Delta_x} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{ \imath k_x \Delta_x / 2} = \imath K_x e^{ \imath k_x \Delta_x / 2}\\ - \hat{\partial}_x &\Rightarrow (1 - e^{-\imath k_x \Delta_x}) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{-\imath k_x \Delta_x / 2} = \imath K_x e^{-\imath k_x \Delta_x / 2}\\ - K_x &= 2 \sin(k_x \Delta_x / 2) / \Delta_x \\ + \tilde{\partial}_x &\Rightarrow (e^{ \imath k_x \Delta_x} - 1) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{ \imath k_x \Delta_x / 2} = \imath K_x e^{ \imath k_x \Delta_x / 2}\\ + \hat{\partial}_x &\Rightarrow (1 - e^{-\imath k_x \Delta_x}) / \Delta_t = \frac{-2 \imath}{\Delta_x} \sin(k_x \Delta_x / 2) e^{-\imath k_x \Delta_x / 2} = \imath K_x e^{-\imath k_x \Delta_x / 2}\\ + K_x &= 2 \sin(k_x \Delta_x / 2) / \Delta_x \\ \end{aligned} \]+with similar expressions for the y and z dimnsions (and \(K_y, K_z\)).
This implies
\[ \tilde{\nabla}^2 = -(K_x^2 + K_y^2 + K_z^2) \phi_{\vec{r}} \\ K_x^2 + K_y^2 + K_z^2 = \Omega^2 \mu \epsilon = \Omega^2 / c^2 \]+where \(c = \sqrt{\mu \epsilon}\).
Assuming real \((k_x, k_y, k_z), \omega\) will be real only if
-\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]+\[ c^2 \Delta_t^2 = \frac{\Delta_t^2}{\mu \epsilon} < 1/(\frac{1}{\Delta_x^2} + \frac{1}{\Delta_y^2} + \frac{1}{\Delta_z^2}) \]+If \(\Delta_x = \Delta_y = \Delta_z\), this simplifies to \(c \Delta_t < \Delta_x / \sqrt{3}\). This last form can be interpreted as enforcing causality; the distance that light travels in one timestep (i.e., \(c \Delta_t\)) must be less than the diagonal @@ -13461,15 +13867,16 @@ mu = [mu_xx, mu_yy, mu_zz]
or
\[ - \epsilon = \begin{bmatrix} \epsilon_{xx} & 0 & 0 \\ - 0 & \epsilon_{yy} & 0 \\ - 0 & 0 & \epsilon_{zz} \end{bmatrix} -$$ -$$ - \mu = \begin{bmatrix} \mu_{xx} & 0 & 0 \\ - 0 & \mu_{yy} & 0 \\ - 0 & 0 & \mu_{zz} \end{bmatrix} + \epsilon = \begin{bmatrix} \epsilon_{xx} & 0 & 0 \\ + 0 & \epsilon_{yy} & 0 \\ + 0 & 0 & \epsilon_{zz} \end{bmatrix} \]+\[ + \mu = \begin{bmatrix} \mu_{xx} & 0 & 0 \\ + 0 & \mu_{yy} & 0 \\ + 0 & 0 & \mu_{zz} \end{bmatrix} +\]+where the off-diagonal terms (e.g.
epsilon_xy) are assumed to be zero.High-accuracy volumetric integration of shapes on multiple grids can be performed by the gridlock module.
@@ -13988,7 +14395,7 @@ normalized results are needed.axis- int+int @@ -14004,7 +14411,7 @@ normalized results are needed.shape- Sequence[int]+Sequence[int] @@ -14020,7 +14427,7 @@ normalized results are needed.shift_distance- int+int @@ -14097,7 +14504,7 @@ boundary conditions applied to the cells beyond the receding edge.axis- int+int @@ -14113,7 +14520,7 @@ boundary conditions applied to the cells beyond the receding edge.shape- Sequence[int]+Sequence[int] @@ -14129,7 +14536,7 @@ boundary conditions applied to the cells beyond the receding edge.shift_distance- int+int @@ -14488,7 +14895,7 @@ portions of the operator on the left side of the cross product.axis- int+int @@ -14504,7 +14911,7 @@ portions of the operator on the left side of the cross product.shape- Sequence[int]+Sequence[int] @@ -14578,7 +14985,7 @@ portions of the operator on the left side of the cross product.axis- int+int @@ -14594,7 +15001,7 @@ portions of the operator on the left side of the cross product.shape- Sequence[int]+Sequence[int] @@ -15035,7 +15442,7 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.shape- Sequence[int]+Sequence[int] @@ -15051,7 +15458,7 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.nvdim- int+int diff --git a/search/search_index.json b/search/search_index.json index a1c7210..6772d4a 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"meanas","text":"
meanasis a Python package for finite-difference electromagnetic simulation. It combines:
meanas.fdfdfor frequency-domain operators, sources, waveguide modes, and SCPMLmeanas.fdtdfor Yee-grid timestepping, CPML, energy/flux accounting, and phasor extractionmeanas.fdmathfor the shared discrete operators and derivations underneath both solversThis documentation is built directly from the package docstrings. The API pages are the source of truth for the mathematical derivations and calling conventions.
"},{"location":"#examples-and-api-map","title":"Examples and API Map","text":"For most users, the tracked examples under
examples/are the right entry point. They show the intended combinations of tools for solving complete problems.The API pages are better read as a toolbox map and derivation reference:
"},{"location":"#build-outputs","title":"Build outputs","text":"
- Use the FDTD API for time-domain stepping, CPML, and phasor extraction.
- Use the FDFD API for driven frequency-domain solves and sparse operator algebra.
- Use the Waveguide API for mode solving, port sources, and overlap windows.
- Use the fdmath API for the lower-level finite-difference operators and the shared discrete derivations underneath both solvers.
The docs build generates two HTML views from the same source:
- a normal multi-page site
- a print-oriented combined page under
site/print_page/If
"},{"location":"api/","title":"API Overview","text":"htmlarkis installed,./make_docs.shalso writes a fully inlinedsite/standalone.html.The package is documented directly from its docstrings. The most useful entry points are:
- meanas: top-level package overview
- eigensolvers: generic eigenvalue utilities used by the mode solvers
- fdfd: frequency-domain operators, sources, PML, solvers, and far-field transforms
- waveguides: straight, cylindrical, and 3D waveguide mode helpers
- fdtd: timestepping, CPML, energy/flux helpers, and phasor extraction
- fdmath: shared discrete operators, vectorization helpers, and derivation background
The waveguide and FDTD pages are the best places to start if you want the mathematical derivations rather than just the callable reference.
"},{"location":"api/eigensolvers/","title":"eigensolvers","text":""},{"location":"api/eigensolvers/#meanas.eigensolvers","title":"meanas.eigensolvers","text":"Solvers for eigenvalue / eigenvector problems
"},{"location":"api/eigensolvers/#meanas.eigensolvers.power_iteration","title":"power_iteration","text":"power_iteration(\n operator: spmatrix,\n guess_vector: NDArray[complex128] | None = None,\n iterations: int = 20,\n) -> tuple[complex, NDArray[numpy.complex128]]\nUse power iteration to estimate the dominant eigenvector of a matrix.
Parameters:
Name Type Description DefaultoperatorspmatrixMatrix to analyze.
requiredguess_vectorNDArray[complex128] | NoneStarting point for the eigenvector. Default is a randomly chosen vector.
NoneiterationsintNumber of iterations to perform. Default 20.
20Returns:
Type Descriptiontuple[complex, NDArray[complex128]](Largest-magnitude eigenvalue, Corresponding eigenvector estimate)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.rayleigh_quotient_iteration","title":"rayleigh_quotient_iteration","text":"rayleigh_quotient_iteration(\n operator: spmatrix | LinearOperator,\n guess_vector: NDArray[complex128],\n iterations: int = 40,\n tolerance: float = 1e-13,\n solver: Callable[..., NDArray[complex128]]\n | None = None,\n) -> tuple[complex, NDArray[numpy.complex128]]\nUse Rayleigh quotient iteration to refine an eigenvector guess.
Parameters:
Name Type Description Defaultoperatorspmatrix | LinearOperatorMatrix to analyze.
requiredguess_vectorNDArray[complex128]Eigenvector to refine.
requirediterationsintMaximum number of iterations to perform. Default 40.
40tolerancefloatStop iteration if
(A - I*eigenvalue) @ v < num_vectors * tolerance, Default 1e-13.1e-13solverCallable[..., NDArray[complex128]] | NoneSolver function of the form
x = solver(A, b). By default, use scipy.sparse.spsolve for sparse matrices and scipy.sparse.bicgstab for general LinearOperator instances.NoneReturns:
Type Descriptiontuple[complex, NDArray[complex128]](eigenvalues, eigenvectors)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.signed_eigensolve","title":"signed_eigensolve","text":"signed_eigensolve(\n operator: spmatrix | LinearOperator,\n how_many: int,\n negative: bool = False,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nFind the largest-magnitude positive-only (or negative-only) eigenvalues and eigenvectors of the provided matrix.
Parameters:
Name Type Description Defaultoperatorspmatrix | LinearOperatorMatrix to analyze.
requiredhow_manyintHow many eigenvalues to find.
requirednegativeboolWhether to find negative-only eigenvalues. Default False (positive only).
FalseReturns:
Type DescriptionNDArray[complex128](sorted list of eigenvalues, 2D ndarray of corresponding eigenvectors)
NDArray[complex128]"},{"location":"api/fdfd/","title":"fdfd","text":""},{"location":"api/fdfd/#meanas.fdfd","title":"meanas.fdfd","text":"
eigenvectors[:, k]corresponds to the k-th eigenvalueTools for finite difference frequency-domain (FDFD) simulations and calculations.
These mostly involve picking a single frequency, then setting up and solving a matrix equation (Ax=b) or eigenvalue problem.
Submodules:
operators,functional: General FDFD problem setup.solvers: Solver interface and reference implementation.scpml: Stretched-coordinate perfectly matched layer (SCPML) boundary conditions.waveguide_2d: Operators and mode-solver for waveguides with constant cross-section.waveguide_3d: Functions for transformingwaveguide_2dresults into 3D, including mode-source and overlap-window construction.farfield,bloch,eme: specialized helper modules for near/far transforms, Bloch-periodic problems, and eigenmode expansion.================================================================
From the \"Frequency domain\" section of
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &= \\tilde{E}_{\\vec{r}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\tilde{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &= \\tilde{H}_{\\vec{r} + \\frac{1}{2}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\\\ \\tilde{J}_{l, \\vec{r}} &= \\tilde{J}_{\\vec{r}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\\\ \\tilde{M}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &= \\tilde{M}_{\\vec{r} + \\frac{1}{2}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\Omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} &= -\\imath \\Omega \\tilde{J}_{\\vec{r}} e^{\\imath \\omega \\Delta_t / 2} \\\\ \\Omega &= 2 \\sin(\\omega \\Delta_t / 2) / \\Delta_t \\end{aligned} \\]meanas.fdmath, we haveresulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\end{aligned} \\]Maxwell's equations are then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2} \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2} \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\tilde{\\nabla} \\cdot \\hat{B}_{\\vec{r} + \\frac{1}{2}} &= 0 \\\\ \\hat{\\nabla} \\cdot \\tilde{D}_{\\vec{r}} &= \\rho_{\\vec{r}} \\end{aligned} \\]With \\(\\Delta_t \\to 0\\), this simplifies to
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &\\to \\tilde{E}_{\\vec{r}} \\\\ \\tilde{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{H}_{\\vec{r} + \\frac{1}{2}} \\\\ \\tilde{J}_{l, \\vec{r}} &\\to \\tilde{J}_{\\vec{r}} \\\\ \\tilde{M}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\Omega &\\to \\omega \\\\ \\tilde{\\partial}_t &\\to -\\imath \\omega \\\\ \\hat{\\partial}_t &\\to -\\imath \\omega \\\\ \\end{aligned} \\]and then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\omega \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\omega \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\end{aligned} \\] \\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\omega \\tilde{J}_{\\vec{r}} \\\\ \\]"},{"location":"api/fdfd/#core-operator-layers","title":"Core operator layers","text":""},{"location":"api/fdfd/#meanas.fdfd.functional","title":"meanas.fdfd.functional","text":"Functional versions of many FDFD operators. These can be useful for performing FDFD calculations without needing to construct large matrices in memory.
The functions generated here expect
"},{"location":"api/fdfd/#meanas.fdfd.functional.e_full","title":"e_full","text":"cfdfield_tinputs with shape (3, X, Y, Z), e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nWave operator for use with E-field. See
operators.e_fullfor details.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
fimplementing the wave operatorcfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.eh_full","title":"eh_full","text":"
f(E)->-i * omega * Jeh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> Callable[\n [cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]\n]\nWave operator for full (both E and H) field representation. See
operators.eh_full.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type DescriptionCallable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]Function
fimplementing the wave operatorCallable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]"},{"location":"api/fdfd/#meanas.fdfd.functional.e2h","title":"e2h","text":"
f(E, H)->(J, -M)e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nUtility operator for converting the
Efield into theHfield. For use withe_full-- assumes that there is no magnetic currentM.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
ffor convertingEtoH,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.m2j","title":"m2j","text":"
f(E)->Hm2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nUtility operator for converting magnetic current
Mdistribution into equivalent electric current distributionJ. For use with e.g.e_full.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
ffor convertingMtoJ,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.e_tfsf_source","title":"e_tfsf_source","text":"
f(M)->Je_tfsf_source(\n TF_region: fdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nOperator that turns an E-field distribution into a total-field/scattered-field (TFSF) source.
If
\\[ \\frac{A Q - Q A}{-i \\omega} E. \\]Ais the full wave operator frome_full(...)andQis the diagonal mask selecting the total-field region, then the TFSF source is the commutatorThis vanishes in the interior of the total-field and scattered-field regions and is supported only at their shared boundary, where the mask discontinuity makes
AandQfail to commute. The returned current is therefore the distributed source needed to inject the desired total field without also forcing the scattered-field region.Parameters:
Name Type Description DefaultTF_regionfdfieldmask which is set to 1 in the total-field region, and 0 elsewhere (i.e. in the scattered-field region). Should have the same shape as the simulation grid, e.g.
requiredepsilon[0].shape.omegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant distribution
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
fwhich takes an E field and returns a current distribution,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.poynting_e_cross_h","title":"poynting_e_cross_h","text":"
f(E)->Jpoynting_e_cross_h(\n dxes: dx_lists_t,\n) -> Callable[[cfdfield, cfdfield], cfdfield_t]\nGenerates a function that takes the single-frequency
EandHfields and calculates the cross productExH= \\(E \\times H\\) as required for the Poynting vector, \\(S = E \\times H\\).On the Yee grid, the electric and magnetic components are not stored at the same locations. This helper therefore applies the same one-cell electric-field shifts used by the sparse
Noteoperators.poynting_e_cross(...)construction so that the discrete cross product matches the face-centered energy flux used inmeanas.fdtd.energy.poynting(...).This function also shifts the input
NoteEfield by one cell as required for computing the Poynting cross product (seemeanas.fdfdmodule docs).If
EandHare peak amplitudes as assumed elsewhere in this code, the time-average of the poynting vector is<S> = Re(S)/2 = Re(E x H*) / 2. The factor of1/2can be omitted if root-mean-square quantities are used instead.Parameters:
Name Type Description Defaultdxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionCallable[[cfdfield, cfdfield], cfdfield_t]Function
fthat returns the staggered-grid cross productE \\times H.Callable[[cfdfield, cfdfield], cfdfield_t]For time-average power, call it as
"},{"location":"api/fdfd/#meanas.fdfd.operators","title":"meanas.fdfd.operators","text":"f(E, H.conj())and takeRe(...) / 2.Sparse matrix operators for use with electromagnetic wave equations.
These functions return sparse-matrix (
scipy.sparse.sparray) representations of a variety of operators, intended for use with E and H fields vectorized using themeanas.fdmath.vectorization.vec()andmeanas.fdmath.vectorization.unvec()functions.E- and H-field values are defined on a Yee cell;
epsilonvalues should be calculated for cells centered at each E component (muat each H component).Many of these functions require a
dxesparameter, of typedx_lists_t; see themeanas.fdmath.typessubmodule for details.The following operators are included:
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_full","title":"e_full","text":"
- E-only wave operator
- H-only wave operator
- EH wave operator
- Curl for use with E, H fields
- E to H conversion
- M to J conversion
- Poynting cross products
- Circular shifts
- Discrete derivatives
- Averaging operators
- Cross product matrices
e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield | vcfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator $$ \\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon $$
del x (1/mu * del x) - omega**2 * epsilon\nfor use with the E-field, with wave equation $$ (\\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon) E = -\\imath \\omega J $$
(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * J\nTo make this matrix symmetric, use the preconditioners from
e_full_preconditioners().Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfield | vcfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_full_preconditioners","title":"e_full_preconditioners","text":"e_full_preconditioners(\n dxes: dx_lists_t,\n) -> tuple[sparse.sparray, sparse.sparray]\nLeft and right preconditioners
(Pl, Pr)for symmetrizing thee_fullwave operator.The preconditioned matrix
A_symm = (Pl @ A @ Pr)is complex-symmetric (non-Hermitian unless there is no loss or PMLs).The preconditioner matrices are diagonal and complex, with
Pr = 1 / PlParameters:
Name Type Description Defaultdxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type Descriptiontuple[sparray, sparray]Preconditioner matrices
"},{"location":"api/fdfd/#meanas.fdfd.operators.h_full","title":"h_full","text":"(Pl, Pr).h_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator $$ \\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu $$
del x (1/epsilon * del x) - omega**2 * mu\nfor use with the H-field, with wave equation $$ (\\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu) E = \\imath \\omega M $$
(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * M\nParameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.eh_full","title":"eh_full","text":"eh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator for
[E, H]field representation. This operator implements Maxwell's equations without cancelling out either E or H. The operator is $$ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} $$[[-i * omega * epsilon, del x ],\n [del x, i * omega * mu]]\nfor use with a field vector of the form
cat(vec(E), vec(H)): $$ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} \\begin{bmatrix} E \\ H \\end{bmatrix} = \\begin{bmatrix} J \\ -M \\end{bmatrix} $$Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e2h","title":"e2h","text":"e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nUtility operator for converting the E field into the H field. For use with
e_full()-- assumes that there is no magnetic current M.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix for converting E to H.
"},{"location":"api/fdfd/#meanas.fdfd.operators.m2j","title":"m2j","text":"m2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n) -> sparse.sparray\nOperator for converting a magnetic current M into an electric current J. For use with eg.
e_full().Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix for converting M to J.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_e_cross","title":"poynting_e_cross","text":"poynting_e_cross(\n e: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\nOperator for computing the staggered-grid
(E \\times)part of the Poynting vector.On the Yee grid the E and H components live on different edges, so the electric field must be shifted by one cell in the appropriate direction before forming the discrete cross product. This sparse operator encodes that shifted cross product directly and is the matrix equivalent of
functional.poynting_e_cross_h(...).Parameters:
Name Type Description DefaultevcfdfieldVectorized E-field for the ExH cross product
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionsparraySparse matrix containing the
(E \\times)part of the staggered Poyntingsparraycross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_h_cross","title":"poynting_h_cross","text":"poynting_h_cross(\n h: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\nOperator for computing the staggered-grid
(H \\times)part of the Poynting vector.Together with
\\[ H \\times E = -(E \\times H), \\]poynting_e_cross(...), this provides the matrix form of the Yee-grid cross product used in the flux helpers. The two are related by the usual antisymmetry of the cross product,once the same staggered field placement is used on both sides.
Parameters:
Name Type Description DefaulthvcfdfieldVectorized H-field for the HxE cross product
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionsparraySparse matrix containing the
(H \\times)part of the staggered Poyntingsparraycross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_tfsf_source","title":"e_tfsf_source","text":"e_tfsf_source(\n TF_region: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n) -> sparse.sparray\nOperator that turns a desired E-field distribution into a total-field/scattered-field (TFSF) source.
Let
\\[ \\frac{A Q - Q A}{-i \\omega}. \\]Abe the full wave operator frome_full(...), and letQ = \\mathrm{diag}(TF_region)be the projector onto the total-field region. Then the TFSF current operator is the commutatorInside regions where
Qis locally constant,AandQcommute and the source vanishes. Only cells at the TF/SF boundary contribute nonzero current, which is exactly the desired distributed source for injecting the chosen field into the total-field region without directly forcing the scattered-field region.Parameters:
Name Type Description DefaultTF_regionvfdfieldMask, which is set to 1 inside the total-field region and 0 in the scattered-field region
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
NoneReturns:
Type DescriptionsparraySparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_boundary_source","title":"e_boundary_source","text":"e_boundary_source(\n mask: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n periodic_mask_edges: bool = False,\n) -> sparse.sparray\nOperator that turns an E-field distrubtion into a current (J) distribution along the edges (external and internal) of the provided mask. This is just an
e_tfsf_source()with an additional masking step.Equivalently, this helper first constructs the TFSF commutator source for the full mask and then zeroes out all cells except the mask boundary. The boundary is defined as the set of cells whose mask value changes under a one-cell shift in any Cartesian direction. With
periodic_mask_edges=Falsethe shifts mirror at the domain boundary; withTruethey wrap periodically.Parameters:
Name Type Description DefaultmaskvfdfieldThe current distribution is generated at the edges of the mask, i.e. any points where shifting the mask by one cell in any direction would change its value.
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
NoneReturns:
Type DescriptionsparraySparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.solvers","title":"meanas.fdfd.solvers","text":"Solvers and solver interface for FDFD problems.
"},{"location":"api/fdfd/#meanas.fdfd.solvers.generic","title":"generic","text":"generic(\n omega: complex,\n dxes: dx_lists_t,\n J: vcfdfield,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n *,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n adjoint: bool = False,\n matrix_solver: Callable[..., ArrayLike] = _scipy_qmr,\n matrix_solver_opts: dict[str, Any] | None = None,\n E_guess: vcfdfield | None = None,\n) -> vcfdfield_t\nConjugate gradient FDFD solver using CSR sparse matrices.
All ndarray arguments should be 1D arrays, as returned by
meanas.fdmath.vectorization.vec().Parameters:
Name Type Description DefaultomegacomplexComplex frequency to solve at.
requireddxesdx_lists_trequired
[[dx_e, dy_e, dz_e], [dx_h, dy_h, dz_h]](complex cell sizes) as discussed inmeanas.fdmath.typesJvcfdfieldElectric current distribution (at E-field locations)
requiredepsilonvfdfieldDielectric constant distribution (at E-field locations)
requiredmuvfdfield | NoneMagnetic permeability distribution (at H-field locations)
Nonepecvfdfield | NonePerfect electric conductor distribution (at E-field locations; non-zero value indicates PEC is present)
Nonepmcvfdfield | NonePerfect magnetic conductor distribution (at H-field locations; non-zero value indicates PMC is present)
NoneadjointboolIf true, solves the adjoint problem.
Falsematrix_solverCallable[..., ArrayLike]Called as
matrix_solver(A, b, **matrix_solver_opts) -> x, whereA:scipy.sparse.csr_array;b:ArrayLike;x:ArrayLike; Default is a wrapped version ofscipy.sparse.linalg.qmr()which doesn't return convergence info and logs the residual every 100 iterations._scipy_qmrmatrix_solver_optsdict[str, Any] | NonePassed as kwargs to
matrix_solver(...)NoneE_guessvcfdfield | NoneGuess at the solution E-field.
matrix_solvermust accept anx0argument with the same purpose.NoneReturns:
Type Descriptionvcfdfield_tE-field which solves the system.
"},{"location":"api/fdfd/#meanas.fdfd.scpml","title":"meanas.fdfd.scpml","text":"Functions for creating stretched coordinate perfectly matched layer (PML) absorbers.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.s_function_t","title":"s_function_tmodule-attribute","text":"s_function_t = Callable[\n [NDArray[float64]], NDArray[float64]\n]\nTypedef for s-functions, see
"},{"location":"api/fdfd/#meanas.fdfd.scpml.prepare_s_function","title":"prepare_s_function","text":"prepare_s_function()prepare_s_function(\n ln_R: float = -16, m: float = 4\n) -> s_function_t\nCreate an s_function to pass to the SCPML functions. This is used when you would like to customize the PML parameters.
Parameters:
Name Type Description Defaultln_RfloatNatural logarithm of the desired reflectance
-16mfloatPolynomial order for the PML (imaginary part increases as distance ** m)
4Returns:
Type Descriptions_function_tAn s_function, which takes an ndarray (distances) and returns an ndarray (complex part
s_function_tof the cell width; needs to be divided by
sqrt(epilon_effective) * real(omega))s_function_tbefore use.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.uniform_grid_scpml","title":"uniform_grid_scpml","text":"uniform_grid_scpml(\n shape: Sequence[int],\n thicknesses: Sequence[int],\n omega: float,\n epsilon_effective: float = 1.0,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\nCreate dx arrays for a uniform grid with a cell width of 1 and a pml.
If you want something more fine-grained, check out
stretch_with_scpml(...).Parameters:
Name Type Description DefaultshapeSequence[int]Shape of the grid, including the PMLs (which are 2*thicknesses thick)
requiredthicknessesSequence[int]required
[th_x, th_y, th_z]Thickness of the PML in each direction. Both polarities are added. Each th_ of pml is applied twice, once on each edge of the grid along the given axis.th_*may be zero, in which case no pml is added.omegafloatAngular frequency for the simulation
requiredepsilon_effectivefloatEffective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0s_functions_function_t | Nonecreated by
prepare_s_function(...), allowing customization of pml parameters. Default usesprepare_s_function()with no parameters.NoneReturns:
Type Descriptionlist[list[NDArray[float64]]]Complex cell widths (dx_lists_mut) as discussed in
"},{"location":"api/fdfd/#meanas.fdfd.scpml.stretch_with_scpml","title":"stretch_with_scpml","text":"meanas.fdmath.types.stretch_with_scpml(\n dxes: list[list[NDArray[float64]]],\n axis: int,\n polarity: int,\n omega: float,\n epsilon_effective: float = 1.0,\n thickness: int = 10,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\nStretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis.
Parameters:
Name Type Description Defaultdxeslist[list[NDArray[float64]]]Grid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintaxis to stretch (0=x, 1=y, 2=z)
requiredpolarityintdirection to stretch (-1 for -ve, +1 for +ve)
requiredomegafloatAngular frequency for the simulation
requiredepsilon_effectivefloatEffective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0thicknessintnumber of cells to use for pml (default 10)
10s_functions_function_t | NoneCreated by
prepare_s_function(...), allowing customization of pml parameters. Default usesprepare_s_function()with no parameters.NoneReturns:
Type Descriptionlist[list[NDArray[float64]]]Complex cell widths (dx_lists_mut) as discussed in
meanas.fdmath.types.list[list[NDArray[float64]]]Multiple calls to this function may be necessary if multiple absorpbing boundaries are needed.
"},{"location":"api/fdfd/#meanas.fdfd.farfield","title":"meanas.fdfd.farfield","text":"Functions for performing near-to-farfield transformation (and the reverse).
"},{"location":"api/fdfd/#meanas.fdfd.farfield.near_to_farfield","title":"near_to_farfield","text":"near_to_farfield(\n E_near: cfdfield_t,\n H_near: cfdfield_t,\n dx: float,\n dy: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\nCompute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_nearcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse E fields (e.g. [Ex, Ey] for calculating the farfield toward the z-direction).
requiredH_nearcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse H fields (e.g. [Hx, hy] for calculating the farfield towrad the z-direction).
requireddxfloatCell size along x-dimension, in units of wavelength.
requireddyfloatCell size along y-dimension, in units of wavelength.
requiredpadded_sizelist[int] | int | NoneShape of the output. A single integer
nwill be expanded to(n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.NoneReturns:
Type Descriptiondict[str, Any]Dict with keys
dict[str, Any]
E_far: Normalized E-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any]
H_far: Normalized H-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any]
kx,ky: Wavevector values corresponding to the x- and y- axes in E_far and H_far, normalized to wavelength (dimensionless).dict[str, Any]
dkx,dky: step size for kx and ky, normalized to wavelength.dict[str, Any]
theta: arctan2(ky, kx) corresponding to each (kx, ky). This is the angle in the x-y plane, counterclockwise from above, starting from +x.dict[str, Any]"},{"location":"api/fdfd/#meanas.fdfd.farfield.far_to_nearfield","title":"far_to_nearfield","text":"
phi: arccos(kz / k) corresponding to each (kx, ky). This is the angle away from +z.far_to_nearfield(\n E_far: cfdfield_t,\n H_far: cfdfield_t,\n dkx: float,\n dky: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\nCompute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_farcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse E fields (e.g. [Ex, Ey] for calculating the nearfield toward the z-direction). Fields should be normalized so that E_far = E_far_actual / (i k exp(-i k r) / (4 pi r))
requiredH_farcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse H fields (e.g. [Hx, hy] for calculating the nearfield toward the z-direction). Fields should be normalized so that H_far = H_far_actual / (i k exp(-i k r) / (4 pi r))
requireddkxfloatkx discretization, in units of wavelength.
requireddkyfloatky discretization, in units of wavelength.
requiredpadded_sizelist[int] | int | NoneShape of the output. A single integer
nwill be expanded to(n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.NoneReturns:
Type Descriptiondict[str, Any]Dict with keys
dict[str, Any]
E: E-field nearfielddict[str, Any]
H: H-field nearfielddict[str, Any]"},{"location":"api/fdmath/","title":"fdmath","text":""},{"location":"api/fdmath/#meanas.fdmath","title":"meanas.fdmath","text":"
dx,dy: spatial discretization, normalized to wavelength (dimensionless)Basic discrete calculus for finite difference (fd) simulations.
"},{"location":"api/fdmath/#meanas.fdmath--fields-functions-and-operators","title":"Fields, Functions, and Operators","text":"Discrete fields are stored in one of two forms:
Operators which act on fields also come in two forms
- The
fdfield_tform is a multidimensionalnumpy.NDArray
- For a scalar field, this is just
U[m, n, p], wherem,n, andpare discrete indices referring to positions on the x, y, and z axes respectively.- For a vector field, the first index specifies which vector component is accessed:
E[:, m, n, p] = [Ex[m, n, p], Ey[m, n, p], Ez[m, n, p]].- The
vfdfield_tform is simply a vectorzied (i.e. 1D) version of thefdfield_t, as obtained bymeanas.fdmath.vectorization.vec(effectively justnumpy.ravel)
- Python functions, created by the functions in
meanas.fdmath.functional. The generated functions act on fields in thefdfield_tform.- Linear operators, usually 2D sparse matrices using
scipy.sparse, created bymeanas.fdmath.operators. These operators act on vectorized fields in thevfdfield_tform.The operations performed should be equivalent:
functional.op(*args)(E)should be equivalent tounvec(operators.op(*args) @ vec(E), E.shape[1:]).Generally speaking the
"},{"location":"api/fdmath/#meanas.fdmath--discrete-calculus","title":"Discrete calculus","text":"field_tform is easier to work with, but can be harder or less efficient to compose (e.g. it is easy to generate a single matrix by multiplying a series of other matrices).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.
"},{"location":"api/fdmath/#meanas.fdmath--scalar-derivatives-and-cell-shifts","title":"Scalar derivatives and cell shifts","text":"Define the discrete forward derivative as $$ [\\tilde{\\partial}x f] - f_m) $$ where }{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m + 1\\(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
fas a 1D array of values, with thei-th valuef[i]taking up a lengthdx[i]along the x-axis, the forward derivative isderiv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]\nLikewise, discrete reverse derivative is $$ [\\hat{\\partial}x f ]) $$ or}{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m} - f_{m - 1
deriv_back(f)[i] = (f[i] - f[i - 1]) / dx[i]\nThe 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]\n dx0 dx1 dx2 dx3 cell sizes for function\n ----- ----- ----------- -----\n ______________________________\n | | | |\n f0 | f1 | f2 | f3 | function\n _____|_____|___________|_____|\n | | | |\n | Df0 | Df1 | Df2 | Df3 forward derivative (periodic boundary)\n __|_____|________|________|___\n\n dx'3] dx'0 dx'1 dx'2 [dx'3 cell sizes for forward derivative\n -- ----- -------- -------- ---\n dx'0] dx'1 dx'2 dx'3 [dx'0 cell sizes for reverse derivative\n ______________________________\n | | | |\n | df1 | df2 | df3 | df0 reverse derivative (periodic boundary)\n __|_____|________|________|___\n\nPeriodic boundaries are used here and elsewhere unless otherwise noted.\nIn 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. If the positions labeled with \\(m\\) are considered the \"base\" or \"original\" grid, the positions labeled with \\(m + \\frac{1}{2}\\) are said to lie on a \"dual\" or \"derived\" grid.
For the remainder of the
"},{"location":"api/fdmath/#meanas.fdmath--gradients-and-fore-vectors","title":"Gradients and fore-vectors","text":"Discrete calculussection, all figures will show constant-length cells in order to focus on the vector derivatives themselves. See theGrid descriptionsection below for additional information on this topic and generalization to three dimensions.Expanding to three dimensions, we can define two gradients $$ [\\tilde{\\nabla} f]{m,n,p} = \\vec{x} [\\tilde{\\partial}_x f] + \\vec{y} [\\tilde{\\partial}}{2},n,py f] + \\vec{z} [\\tilde{\\partial}}{2},pz f] $$ $$ [\\hat{\\nabla} f]}{2}{m,n,p} = \\vec{x} [\\hat{\\partial}_x f] + \\vec{y} [\\hat{\\partial}}{2},n,py f] + \\vec{z} [\\hat{\\partial}}{2},pz f] $$}{2}
or
[code: gradients]\ngrad_forward(f)[i,j,k] = [Dx_forward(f)[i, j, k],\n Dy_forward(f)[i, j, k],\n Dz_forward(f)[i, j, k]]\n = [(f[i + 1, j, k] - f[i, j, k]) / dx[i],\n (f[i, j + 1, k] - f[i, j, k]) / dy[i],\n (f[i, j, k + 1] - f[i, j, k]) / dz[i]]\n\ngrad_back(f)[i,j,k] = [Dx_back(f)[i, j, k],\n Dy_back(f)[i, j, k],\n Dz_back(f)[i, j, k]]\n = [(f[i, j, k] - f[i - 1, j, k]) / dx[i],\n (f[i, j, k] - f[i, j - 1, k]) / dy[i],\n (f[i, j, k] - f[i, j, k - 1]) / dz[i]]\nThe 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 + \\vec{y} g^y_{m,n + \\frac{1}{2},p} + \\vec{z} g^z_{m,n,p + \\frac{1}{2}} $$ $$ \\hat{g}}{2},n,p{m,n,p} = \\vec{x} g^x + \\vec{y} g^y_{m,n - \\frac{1}{2},p} + \\vec{z} g^z_{m,n,p - \\frac{1}{2}} $$}{2},n,p
"},{"location":"api/fdmath/#meanas.fdmath--divergences","title":"Divergences","text":"[figure: gradient / fore-vector]\n (m, n+1, p+1) ______________ (m+1, n+1, p+1)\n /: /|\n / : / |\n / : / |\n (m, n, p+1)/_____________/ | The forward derivatives are defined\n | : | | at the Dx, Dy, Dz points,\n | :.........|...| but the forward-gradient fore-vector\n z y Dz / | / is the set of all three\n |/_x | Dy | / and is said to be \"located\" at (m,n,p)\n |/ |/\n (m, n, p)|_____Dx______| (m+1, n, p)\nThere are also two divergences,
$$ d_{n,m,p} = [\\tilde{\\nabla} \\cdot \\hat{g}]{n,m,p} = [\\tilde{\\partial}_x g^x] + [\\tilde{\\partial}y g^y] + [\\tilde{\\partial}z g^z] $$
$$ d_{n,m,p} = [\\hat{\\nabla} \\cdot \\tilde{g}]{n,m,p} = [\\hat{\\partial}_x g^x] + [\\hat{\\partial}y g^y] + [\\hat{\\partial}z g^z] $$
or
[code: divergences]\ndiv_forward(g)[i,j,k] = Dx_forward(gx)[i, j, k] +\n Dy_forward(gy)[i, j, k] +\n Dz_forward(gz)[i, j, k]\n = (gx[i + 1, j, k] - gx[i, j, k]) / dx[i] +\n (gy[i, j + 1, k] - gy[i, j, k]) / dy[i] +\n (gz[i, j, k + 1] - gz[i, j, k]) / dz[i]\n\ndiv_back(g)[i,j,k] = Dx_back(gx)[i, j, k] +\n Dy_back(gy)[i, j, k] +\n Dz_back(gz)[i, j, k]\n = (gx[i, j, k] - gx[i - 1, j, k]) / dx[i] +\n (gy[i, j, k] - gy[i, j - 1, k]) / dy[i] +\n (gz[i, j, k] - gz[i, j, k - 1]) / dz[i]\nwhere
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-vector's) location \\((m,n,p)\\) and not at the locations of its components \\((m \\pm \\frac{1}{2},n,p)\\) etc.
"},{"location":"api/fdmath/#meanas.fdmath--curls","title":"Curls","text":"[figure: divergence]\n ^^\n (m-1/2, n+1/2, p+1/2) _____||_______ (m+1/2, n+1/2, p+1/2)\n /: || ,, /|\n / : || // / | The divergence at (m, n, p) (the center\n / : // / | of this cube) of a fore-vector field\n (m-1/2, n-1/2, p+1/2)/_____________/ | is the sum of the outward-pointing\n | : | | fore-vector components, which are\n z y <==|== :.........|.====> located at the face centers.\n |/_x | / | /\n | / // | / Note that in a nonuniform grid, each\n |/ // || |/ dimension is normalized by the cell width.\n (m-1/2, n-1/2, p-1/2)|____//_______| (m+1/2, n-1/2, p-1/2)\n '' ||\n VV\nThe two curls are then
$$ \\begin{aligned} \\hat{h}{m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}} &= \\ [\\tilde{\\nabla} \\times \\tilde{g}] &= \\vec{x} (\\tilde{\\partial}}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}y g^z}{2}} - \\tilde{\\partialz g^y) \\ &+ \\vec{y} (\\tilde{\\partial}}{2},pz g^x}{2},n,p} - \\tilde{\\partialx g^z) \\ &+ \\vec{z} (\\tilde{\\partial}}{2}x g^y}{2},p} - \\tilde{\\partialy g^z) \\end{aligned} $$}{2},n,p
and
$$ \\tilde{h}{m - \\frac{1}{2}, n - \\frac{1}{2}, p - \\frac{1}{2}} = [\\hat{\\nabla} \\times \\hat{g}] $$}{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]\ncurl_forward(g)[i,j,k] = [Dy_forward(gz)[i, j, k] - Dz_forward(gy)[i, j, k],\n Dz_forward(gx)[i, j, k] - Dx_forward(gz)[i, j, k],\n Dx_forward(gy)[i, j, k] - Dy_forward(gx)[i, j, k]]\n\ncurl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],\n Dz_back(gx)[i, j, k] - Dx_back(gz)[i, j, k],\n Dx_back(gy)[i, j, k] - Dy_back(gx)[i, j, k]]\nFor 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 ofgin the xy plane where the curl's z-component is located; these are[curl components]\n(m, n + 1/2, p + 1/2) : x-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1, n + 1/2, p + 1/2) : x-component of back-vector at (m + 3/2, n + 1/2, p + 1/2)\n(m + 1/2, n , p + 1/2) : y-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1/2, n + 1 , p + 1/2) : y-component of back-vector at (m + 1/2, n + 3/2, p + 1/2)\nThese 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).
"},{"location":"api/fdmath/#meanas.fdmath--maxwells-equations","title":"Maxwell's Equations","text":"[figure: z-component of curl]\n : |\n z y : ^^ |\n |/_x :....||.<.....| (m+1, n+1, p+1/2)\n / || /\n | v || | ^\n |/ |/\n (m, n, p+1/2) |_____>______| (m+1, n, p+1/2)\nIf we discretize both space (m,n,p) and time (l), Maxwell's equations become
$$ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}{l,\\vec{r}} &= -\\tilde{\\partial}_t \\hat{B} - \\hat{M}}{2}, \\vec{r} + \\frac{1}{2}{l, \\vec{r} + \\frac{1}{2}} \\ \\hat{\\nabla} \\times \\hat{H}}{2},\\vec{r} + \\frac{1}{2}} &= \\hat{\\partialt \\tilde{D} + \\tilde{J}}{l-\\frac{1}{2},\\vec{r}} \\ \\tilde{\\nabla} \\cdot \\hat{B} &= 0 \\ \\hat{\\nabla} \\cdot \\tilde{D}}{2}, \\vec{r} + \\frac{1}{2}{l,\\vec{r}} &= \\rho \\end{aligned} $$}
with
$$ \\begin{aligned} \\hat{B}{\\vec{r}} &= \\mu} + \\frac{1}{2}} \\cdot \\hat{H{\\vec{r} + \\frac{1}{2}} \\ \\tilde{D} \\end{aligned} $$}} &= \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}
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 updates]\nH[i, j, k] -= dt * (curl_forward(E)[i, j, k] + M[t, i, j, k]) / mu[i, j, k]\nE[i, j, k] += dt * (curl_back( H)[i, j, k] + J[t, i, j, k]) / epsilon[i, j, k]\nNote 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: Field components]\n\n (m - 1/2,=> ____________Hx__________[H] <= r + 1/2 = (m + 1/2,\n n + 1/2, /: /: /| n + 1/2,\n z y p + 1/2) / : / : / | p + 1/2)\n |/_x / : / : / |\n / : Ez__________Hy | Locations of the E- and\n / : : : /| | H-field components for the\n (m - 1/2, / : : Ey...../.|..Hz [E] fore-vector at r = (m,n,p)\n n - 1/2, =>/________________________/ | /| (the large cube's center)\n p + 1/2) | : : / | | / | and [H] back-vector at r + 1/2\n | : :/ | |/ | (the top right corner)\n | : [E].......|.Ex |\n | :.................|......| <= (m + 1/2, n + 1/2, p + 1/2)\n | / | /\n | / | /\n | / | / This is the Yee discretization\n | / | / scheme (\"Yee cell\").\nr - 1/2 = | / | /\n (m - 1/2, |/ |/\n n - 1/2,=> |________________________| <= (m + 1/2, n - 1/2, p - 1/2)\n p - 1/2)\nEach component forms its own grid, offset from the others:
[figure: E-fields for adjacent cells]\n\n H1__________Hx0_________H0\n z y /: /|\n |/_x / : / | This figure shows H back-vector locations\n / : / | H0, H1, etc. and their associated components\n Hy1 : Hy0 | H0 = (Hx0, Hy0, Hz0) etc.\n / : / |\n / Hz1 / Hz0\n H2___________Hx3_________H3 | The equivalent drawing for E would have\n | : | | fore-vectors located at the cube's\n | : | | center (and the centers of adjacent cubes),\n | : | | with components on the cube's faces.\n | H5..........Hx4...|......H4\n | / | /\n Hz2 / Hz2 /\n | / | /\n | Hy6 | Hy4\n | / | /\n |/ |/\n H6__________Hx7__________H7\nThe 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.
"},{"location":"api/fdmath/#meanas.fdmath--wave-equation","title":"Wave equation","text":"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{aligned} \\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{aligned} \\]"},{"location":"api/fdmath/#meanas.fdmath--frequency-domain","title":"Frequency domain","text":"We can substitute in a time-harmonic fields
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &= \\tilde{E}_{\\vec{r}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\tilde{J}_{l, \\vec{r}} &= \\tilde{J}_{\\vec{r}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\end{aligned} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow (e^{ \\imath \\omega \\Delta_t} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{-\\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow (1 - e^{-\\imath \\omega \\Delta_t}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{ \\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\Omega &= 2 \\sin(\\omega \\Delta_t / 2) / \\Delta_t \\end{aligned} \\]This gives the frequency-domain wave equation,
\\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\Omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\Omega \\tilde{J}_{\\vec{r}} e^{\\imath \\omega \\Delta_t / 2} \\\\ \\]"},{"location":"api/fdmath/#meanas.fdmath--plane-waves-and-dispersion-relation","title":"Plane waves and Dispersion relation","text":"With uniform material distribution and no sources
\\[ \\begin{aligned} \\mu_{\\vec{r} + \\frac{1}{2}} &= \\mu \\\\ \\epsilon_{\\vec{r}} &= \\epsilon \\\\ \\tilde{J}_{\\vec{r}} &= 0 \\\\ \\end{aligned} \\]the frequency domain wave equation simplifies to
\\[ \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} - \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]Since \\(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}} = 0\\), we can simplify
\\[ \\begin{aligned} \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\tilde{\\nabla}(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}}) - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} \\end{aligned} \\]and we get
\\[ \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} + \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]We can convert this to three scalar-wave equations of the form
\\[ (\\tilde{\\nabla}^2 + K^2) \\phi_{\\vec{r}} = 0 \\]with \\(K^2 = \\Omega^2 \\mu \\epsilon\\). Now we let
\\[ \\phi_{\\vec{r}} = A e^{\\imath (k_x m \\Delta_x + k_y n \\Delta_y + k_z p \\Delta_z)} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_x &\\Rightarrow (e^{ \\imath k_x \\Delta_x} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{ \\imath k_x \\Delta_x / 2} = \\imath K_x e^{ \\imath k_x \\Delta_x / 2}\\\\ \\hat{\\partial}_x &\\Rightarrow (1 - e^{-\\imath k_x \\Delta_x}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{-\\imath k_x \\Delta_x / 2} = \\imath K_x e^{-\\imath k_x \\Delta_x / 2}\\\\ K_x &= 2 \\sin(k_x \\Delta_x / 2) / \\Delta_x \\\\ \\end{aligned} \\]with similar expressions for the y and z dimnsions (and \\(K_y, K_z\\)).
This implies
\\[ \\tilde{\\nabla}^2 = -(K_x^2 + K_y^2 + K_z^2) \\phi_{\\vec{r}} \\\\ K_x^2 + K_y^2 + K_z^2 = \\Omega^2 \\mu \\epsilon = \\Omega^2 / c^2 \\]where \\(c = \\sqrt{\\mu \\epsilon}\\).
Assuming real \\((k_x, k_y, k_z), \\omega\\) will be real only if
\\[ c^2 \\Delta_t^2 = \\frac{\\Delta_t^2}{\\mu \\epsilon} < 1/(\\frac{1}{\\Delta_x^2} + \\frac{1}{\\Delta_y^2} + \\frac{1}{\\Delta_z^2}) \\]If \\(\\Delta_x = \\Delta_y = \\Delta_z\\), this simplifies to \\(c \\Delta_t < \\Delta_x / \\sqrt{3}\\). This last form can be interpreted as enforcing causality; the distance that light travels in one timestep (i.e., \\(c \\Delta_t\\)) must be less than the diagonal of the smallest cell ( \\(\\Delta_x / \\sqrt{3}\\) when on a uniform cubic grid).
"},{"location":"api/fdmath/#meanas.fdmath--grid-description","title":"Grid description","text":"As described in the section on scalar discrete derivatives above, cell widths (
dx[i],dy[j],dz[k]) along each axis can be arbitrary and independently defined. Moreover, all field components are actually defined at \"derived\" or \"dual\" positions, in-between the \"base\" grid points on one or more axes.To get a better sense of how this works, let's start by drawing a grid with uniform
dyanddzand nonuniformdx. We will only draw one cell in the y and z dimensions to make the illustration simpler; we need at least two cells in the x dimension to demonstrate how nonuniformdxaffects the various components.Place the E fore-vectors at integer indices \\(r = (m, n, p)\\) and the H back-vectors at fractional indices \\(r + \\frac{1}{2} = (m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2})\\). Remember that these are indices and not coordinates; they can correspond to arbitrary (monotonically increasing) coordinates depending on the cell widths.
Draw lines to denote the planes on which the H components and back-vectors are defined. For simplicity, don't draw the equivalent planes for the E components and fore-vectors, except as necessary to show their locations -- it's easiest to just connect them to their associated H-equivalents.
The result looks something like this:
[figure: Component centers]\n p=\n [H]__________Hx___________[H]_____Hx______[H] __ +1/2\n z y /: /: /: /: /| | |\n |/_x / : / : / : / : / | | |\n / : / : / : / : / | | |\n Hy : Ez...........Hy : Ez......Hy | | |\n /: : : : /: : : : /| | | |\n / : Hz : Ey....../.:..Hz : Ey./.|..Hz __ 0 | dz[0]\n / : /: : / / : /: : / / | /| | |\n /_________________________/_______________/ | / | | |\n | :/ : :/ | :/ : :/ | |/ | | |\n | Ex : [E].......|..Ex : [E]..|..Ex | | |\n | : | : | | | |\n | [H]..........Hx....|......[H].....H|x.....[H] __ --------- (n=+1/2, p=-1/2)\n | / | / | / / /\n Hz / Hz / Hz / / /\n | / | / | / / /\n | Hy | Hy | Hy __ 0 / dy[0]\n | / | / | / / /\n | / | / | / / /\n |/ |/ |/ / /\n [H]__________Hx___________[H]_____Hx______[H] __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ------------------------- ----------------\n dx[0] dx[1]\n\n Part of a nonuniform \"base grid\", with labels specifying\n positions of the various field components. [E] fore-vectors\n are at the cell centers, and [H] back-vectors are at the\n vertices. H components along the near (-y) top (+z) edge\n have been omitted to make the insides of the cubes easier\n to visualize.\nThe above figure shows where all the components are located; however, it is also useful to show what volumes those components correspond to. Consider the Ex component at
m = +1/2: it is shifted in the x-direction by a half-cell from the E fore-vector atm = 0(labeled[E]in the figure). It corresponds to a volume betweenm = 0andm = +1(the other dimensions are not shifted, i.e. they are still bounded byn, p = +-1/2). (See figure below). Sincemis an index and not an x-coordinate, the Ex component is not necessarily at the center of the volume it represents, and the x-length of its volume is the derived quantitydx'[0] = (dx[0] + dx[1]) / 2rather than the basedx. (See alsoScalar derivatives and cell shifts).[figure: Ex volumes]\n p=\n <_________________________________________> __ +1/2\n z y << /: / /: >> | |\n |/_x < < / : / / : > > | |\n < < / : / / : > > | |\n < < / : / / : > > | |\n <: < / : : / : >: > | |\n < : < / : : / : > : > __ 0 | dz[0]\n < : < / : : / :> : > | |\n <____________/____________________/_______> : > | |\n < : < | : : | > : > | |\n < Ex < | : Ex | > Ex > | |\n < : < | : : | > : > | |\n < : <....|.......:........:...|.......>...:...> __ --------- (n=+1/2, p=-1/2)\n < : < | / : /| /> : > / /\n < : < | / : / | / > : > / /\n < :< | / :/ | / > :> / /\n < < | / : | / > > _ 0 / dy[0]\n < < | / | / > > / /\n < < | / | / > > / /\n << |/ |/ >> / /\n <____________|____________________|_______> __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ -------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Ex values are positioned on the x-faces of the base\n grid. They represent the Ex field in volumes shifted by\n a half-cell in the x-dimension, as shown here. Only the\n center cell (with width dx'[0]) is fully shown; the\n other two are truncated (shown using >< markers).\n\n Note that the Ex positions are the in the same positions\n as the previous figure; only the cell boundaries have moved.\n Also note that the points at which Ex is defined are not\n necessarily centered in the volumes they represent; non-\n uniform cell sizes result in off-center volumes like the\n center cell here.\nThe next figure shows the volumes corresponding to the Hy components, which are shifted in two dimensions (x and z) compared to the base grid.
"},{"location":"api/fdmath/#meanas.fdmath--datastructure-dx_lists_t","title":"Datastructure: dx_lists_t","text":"[figure: Hy volumes]\n p=\n z y mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm __ +1/2 s\n |/_x << m: m: >> | |\n < < m : m : > > | | dz'[1]\n < < m : m : > > | |\n Hy........... m........Hy...........m......Hy > | |\n < < m : m : > > | |\n < ______ m_____:_______________m_____:_>______ __ 0\n < < m /: m / > > | |\n mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm > | |\n < < | / : | / > > | | dz'[0]\n < < | / : | / > > | |\n < < | / : | / > > | |\n < wwwww|w/wwwwwwwwwwwwwwwwwww|w/wwwww>wwwwwwww __ s\n < < |/ w |/ w> > / /\n _____________|_____________________|________ > / /\n < < | w | w > > / /\n < Hy........|...w........Hy.......|...w...>..Hy _ 0 / dy[0]\n < < | w | w > > / /\n << | w | w > > / /\n < |w |w >> / /\n wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww __ -1/2 /\n\n |------------|------------|--------|-------|\n-1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ --------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Hy values are positioned on the y-edges of the base\n grid. Again here, the 'Hy' labels represent the same points\n as in the basic grid figure above; the edges have shifted\n by a half-cell along the x- and z-axes.\n\n The grid lines _|:/ are edges of the area represented by\n each Hy value, and the lines drawn using <m>.w represent\n edges where a cell's faces extend beyond the drawn area\n (i.e. where the drawing is truncated in the x- or z-\n directions).\nIn this documentation, the E fore-vectors are placed on the base grid. An equivalent formulation could place the H back-vectors on the base grid instead. However, in the case of a non-uniform grid, the operation to get from the \"base\" cell widths to \"derived\" ones is not its own inverse.
The base grid's cell sizes could be fully described by a list of three 1D arrays, specifying the cell widths along all three axes:
[dx, dy, dz] = [[dx[0], dx[1], ...], [dy[0], ...], [dz[0], ...]]\nNote that this is a list-of-arrays rather than a 2D array, as the simulation domain may have a different number of cells along each axis.
Knowing the base grid's cell widths and the boundary conditions (periodic unless otherwise noted) is enough information to calculate the cell widths
dx',dy', anddz'for the derived grids.However, since most operations are trivially generalized to allow either E or H to be defined on the base grid, they are written to take the a full set of base and derived cell widths, distinguished by which field they apply to rather than their \"base\" or \"derived\" status. This removes the need for each function to generate the derived widths, and makes the \"base\" vs \"derived\" distinction unnecessary in the code.
The resulting data structure containing all the cell widths takes the form of a list-of-lists-of-arrays. The first list-of-arrays provides the cell widths for the E-field fore-vectors, while the second list-of-arrays does the same for the H-field back-vectors:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath--permittivity-and-permeability","title":"Permittivity and Permeability","text":"dx_e[0]is the x-width of them=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of then=0cells, as used when calculating dH/dy, etc.Since each vector component of E and H is defined in a different location and represents a different volume, the value of the spatially-discrete
epsilonandmucan also be different for all three field components, even when representing a simple planar interface between two isotropic materials.As a result,
epsilonandmuare taken to have the same dimensions as the field, and composed of the three diagonal tensor components:[equations: epsilon_and_mu]\nepsilon = [epsilon_xx, epsilon_yy, epsilon_zz]\nmu = [mu_xx, mu_yy, mu_zz]\nor
\\[ \\epsilon = \\begin{bmatrix} \\epsilon_{xx} & 0 & 0 \\\\ 0 & \\epsilon_{yy} & 0 \\\\ 0 & 0 & \\epsilon_{zz} \\end{bmatrix} $$ $$ \\mu = \\begin{bmatrix} \\mu_{xx} & 0 & 0 \\\\ 0 & \\mu_{yy} & 0 \\\\ 0 & 0 & \\mu_{zz} \\end{bmatrix} \\]where the off-diagonal terms (e.g.
epsilon_xy) are assumed to be zero.High-accuracy volumetric integration of shapes on multiple grids can be performed by the gridlock module.
The values of the vacuum permittivity and permability effectively become scaling factors that appear in several locations (e.g. between the E and H fields). In order to limit floating-point inaccuracy and simplify calculations, they are often set to 1 and relative permittivities and permeabilities are used in their places; the true values can be multiplied back in after the simulation is complete if non- normalized results are needed.
"},{"location":"api/fdmath/#functional-and-sparse-operators","title":"Functional and sparse operators","text":""},{"location":"api/fdmath/#meanas.fdmath.functional","title":"meanas.fdmath.functional","text":"Math functions for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\nUtility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\nUtility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\nCurl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type DescriptionCallable[[TT], TT]Function
ffor taking the discrete forward curl of a field,Callable[[TT], TT]"},{"location":"api/fdmath/#meanas.fdmath.functional.curl_back","title":"curl_back","text":"
f(E)-> curlE \\(= \\nabla_f \\times E\\)curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\nCreate a function which takes the backward curl of a field.
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type DescriptionCallable[[TT], TT]Function
ffor taking the discrete backward curl of a field,Callable[[TT], TT]"},{"location":"api/fdmath/#meanas.fdmath.operators","title":"meanas.fdmath.operators","text":"
f(H)-> curlH \\(= \\nabla_b \\times H\\)Matrix operators for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_circ","title":"shift_circ","text":"shift_circ(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\nUtility operator for performing a circular shift along a specified axis by a specified number of elements.
Parameters:
Name Type Description DefaultaxisintAxis to shift along. x=0, y=1, z=2
requiredshapeSequence[int]Shape of the grid being shifted
requiredshift_distanceintNumber of cells to shift by. May be negative. Default 1.
1Returns:
Type DescriptionsparraySparse matrix for performing the circular shift.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_with_mirror","title":"shift_with_mirror","text":"shift_with_mirror(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\nUtility operator for performing an n-element shift along a specified axis, with mirror boundary conditions applied to the cells beyond the receding edge.
Parameters:
Name Type Description DefaultaxisintAxis to shift along. x=0, y=1, z=2
requiredshapeSequence[int]Shape of the grid being shifted
requiredshift_distanceintNumber of cells to shift by. May be negative. Default 1.
1Returns:
Type DescriptionsparraySparse matrix for performing the shift-with-mirror.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\nUtility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type Descriptionlist[sparray]List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\nUtility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type Descriptionlist[sparray]List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.cross","title":"cross","text":"cross(B: Sequence[sparray]) -> sparse.sparray\nCross product operator
Parameters:
Name Type Description DefaultBSequence[sparray]List
required[Bx, By, Bz]of sparse matrices corresponding to the x, y, z portions of the operator on the left side of the cross product.Returns:
Type DescriptionsparraySparse matrix corresponding to (B x), where x is the cross product.
"},{"location":"api/fdmath/#meanas.fdmath.operators.vec_cross","title":"vec_cross","text":"vec_cross(b: vfdfield_t) -> sparse.sparray\nVector cross product operator
Parameters:
Name Type Description Defaultbvfdfield_tVector on the left side of the cross product.
requiredReturns:
"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_forward","title":"avg_forward","text":"Sparse matrix corresponding to (b x), where x is the cross product.\navg_forward(\n axis: int, shape: Sequence[int]\n) -> sparse.sparray\nForward average operator
(x4 = (x4 + x5) / 2)Parameters:
Name Type Description DefaultaxisintAxis to average along (x=0, y=1, z=2)
requiredshapeSequence[int]Shape of the grid to average
requiredReturns:
Type DescriptionsparraySparse matrix for forward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_back","title":"avg_back","text":"avg_back(axis: int, shape: Sequence[int]) -> sparse.sparray\nBackward average operator
(x4 = (x4 + x3) / 2)Parameters:
Name Type Description DefaultaxisintAxis to average along (x=0, y=1, z=2)
requiredshapeSequence[int]Shape of the grid to average
requiredReturns:
Type DescriptionsparraySparse matrix for backward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\nCurl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type DescriptionsparraySparse matrix for taking the discretized curl of the E-field
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_back","title":"curl_back","text":"curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\nCurl operator for use with the H field.
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type DescriptionsparraySparse matrix for taking the discretized curl of the H-field
"},{"location":"api/fdmath/#meanas.fdmath.vectorization","title":"meanas.fdmath.vectorization","text":"Functions for moving between a vector field (list of 3 ndarrays,
"},{"location":"api/fdmath/#meanas.fdmath.vectorization.vec","title":"vec","text":"[f_x, f_y, f_z]) and a 1D array representation of that field[f_x0, f_x1, f_x2,... f_y0,... f_z0,...]. Vectorized versions of the field use row-major (ie., C-style) ordering.vec(f: None) -> None\nvec(f: fdfield_t) -> vfdfield_t\nvec(f: cfdfield_t) -> vcfdfield_t\nvec(f: fdfield2_t) -> vfdfield2_t\nvec(f: cfdfield2_t) -> vcfdfield2_t\nvec(f: fdslice_t) -> vfdslice_t\nvec(f: cfdslice_t) -> vcfdslice_t\nvec(f: ArrayLike) -> NDArray\nvec(\n f: fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | ArrayLike\n | None,\n) -> (\n vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | NDArray\n | None\n)\nCreate a 1D ndarray from a vector field which spans a 1-3D region.
Returns
Noneif called withf=None.Parameters:
Name Type Description Defaultffdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | NoneA vector field, e.g.
required[f_x, f_y, f_z]where eachf_component is a 1- to 3-D ndarray (f_*should all be the same size). Doesn't fail withf=None.Returns:
Type Descriptionvfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | NDArray | None1D ndarray containing the linearized field (or
"},{"location":"api/fdmath/#meanas.fdmath.vectorization.unvec","title":"unvec","text":"None)unvec(\n v: None, shape: Sequence[int], nvdim: int = 3\n) -> None\nunvec(\n v: vfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield_t\nunvec(\n v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield_t\nunvec(\n v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield2_t\nunvec(\n v: vcfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield2_t\nunvec(\n v: vfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> fdslice_t\nunvec(\n v: vcfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdslice_t\nunvec(\n v: ArrayLike, shape: Sequence[int], nvdim: int = 3\n) -> NDArray\nunvec(\n v: vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | ArrayLike\n | None,\n shape: Sequence[int],\n nvdim: int = 3,\n) -> (\n fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | NDArray\n | None\n)\nPerform the inverse of vec(): take a 1D ndarray and output an
nvdim-component field of form e.g.[f_x, f_y, f_z](nvdim=3) where each off_*is a len(shape)-dimensional ndarray.Returns
Noneif called withv=None.Parameters:
Name Type Description Defaultvvfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | ArrayLike | None1D ndarray representing a vector field of shape shape (or None)
requiredshapeSequence[int]shape of the vector field
requirednvdimintNumber of components in each vector
3Returns:
Type Descriptionfdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | NDArray | None"},{"location":"api/fdmath/#meanas.fdmath.types","title":"meanas.fdmath.types","text":"
[f_x, f_y, f_z]where eachf_is alen(shape)dimensional ndarray (orNone)Types shared across multiple submodules
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists_t","title":"dx_lists_tmodule-attribute","text":"dx_lists_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists2_t","title":"dx_lists2_tdx_e[0]is the x-width of thex=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of they=0cells, as used when calculating dH/dy, etc.module-attribute","text":"dx_lists2_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n2D 'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists_mut","title":"dx_lists_mutdx_e[0]is the x-width of thex=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of they=0cells, as used when calculating dH/dy, etc.module-attribute","text":"dx_lists_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\nMutable version of
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists2_mut","title":"dx_lists2_mutdx_lists_tmodule-attribute","text":"dx_lists2_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\nMutable version of
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield_updater_t","title":"fdfield_updater_tdx_lists2_tmodule-attribute","text":"fdfield_updater_t = Callable[..., fdfield_t]\nConvenience type for functions which take and return an fdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield_updater_t","title":"cfdfield_updater_tmodule-attribute","text":"cfdfield_updater_t = Callable[..., cfdfield_t]\nConvenience type for functions which take and return an cfdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield","title":"fdfield","text":"fdfield = fdfield_t | NDArray[floating]\nVector field with shape (3, X, Y, Z) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdfield","title":"vfdfield","text":"[E_x, E_y, E_z])vfdfield = vfdfield_t | NDArray[floating]\nLinearized vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield","title":"cfdfield","text":"cfdfield = cfdfield_t | NDArray[complexfloating]\nComplex vector field with shape (3, X, Y, Z) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdfield","title":"vcfdfield","text":"[E_x, E_y, E_z])vcfdfield = vcfdfield_t | NDArray[complexfloating]\nLinearized complex vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdslice","title":"fdslice","text":"fdslice = fdslice_t | NDArray[floating]\nVector field slice with shape (3, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdslice","title":"vfdslice","text":"[E_x, E_y, E_z]at a single Z position)vfdslice = vfdslice_t | NDArray[floating]\nLinearized vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdslice","title":"cfdslice","text":"cfdslice = cfdslice_t | NDArray[complexfloating]\nComplex vector field slice with shape (3, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdslice","title":"vcfdslice","text":"[E_x, E_y, E_z]at a single Z position)vcfdslice = vcfdslice_t | NDArray[complexfloating]\nLinearized complex vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield2","title":"fdfield2","text":"fdfield2 = fdfield2_t | NDArray[floating]\n2D Vector field with shape (2, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdfield2","title":"vfdfield2","text":"[E_x, E_y])vfdfield2 = vfdfield2_t | NDArray[floating]\n2D Linearized vector field (single vector of length 2XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield2","title":"cfdfield2","text":"cfdfield2 = cfdfield2_t | NDArray[complexfloating]\n2D Complex vector field with shape (2, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdfield2","title":"vcfdfield2","text":"[E_x, E_y])vcfdfield2 = vcfdfield2_t | NDArray[complexfloating]\n2D Linearized complex vector field (single vector of length 2XY)
"},{"location":"api/fdtd/","title":"fdtd","text":""},{"location":"api/fdtd/#meanas.fdtd","title":"meanas.fdtd","text":"Utilities for running finite-difference time-domain (FDTD) simulations
See the discussion of
"},{"location":"api/fdtd/#meanas.fdtd--timestep","title":"Timestep","text":"Maxwell's Equationsinmeanas.fdmathfor basic mathematical background.From the discussion of \"Plane waves and the Dispersion relation\" in
\\[ c^2 \\Delta_t^2 = \\frac{\\Delta_t^2}{\\mu \\epsilon} < 1/(\\frac{1}{\\Delta_x^2} + \\frac{1}{\\Delta_y^2} + \\frac{1}{\\Delta_z^2}) \\]meanas.fdmath, we haveor, if \\(\\Delta_x = \\Delta_y = \\Delta_z\\), then \\(c \\Delta_t < \\frac{\\Delta_x}{\\sqrt{3}}\\).
Based on this, we can set
dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2)\nThe
"},{"location":"api/fdtd/#meanas.fdtd--poynting-vector-and-energy-conservation","title":"Poynting Vector and Energy Conservation","text":"dx_min,dy_min,dz_minshould be the minimum value across both the base and derived grids.Let
\\[ \\begin{aligned} \\tilde{S}_{l, l', \\vec{r}} &=& &\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &=& &\\vec{x} (\\tilde{E}^y_{l,m+1,n,p} \\hat{H}^z_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^z_{l,m+1,n,p} \\hat{H}^y_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{y} (\\tilde{E}^z_{l,m,n+1,p} \\hat{H}^x_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^x_{l,m,n+1,p} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{z} (\\tilde{E}^x_{l,m,n,p+1} \\hat{H}^y_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^y_{l,m,n,p+1} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\end{aligned} \\]where \\(\\vec{r} = (m, n, p)\\) and \\(\\otimes\\) is a modified cross product in which the \\(\\tilde{E}\\) terms are shifted as indicated.
By taking the divergence and rearranging terms, we can show that
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l', \\vec{r}} &= \\hat{\\nabla} \\cdot (\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}}) \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l, \\vec{r}} - \\tilde{E}_{l, \\vec{r}} \\cdot \\hat{\\nabla} \\times \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot (-\\tilde{\\partial}_t \\mu_{\\vec{r} + \\frac{1}{2}} \\hat{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} - \\hat{M}_{l, \\vec{r} + \\frac{1}{2}}) - \\tilde{E}_{l, \\vec{r}} \\cdot (\\hat{\\partial}_t \\tilde{\\epsilon}_{\\vec{r}} \\tilde{E}_{l'+\\frac{1}{2}, \\vec{r}} + \\tilde{J}_{l', \\vec{r}}) \\\\ &= \\hat{H}_{l'} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t )(\\tilde{E}_{l'+\\frac{1}{2}} - \\tilde{E}_{l'-\\frac{1}{2}}) - \\hat{H}_{l'} \\cdot \\hat{M}_{l} - \\tilde{E}_l \\cdot \\tilde{J}_{l'} \\\\ \\end{aligned} \\]where in the last line the spatial subscripts have been dropped to emphasize the time subscripts \\(l, l'\\), i.e.
\\[ \\begin{aligned} \\tilde{E}_l &= \\tilde{E}_{l, \\vec{r}} \\\\ \\hat{H}_l &= \\tilde{H}_{l, \\vec{r} + \\frac{1}{2}} \\\\ \\tilde{\\epsilon} &= \\tilde{\\epsilon}_{\\vec{r}} \\\\ \\end{aligned} \\]etc. For \\(l' = l + \\frac{1}{2}\\) we get
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} &= \\hat{H}_{l + \\frac{1}{2}} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} - \\tilde{E}_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= (-\\mu / \\Delta_t)(\\hat{H}^2_{l + \\frac{1}{2}} - \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}}) - (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} \\cdot \\tilde{E}_l - \\tilde{E}^2_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= -(\\mu \\hat{H}^2_{l + \\frac{1}{2}} +\\epsilon \\tilde{E}_{l+1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ +(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ \\end{aligned} \\]and for \\(l' = l - \\frac{1}{2}\\),
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} &= (\\mu \\hat{H}^2_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}_{l-1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ -(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]These two results form the discrete time-domain analogue to Poynting's theorem. They hint at the expressions for the energy, which can be calculated at the same time-index as either the E or H field:
\\[ \\begin{aligned} U_l &= \\epsilon \\tilde{E}^2_l + \\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} \\\\ U_{l + \\frac{1}{2}} &= \\epsilon \\tilde{E}_l \\cdot \\tilde{E}_{l + 1} + \\mu \\hat{H}^2_{l + \\frac{1}{2}} \\\\ \\end{aligned} \\]Rewriting the Poynting theorem in terms of the energy expressions,
\\[ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]This result is exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary.
Note that each value of \\(J\\) contributes to the energy twice (i.e. once per field update) despite only causing the value of \\(E\\) to change once (same for \\(M\\) and \\(H\\)).
"},{"location":"api/fdtd/#meanas.fdtd--sources","title":"Sources","text":"It is often useful to excite the simulation with an arbitrary broadband pulse and then extract the frequency-domain response by performing an on-the-fly Fourier transform of the time-domain fields.
\\[ \\Delta_t \\sum_l w_l e^{-i \\omega t_l} f_l \\]
accumulate_phasorinmeanas.fdtd.phasorperforms the phase accumulation for one or more target frequencies, while leaving source normalization and simulation-loop policy to the caller. Convenience wrappersaccumulate_phasor_e,accumulate_phasor_h, andaccumulate_phasor_japply the usual Yee time offsets. The helpers accumulatewith caller-provided sample times and weights. In this codebase the matching electric-current convention is typically
E -= dt * J / epsilonin FDTD and-i \\omega Jon the right-hand side of the FDFD wave equation.The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written
\\[ f_r(t) = (1 - \\frac{1}{2} (\\omega (t - \\tau))^2) e^{-(\\frac{\\omega (t - \\tau)}{2})^2} \\]with \\(\\tau > \\frac{2 * \\pi}{\\omega}\\) as a minimum delay to avoid a discontinuity at t=0 (assuming the source is off for t<0 this gives \\(\\sim 10^{-3}\\) error at t=0).
"},{"location":"api/fdtd/#meanas.fdtd--boundary-conditions","title":"Boundary conditions","text":"
meanas.fdtdexposes two boundary-related building blocks:
conducting_boundary(...)for simple perfect-electric-conductor style field clamping at one face of the domain.cpml_params(...)andupdates_with_cpml(...)for convolutional perfectly matched layers (CPMLs) attached to one or more faces of the Yee grid.
updates_with_cpml(...)accepts a three-by-two table of CPML parameter blocks:cpml_params[axis][polarity_index]\nwhere
axisis0,1, or2andpolarity_indexcorresponds to(-1, +1). PassingNonefor one entry disables CPML on that face while leaving the other directions unchanged. This is how mixed boundary setups such as \"absorbing in x, periodic in y/z\" are expressed.When comparing an FDTD run against an FDFD solve, use the same stretched coordinate system in both places:
- Build the FDTD update with the desired CPML parameters.
- Stretch the FDFD
dxeswith the matching SCPML transform.- Compare the extracted phasor against the FDFD field or residual on those stretched
dxes.The electric-current sign convention used throughout the examples and tests is
\\[ E \\leftarrow E - \\Delta_t J / \\epsilon \\]which matches the FDFD right-hand side
\\[ -i \\omega J. \\]"},{"location":"api/fdtd/#core-update-and-analysis-helpers","title":"Core update and analysis helpers","text":""},{"location":"api/fdtd/#meanas.fdtd.base","title":"meanas.fdtd.base","text":"Basic FDTD field updates
"},{"location":"api/fdtd/#meanas.fdtd.base.maxwell_e","title":"maxwell_e","text":"maxwell_e(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\nBuild a function which performs a portion the time-domain E-field update,
E += curl_back(H[t]) / epsilon\nThe full update should be
E += (curl_back(H[t]) + J) / epsilon\nwhich requires an additional step of
E += J / epsilonwhich is not performed by the generated function.See
meanas.fdmathfor descriptions of
- This update step: \"Maxwell's equations\" section
dxes: \"Datastructure: dx_lists_t\" sectionepsilon: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of
meanas.fdtdfor a discussion of thedtparameter.Parameters:
Name Type Description DefaultdtfloatTimestep. See
requiredmeanas.fdtdfor details.dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_updater_tFunction
"},{"location":"api/fdtd/#meanas.fdtd.base.maxwell_h","title":"maxwell_h","text":"f(E_old, H_old, epsilon) -> E_new.maxwell_h(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\nBuild a function which performs part of the time-domain H-field update,
H -= curl_forward(E[t]) / mu\nThe full update should be
H -= (curl_forward(E[t]) + M) / mu\nwhich requires an additional step of
H -= M / muwhich is not performed by the generated function; this step can be omitted if there is no magnetic currentM.See
meanas.fdmathfor descriptions of
- This update step: \"Maxwell's equations\" section
dxes: \"Datastructure: dx_lists_t\" sectionmu: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of
meanas.fdtdfor a discussion of thedtparameter.Parameters:
Name Type Description DefaultdtfloatTimestep. See
requiredmeanas.fdtdfor details.dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_updater_tFunction
"},{"location":"api/fdtd/#meanas.fdtd.pml","title":"meanas.fdtd.pml","text":"f(E_old, H_old, epsilon) -> E_new.Convolutional perfectly matched layer (CPML) support for FDTD updates.
The helpers in this module construct per-face CPML parameters and then wrap the standard Yee updates with the additional auxiliary
psifields needed by the CPML recurrence.The intended call pattern is:
- Build a
cpml_params[axis][polarity_index]table withcpml_params(...).- Pass that table into
updates_with_cpml(...)together withdt,dxes, andepsilon.- Advance the returned
update_E/update_Hclosures in the simulation loop.Each face can be enabled or disabled independently by replacing one table entry with
"},{"location":"api/fdtd/#meanas.fdtd.pml.cpml_params","title":"cpml_params","text":"None.cpml_params(\n axis: int,\n polarity: int,\n dt: float,\n thickness: int = 8,\n ln_R_per_layer: float = -1.6,\n epsilon_eff: float = 1,\n mu_eff: float = 1,\n m: float = 3.5,\n ma: float = 1,\n cfs_alpha: float = 0,\n) -> dict[str, Any]\nConstruct the parameter block for one CPML face.
Parameters:
Name Type Description DefaultaxisintWhich Cartesian axis the CPML is normal to (
required0,1, or2).polarityintWhich face along that axis (
required-1for the low-index face,+1for the high-index face).dtfloatTimestep used by the Yee update.
requiredthicknessintNumber of Yee cells occupied by the CPML region.
8ln_R_per_layerfloatLogarithmic attenuation target per layer.
-1.6epsilon_efffloatEffective permittivity used when choosing the CPML scaling.
1mu_efffloatEffective permeability used when choosing the CPML scaling.
1mfloatPolynomial grading exponent for
sigmaandkappa.3.5mafloatPolynomial grading exponent for the complex-frequency shift
alpha.1cfs_alphafloatMaximum complex-frequency shift parameter.
0Returns:
Type Descriptiondict[str, Any]Dictionary with:
dict[str, Any]
param_e:(p0, p1, p2)arrays for the E updatedict[str, Any]
param_h:(p0, p1, p2)arrays for the H updatedict[str, Any]"},{"location":"api/fdtd/#meanas.fdtd.pml.updates_with_cpml","title":"updates_with_cpml","text":"
region: slice tuple selecting the CPML cells on that faceupdates_with_cpml(\n cpml_params: Sequence[Sequence[dict[str, Any] | None]],\n dt: float,\n dxes: dx_lists_t,\n epsilon: fdfield,\n *,\n dtype: DTypeLike = numpy.float32,\n) -> tuple[\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n]\nBuild Yee-step update closures augmented with CPML terms.
Parameters:
Name Type Description Defaultcpml_paramsSequence[Sequence[dict[str, Any] | None]]Three-by-two sequence indexed as
required[axis][polarity_index]. Entries are the dictionaries returned bycpml_params(...); useNoneto disable CPML on one face.dtfloatTimestep.
requireddxesdx_lists_tYee-grid spacing lists
required[dx_e, dx_h].epsilonfdfieldElectric material distribution used by the E update.
requireddtypeDTypeLikeStorage dtype for the auxiliary CPML state arrays.
float32Returns:
Type DescriptionCallable[[fdfield_t, fdfield_t, fdfield_t], None]
(update_E, update_H)closures with the same call shape as the basicCallable[[fdfield_t, fdfield_t, fdfield_t], None]Yee updates:
tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]
update_E(e, h, epsilon)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]
update_H(e, h, mu)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]The closures retain the CPML auxiliary state internally.
"},{"location":"api/fdtd/#meanas.fdtd.boundaries","title":"meanas.fdtd.boundaries","text":"Boundary conditions
"},{"location":"api/fdtd/#meanas.fdtd.boundaries--todo-conducting-boundary-documentation","title":"TODO conducting boundary documentation","text":""},{"location":"api/fdtd/#meanas.fdtd.energy","title":"meanas.fdtd.energy","text":""},{"location":"api/fdtd/#meanas.fdtd.energy.poynting","title":"poynting","text":"poynting(\n e: fdfield, h: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\nCalculate the poynting vector
S(\\(S\\)).This is the energy transfer rate (amount of energy
Uperdttransferred between adjacent cells) in each direction that happens during the half-step bounded by the two provided fields.The returned vector field
Sis the energy flow across +x, +y, and +z boundaries of the correspondingUcell. For example,mx = numpy.roll(mask, -1, axis=0)\n my = numpy.roll(mask, -1, axis=1)\n mz = numpy.roll(mask, -1, axis=2)\n\n u_hstep = fdtd.energy_hstep(e0=es[ii - 1], h1=hs[ii], e2=es[ii], **args)\n u_estep = fdtd.energy_estep(h0=hs[ii], e1=es[ii], h2=hs[ii + 1], **args)\n delta_j_B = fdtd.delta_energy_j(j0=js[ii], e1=es[ii], dxes=dxes)\n du_half_h2e = u_estep - u_hstep - delta_j_B\n\n s_h2e = -fdtd.poynting(e=es[ii], h=hs[ii], dxes=dxes) * dt\n planes = [s_h2e[0, mask].sum(), -s_h2e[0, mx].sum(),\n s_h2e[1, mask].sum(), -s_h2e[1, my].sum(),\n s_h2e[2, mask].sum(), -s_h2e[2, mz].sum()]\n\n assert_close(sum(planes), du_half_h2e[mask])\n(see
meanas.tests.test_fdtd.test_poynting_planes)The full relationship is $$ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}{l, l + \\frac{1}{2}} \\ - \\hat{H}}{2}} \\cdot \\hat{Ml \\ - \\tilde{E}_l \\cdot \\tilde{J} \\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}}{2}{l, l - \\frac{1}{2}} \\ - \\hat{H}}{2}} \\cdot \\hat{Ml \\ - \\tilde{E}_l \\cdot \\tilde{J} \\ \\end{aligned} $$}{2}
These equalities are exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary. (See
meanas.fdtddocs for derivation.)Parameters:
Name Type Description DefaultefdfieldE-field
requiredhfdfieldH-field (one half-timestep before or after
requirede)dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Name Type Descriptionsfdfield_tVector field. Components indicate the energy transfer rate from the corresponding energy cell into its +x, +y, and +z neighbors during the half-step from the time of the earlier input field until the time of later input field.
"},{"location":"api/fdtd/#meanas.fdtd.energy.poynting_divergence","title":"poynting_divergence","text":"poynting_divergence(\n s: fdfield | None = None,\n *,\n e: fdfield | None = None,\n h: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate the divergence of the poynting vector.
This is the net energy flow for each cell, i.e. the change in energy
Uperdtcaused by transfer of energy to nearby cells (rather than absorption/emission by currentsJorM).See
poyntingandmeanas.fdtdfor more details. Args: s: Poynting vector, as calculated withpoynting. Optional; caller can provideeandhinstead. e: E-field (optional; need eithersor botheandh) h: H-field (optional; need eithersor botheandh) dxes: Grid description; seemeanas.fdmath.Returns:
Name Type Descriptiondsfdfield_tDivergence of the poynting vector. Entries indicate the net energy flow out of the corresponding energy cell.
"},{"location":"api/fdtd/#meanas.fdtd.energy.energy_hstep","title":"energy_hstep","text":"energy_hstep(\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate energy
Uat the time of the provided H-fieldh1.TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulte0fdfieldE-field one half-timestep before the energy.
requiredh1fdfieldH-field (at the same timestep as the energy).
requirede2fdfieldE-field one half-timestep after the energy.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tEnergy, at the time of the H-field
"},{"location":"api/fdtd/#meanas.fdtd.energy.energy_estep","title":"energy_estep","text":"h1.energy_estep(\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate energy
Uat the time of the provided E-fielde1.TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulth0fdfieldH-field one half-timestep before the energy.
requirede1fdfieldE-field (at the same timestep as the energy).
requiredh2fdfieldH-field one half-timestep after the energy.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tEnergy, at the time of the E-field
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_h2e","title":"delta_energy_h2e","text":"e1.delta_energy_h2e(\n dt: float,\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n h3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nChange in energy during the half-step from
h1toe2.This is just from (e2 * e2 + h3 * h1) - (h1 * h1 + e0 * e2)
Parameters:
Name Type Description Defaulte0fdfieldE-field one half-timestep before the start of the energy delta.
requiredh1fdfieldH-field at the start of the energy delta.
requirede2fdfieldE-field at the end of the energy delta (one half-timestep after
requiredh1).h3fdfieldH-field one half-timestep after the end of the energy delta.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tChange in energy from the time of
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_e2h","title":"delta_energy_e2h","text":"h1to the time ofe2.delta_energy_e2h(\n dt: float,\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n e3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nChange in energy during the half-step from
e1toh2.This is just from (h2 * h2 + e3 * e1) - (e1 * e1 + h0 * h2)
Parameters:
Name Type Description Defaulth0fdfieldE-field one half-timestep before the start of the energy delta.
requirede1fdfieldH-field at the start of the energy delta.
requiredh2fdfieldE-field at the end of the energy delta (one half-timestep after
requirede1).e3fdfieldH-field one half-timestep after the end of the energy delta.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tChange in energy from the time of
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_j","title":"delta_energy_j","text":"e1to the time ofh2.delta_energy_j(\n j0: fdfield, e1: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\nCalculate the electric-current work term \\(J \\cdot E\\) on the Yee grid.
This is the source contribution that appears beside the flux divergence in the discrete Poynting identities documented in
meanas.fdtd.Note that each value of
Jcontributes twice in a full Yee cycle (once per half-step energy balance) even though it directly changesEonly once.Parameters:
Name Type Description Defaultj0fdfieldElectric-current density sampled at the same half-step as the current work term.
requirede1fdfieldElectric field sampled at the matching integer timestep.
requireddxesdx_lists_t | NoneGrid description; defaults to unit spacing.
NoneReturns:
Type Descriptionfdfield_tPer-cell source-work contribution with the scalar field shape.
"},{"location":"api/fdtd/#meanas.fdtd.energy.dxmul","title":"dxmul","text":"dxmul(\n ee: fdfield,\n hh: fdfield,\n epsilon: fdfield | float | None = None,\n mu: fdfield | float | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nMultiply E- and H-like field products by material weights and cell volumes.
Parameters:
Name Type Description DefaulteefdfieldThree-component electric-field product, such as
requirede0 * e2.hhfdfieldThree-component magnetic-field product, such as
requiredh1 * h1.epsilonfdfield | float | NoneElectric material weight; defaults to
1.Nonemufdfield | float | NoneMagnetic material weight; defaults to
1.Nonedxesdx_lists_t | NoneGrid description; defaults to unit spacing.
NoneReturns:
Type Descriptionfdfield_tScalar field containing the weighted electric plus magnetic contribution
fdfield_tfor each Yee cell.
"},{"location":"api/fdtd/#meanas.fdtd.phasor","title":"meanas.fdtd.phasor","text":"Helpers for extracting single- or multi-frequency phasors from FDTD samples.
These helpers are intentionally low-level: callers own the accumulator arrays and the sampling policy. The accumulated quantity is
dt * sum(weight * exp(-1j * omega * t_step) * sample_step)\nwhere
t_step = (step + offset_steps) * dt.The usual Yee offsets are:
accumulate_phasor_e(..., step=l)forE_laccumulate_phasor_h(..., step=l)forH_{l + 1/2}accumulate_phasor_j(..., step=l)forJ_{l + 1/2}These helpers do not choose warmup/accumulation windows or pulse-envelope normalization. They also do not impose a current sign convention. In this codebase, electric-current injection normally appears as
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor","title":"accumulate_phasor","text":"E -= dt * J / epsilon, which matches the FDFD right-hand side-1j * omega * J.accumulate_phasor(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n offset_steps: float = 0.0,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAdd one time-domain sample into a phasor accumulator.
The added quantity is
dt * weight * exp(-1j * omega * t_step) * sample\nwhere
Notet_step = (step + offset_steps) * dt.This helper already multiplies by
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_e","title":"accumulate_phasor_e","text":"dt. If the caller's normalization factor was derived from a discrete sum that already includesdt, passweight / dthere.accumulate_phasor_e(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate an E-field sample taken at integer timestep
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_h","title":"accumulate_phasor_h","text":"step.accumulate_phasor_h(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate an H-field sample corresponding to
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_j","title":"accumulate_phasor_j","text":"H_{step + 1/2}.accumulate_phasor_j(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate a current sample corresponding to
"},{"location":"api/meanas/","title":"meanas","text":""},{"location":"api/meanas/#meanas","title":"meanas","text":"J_{step + 1/2}.Electromagnetic simulation tools
See the tracked examples for end-to-end workflows, and
"},{"location":"api/waveguides/","title":"waveguides","text":""},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d","title":"meanas.fdfd.waveguide_2d","text":"help(meanas)for the toolbox overview and API derivations.Operators and helper functions for waveguides with unchanging cross-section.
The propagation direction is chosen to be along the z axis, and all fields are given an implicit z-dependence of the form
exp(-1 * wavenumber * z).As the z-dependence is known, all the functions in this file assume a 2D grid (i.e.
dxes = [[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]], [[dx_h[0], ...], [dy_h[0], ...]]]).===============
Consider Maxwell's equations in continuous space, in the frequency domain. Assuming a structure with some (x, y) cross-section extending uniformly into the z dimension, with a diagonal \\(\\epsilon\\) tensor, we have
\\[ \\begin{aligned} \\nabla \\times \\vec{E}(x, y, z) &= -\\imath \\omega \\mu \\vec{H} \\\\ \\nabla \\times \\vec{H}(x, y, z) &= \\imath \\omega \\epsilon \\vec{E} \\\\ \\vec{E}(x,y,z) &= (\\vec{E}_t(x, y) + E_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\vec{H}(x,y,z) &= (\\vec{H}_t(x, y) + H_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\end{aligned} \\]Expanding the first two equations into vector components, we get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\partial_y E_z - \\partial_z E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= \\partial_z E_x - \\partial_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\partial_x E_y - \\partial_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\partial_y H_z - \\partial_z H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= \\partial_z H_x - \\partial_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\partial_x H_y - \\partial_y H_x \\\\ \\end{aligned} \\]Substituting in our expressions for \\(\\vec{E}\\), \\(\\vec{H}\\) and discretizing:
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\tilde{\\partial}_y E_z + \\imath \\beta E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta E_x - \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\hat{\\partial}_y H_z + \\imath \\beta H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= -\\imath \\beta H_x - \\hat{\\partial}_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Rewrite the last three equations as
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\imath \\omega E_z &= \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Now apply \\(\\imath \\beta \\tilde{\\partial}_x\\) to the last equation, then substitute in for \\(\\imath \\beta H_x\\) and \\(\\imath \\beta H_y\\):
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_x \\imath \\omega E_z &= \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y) \\\\ \\imath \\beta \\tilde{\\partial}_x E_z &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]With a similar approach (but using \\(\\imath \\beta \\tilde{\\partial}_y\\) instead), we can get
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_y E_z &= \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]We can combine this equation for \\(\\imath \\beta \\tilde{\\partial}_y E_z\\) with the unused \\(\\imath \\omega \\mu_{xx} H_x\\) and \\(\\imath \\omega \\mu_{yy} H_y\\) equations to get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\imath \\beta \\tilde{\\partial}_y E_z \\\\ -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]and
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\imath \\beta \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]However, based on our rewritten equation for \\(\\imath \\beta H_x\\) and the so-far unused equation for \\(\\imath \\omega \\mu_{zz} H_z\\) we can also write
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} (\\imath \\beta H_x) &= -\\imath \\omega \\mu_{xx} (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y + \\imath \\omega \\mu_{xx} \\hat{\\partial}_x ( \\frac{1}{-\\imath \\omega \\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x)) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]and, similarly,
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} (\\imath \\beta H_y) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]By combining both pairs of expressions, we get
\\[ \\begin{aligned} \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]Using these, we can construct the eigenvalue problem
\\[ \\beta^2 \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} = (\\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} \\]In the literature, \\(\\beta\\) is usually used to denote the lossless/real part of the propagation constant, but in
meanasit is allowed to be complex.An equivalent eigenvalue problem can be formed using the \\(H_x\\) and \\(H_y\\) fields, if those are more convenient.
Note that \\(E_z\\) was never discretized, so \\(\\beta\\) will need adjustment to account for numerical dispersion if the result is introduced into a space with a discretized z-axis.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_e","title":"operator_e","text":"operator_e(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nWaveguide operator of the form
omega**2 * mu * epsilon +\nmu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +\n[[Dx], [Dy]] / epsilon * [Dx, Dy] * epsilon\nfor use with a field vector of the form
cat([E_x, E_y]).More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form
operator_e(...) @ [E_x, E_y] = wavenumber**2 * [E_x, E_y]which can then be solved for the eigenmodes of the system (an
exp(-i * wavenumber * z)z-dependence is assumed for the fields).Parameters:
Name Type Description DefaultomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_h","title":"operator_h","text":"operator_h(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nWaveguide operator of the form
omega**2 * epsilon * mu +\nepsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +\n[[Dx], [Dy]] / mu * [Dx, Dy] * mu\nfor use with a field vector of the form
cat([H_x, H_y]).More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\epsilon_{yy} \\mu_{xx} & 0 \\\\ 0 & \\epsilon_{xx} \\mu_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\epsilon_{yy} \\tilde{\\partial}_y \\\\ \\epsilon_{xx} \\tilde{\\partial}_x \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} -\\hat{\\partial}_y & \\hat{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\hat{\\partial}_x \\\\ \\hat{\\partial}_y \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} \\tilde{\\partial}_x \\mu_{xx} & \\tilde{\\partial}_y \\mu_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form
operator_h(...) @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]which can then be solved for the eigenmodes of the system (an
exp(-i * wavenumber * z)z-dependence is assumed for the fields).Parameters:
Name Type Description DefaultomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_e","title":"normalized_fields_e","text":"normalized_fields_e(\n e_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
e_xycontaining the vectorized E_x and E_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulte_xyvcfdfield2Vector containing E_x and E_y fields
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
Notes
e_xyis only the transverse electric eigenvector. This helper first reconstructs the full three-componentEandHfields withexy2e(...)andexy2h(...), then normalizes them to unit forward power using_normalized_fields(...).The normalization target is
\\[ \\Re\\left[\\mathrm{inner\\_product}(e, h, \\mathrm{conj\\_h}=True)\\right] = 1, \\]so the returned fields represent a unit-power forward mode under the discrete Yee-grid Poynting inner product.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_h","title":"normalized_fields_h","text":"normalized_fields_h(\n h_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
h_xycontaining the vectorized H_x and H_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulth_xyvcfdfield2Vector containing H_x and H_y fields
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
NotesThis is the
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.exy2h","title":"exy2h","text":"H_x/H_yanalogue ofnormalized_fields_e(...). The final normalized mode should describe the same physical solution, but because the overall complex phase and sign are chosen heuristically,normalized_fields_e(...)andnormalized_fields_h(...)need not return identical representatives for nearly symmetric modes.exy2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_x and E_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2e","title":"hxy2e","text":"hxy2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
h_xycontaining the vectorized H_x and H_y fields, into a vectorized E containing all three E componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2h","title":"hxy2h","text":"hxy2h(\n wavenumber: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
h_xycontaining the vectorized H_x and H_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xydxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)muvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.exy2e","title":"exy2e","text":"exy2e(\n wavenumber: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_x and E_y fields, into a vectorized E containing all three E componentsFrom the operator derivation (see module docs), we have
\\[ \\imath \\omega \\epsilon_{zz} E_z = \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\]as well as the intermediate equations
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\end{aligned} \\]Combining these, we get
\\[ \\begin{aligned} E_z &= \\frac{1}{- \\omega \\beta \\epsilon_{zz}} (( \\hat{\\partial}_y \\hat{\\partial}_x H_z -\\hat{\\partial}_x \\hat{\\partial}_y H_z) + \\imath \\omega (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y)) &= \\frac{1}{\\imath \\beta \\epsilon_{zz}} (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y) \\end{aligned} \\]Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xydxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.e2h","title":"e2h","text":"e2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)muvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h2e","title":"h2e","text":"h2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized H eigenfield, produces the vectorized E eigenfield slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_e","title":"curl_e","text":"curl_e(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\nDiscretized curl operator for use with the waveguide E field slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)dxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)Returns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_h","title":"curl_h","text":"curl_h(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\nDiscretized curl operator for use with the waveguide H field slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)dxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)Returns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h_err","title":"h_err","text":"h_err(\n h: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\nCalculates the relative error in the H field
Parameters:
Name Type Description DefaulthvcfdsliceVectorized H field
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionfloatRelative error
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.e_err","title":"e_err","text":"norm(A_h @ h) / norm(h).e_err(\n e: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\nCalculates the relative error in the E field
Parameters:
Name Type Description DefaultevcfdsliceVectorized E field
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionfloatRelative error
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.sensitivity","title":"sensitivity","text":"norm(A_e @ e) / norm(e).sensitivity(\n e_norm: vcfdslice,\n h_norm: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> vcfdslice_t\nGiven a waveguide structure (
dxes,epsilon,mu) and mode fields (e_norm,h_norm,wavenumber,omega), calculates the sensitivity of the wavenumber \\(\\beta\\) to changes in the dielectric structure \\(\\epsilon\\).The output is a vector of the same size as
\\[sens_{i} = \\frac{\\partial\\beta}{\\partial\\epsilon_i}\\]vec(epsilon), with each element specifying the sensitivity ofwavenumberto changes in the corresponding element invec(epsilon), i.e.An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
\\[\\beta^2 E_{xy} = A_E E_{xy}\\]where \\(A_E\\) is the waveguide operator from
\\[ (2 \\beta) \\partial_{\\epsilon_i}(\\beta) E_{xy} + \\beta^2 \\partial_{\\epsilon_i} E_{xy} = \\partial_{\\epsilon_i}(A_E) E_{xy} + A_E \\partial_{\\epsilon_i} E_{xy} \\]operator_e(), and \\(E_{xy} = \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix}\\), we can differentiate with respect to one of the \\(\\epsilon\\) elements (i.e. at one Yee grid point), \\(\\epsilon_i\\):We then multiply by \\(H_{yx}^\\star = \\begin{bmatrix}H_y^\\star \\\\ -H_x^\\star \\end{bmatrix}\\) from the left:
\\[ (2 \\beta) \\partial_{\\epsilon_i}(\\beta) H_{yx}^\\star E_{xy} + \\beta^2 H_{yx}^\\star \\partial_{\\epsilon_i} E_{xy} = H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} + H_{yx}^\\star A_E \\partial_{\\epsilon_i} E_{xy} \\]However, \\(H_{yx}^\\star\\) is actually a left-eigenvector of \\(A_E\\). This can be verified by inspecting the form of
\\[ H_{yx}^\\star A_E \\partial_{\\epsilon_i} E_{xy} = \\beta^2 H_{yx}^\\star \\partial_{\\epsilon_i} E_{xy} \\]operator_h(\\(A_H\\)) and comparing its conjugate transpose tooperator_e(\\(A_E\\)). Also, note \\(H_{yx}^\\star \\cdot E_{xy} = H^\\star \\times E\\) recalls the mode orthogonality relation. See doi:10.5194/ars-9-85-201 for a similar approach. Therefore,and we can simplify to
\\[ \\partial_{\\epsilon_i}(\\beta) = \\frac{1}{2 \\beta} \\frac{H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} }{H_{yx}^\\star E_{xy}} \\]This expression can be quickly calculated for all \\(i\\) by writing out the various terms of \\(\\partial_{\\epsilon_i} A_E\\) and recognizing that the vector-matrix-vector products (i.e. scalars) \\(sens_i = \\vec{v}_{left} \\partial_{\\epsilon_i} (\\epsilon_{xyz}) \\vec{v}_{right}\\), indexed by \\(i\\), can be expressed as elementwise multiplications \\(\\vec{sens} = \\vec{v}_{left} \\star \\vec{v}_{right}\\)
Parameters:
Name Type Description Defaulte_normvcfdsliceNormalized, vectorized E_xyz field for the mode. E.g. as returned by
requirednormalized_fields_e.h_normvcfdsliceNormalized, vectorized H_xyz field for the mode. E.g. as returned by
requirednormalized_fields_e.wavenumbercomplexPropagation constant for the mode. The z-axis is assumed to be continuous (i.e. without numerical dispersion).
requiredomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type Descriptionvcfdslice_tSparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nGiven a 2D region, attempts to solve for the eigenmode with the specified mode numbers.
Parameters:
Name Type Description Defaultmode_numbersSequence[int]List of 0-indexed mode numbers to solve for
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdsliceDielectric constant
requiredmuvfdslice | NoneMagnetic permeability (default 1 everywhere)
Nonemode_marginintThe eigensolver will actually solve for
(max(mode_number) + mode_margin)modes, but only return the target mode. Increasing this value can improve the solver's ability to find the correct mode. Default 2.2Returns:
Name Type Descriptione_xysNDArray[complex128]NDArray of vfdfield_t specifying fields. First dimension is mode number.
wavenumbersNDArray[complex128]list of wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdfield2_t, complex]\nWrapper around
solve_modes()that solves for a single mode.Parameters:
Name Type Description Defaultmode_numberint0-indexed mode number to solve for
required*argsAnypassed to
solve_modes()()**kwargsAnypassed to
solve_modes(){}Returns:
Type Descriptiontuple[vcfdfield2_t, complex](e_xy, wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.inner_product","title":"inner_product","text":"inner_product(\n e1: vcfdfield2,\n h2: vcfdfield2,\n dxes: dx_lists2_t,\n prop_phase: float = 0,\n conj_h: bool = False,\n trapezoid: bool = False,\n) -> complex\nCompute the discrete waveguide overlap / Poynting inner product.
This is the 2D transverse integral corresponding to the time-averaged longitudinal Poynting flux,
\\[ \\frac{1}{2}\\int (E_x H_y - E_y H_x) \\, dx \\, dy \\]with the Yee-grid staggering and optional propagation-phase adjustment used by the waveguide helpers in this module.
Parameters:
Name Type Description Defaulte1vcfdfield2Vectorized electric field, typically from
requiredexy2e(...)ornormalized_fields_e(...).h2vcfdfield2Vectorized magnetic field, typically from
requiredhxy2h(...),exy2h(...), or one of the normalization helpers.dxesdx_lists2_tTwo-dimensional Yee-grid spacing lists
required[dx_e, dx_h].prop_phasefloatPhase advance over one propagation cell. This is used to shift the H field into the same longitudinal reference plane as the E field.
0conj_hboolWhether to conjugate
h2before forming the overlap. UseTruefor the usual time-averaged power normalization.FalsetrapezoidboolWhether to use trapezoidal quadrature instead of the default rectangular Yee-cell sum.
FalseReturns:
Type DescriptioncomplexComplex overlap / longitudinal power integral.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d","title":"meanas.fdfd.waveguide_3d","text":"Tools for working with waveguide modes in 3D domains.
This module relies heavily on
waveguide_2dand mostly just transforms its parameters into 2D equivalents and expands the results back into 3D.The intended workflow is:
- Select a single-cell slice normal to the propagation axis.
- Solve the corresponding 2D mode problem with
solve_mode(...).- Turn that mode into a one-sided source with
compute_source(...).- Build an overlap window with
compute_overlap_e(...)for port readout.
polarityis part of the public convention throughout this module:
+1means forward propagation toward increasing index alongaxis-1means backward propagation toward decreasing index alongaxisThat same convention controls which side of the selected slice is used for the overlap window and how the expanded field is phased.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> dict[str, complex | NDArray[complexfloating]]\nGiven a 3D grid, selects a slice from the grid and attempts to solve for an eigenmode propagating through that slice.
Parameters:
Name Type Description Defaultmode_numberintNumber of the mode, 0-indexed
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section.slices[axis]must select exactly one item.epsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptiondict[str, complex | NDArray[complexfloating]]Dictionary containing:
dict[str, complex | NDArray[complexfloating]]
E: full-grid electric field for the solved modedict[str, complex | NDArray[complexfloating]]
H: full-grid magnetic field for the solved modedict[str, complex | NDArray[complexfloating]]
wavenumber: propagation constant corrected for the discretized propagation axisdict[str, complex | NDArray[complexfloating]]Notes
wavenumber_2d: propagation constant of the reduced 2D eigenproblemThe returned fields are normalized through the
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.compute_source","title":"compute_source","text":"waveguide_2dnormalization convention before being expanded back to 3D.compute_source(\n E: cfdfield,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_t\nGiven an eigenmode obtained by
solve_mode, returns the current source distribution necessary to position a unidirectional source at the slice location.Parameters:
Name Type Description DefaultEcfdfieldE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section.slices[axis]should select only one item.mufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_tNotes
Jdistribution for a one-sided electric-current source.The source is built from the expanded mode field and a boundary-source operator. The resulting current is intended to be injected with the same sign convention used elsewhere in the package:
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.compute_overlap_e","title":"compute_overlap_e","text":"
E -= dt * J / epsiloncompute_overlap_e(\n E: cfdfield_t,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n omega: float,\n) -> cfdfield_t\nBuild an overlap field for projecting another 3D electric field onto a mode.
The returned field is intended for the discrete overlap expression
\\[ \\sum \\mathrm{overlap\\_e} \\; E_\\mathrm{other}^* \\]where the sum is over the full Yee-grid field storage.
The construction uses a two-cell window immediately upstream of the selected slice:
- for
polarity=+1, the two cells just beforeslices[axis].start- for
polarity=-1, the two cells just afterslices[axis].stopThe window is clipped to the simulation domain if necessary. A clipped but non-empty window raises
RuntimeWarning; an empty window raisesValueError.The derivation below assumes reflection symmetry and the standard waveguide overlap relation involving
\\[ \\int ((E \\times H_\\mathrm{mode}) + (E_\\mathrm{mode} \\times H)) \\cdot dn. \\]E x H_mode + E_mode x H -> Ex Hmy - EyHmx + Emx Hy - Emy Hx (Z-prop) Ex we/B Emx + Ex i/B dy Hmz - Ey (-we/B Emy) - Ey i/B dx Hmz we/B (Ex Emx + Ey Emy) + i/B (Ex dy Hmz - Ey dx Hmz) we/B (Ex Emx + Ey Emy) + i/B (Ex dy (dx Emy - dy Emx) - Ey dx (dx Emy - dy Emx)) we/B (Ex Emx + Ey Emy) + i/B (Ex dy dx Emy - Ex dy dy Emx - Ey dx dx Emy - Ey dx dy Emx)
Ex j/wu (-jB Emx - dx Emz) - Ey j/wu (dy Emz + jB Emy) B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)
Parameters:
Name Type Description DefaultEcfdfield_tE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.Returns:
Type Descriptioncfdfield_t
overlap_enormalized so thatnumpy.sum(overlap_e * E.conj()) == 1cfdfield_tover the retained overlap window.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.expand_e","title":"expand_e","text":"expand_e(\n E: cfdfield,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n) -> cfdfield_t\nGiven an eigenmode obtained by
solve_mode, expands the E-field from the 2D slice where the mode was calculated to the entire domain (along the propagation axis). This assumes the epsilon cross-section remains constant throughout the entire domain; it is up to the caller to truncate the expansion to any regions where it is valid.Parameters:
Name Type Description DefaultEcfdfieldE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.Returns:
Type Descriptioncfdfield_tNotes
E, with the original field expanded along the specifiedaxis.This helper assumes that the waveguide cross-section remains constant along the propagation axis and applies the phase factor
\\[ e^{-i \\, \\mathrm{polarity} \\, wavenumber \\, \\Delta z} \\]to each copied slice.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl","title":"meanas.fdfd.waveguide_cyl","text":"Operators and helper functions for cylindrical waveguides with unchanging cross-section.
Waveguide operator is derived according to 10.1364/OL.33.001848.
As in
\\[ \\vec{E}(r, y, \\theta), \\vec{H}(r, y, \\theta) \\propto e^{-\\imath m \\theta}, \\]waveguide_2d, the propagation dependence is separated from the transverse solve. Here the propagation coordinate is the bend angle\\theta, and the fields are assumed to have the formwhere
\\[ \\beta = \\frac{m}{r_{\\min}}, \\]mis the angular wavenumber returned bysolve_mode(s). It is often convenient to introduce the corresponding linear wavenumberso that the cylindrical problem resembles the straight-waveguide problem with additional metric factors.
Those metric factors live on the staggered radial Yee grids. If the left edge of the computational window is at
\\[ \\begin{aligned} r_a(n) &= r_{\\min} + \\sum_{j \\le n} \\Delta r_{e, j}, \\\\ r_b\\!\\left(n + \\tfrac{1}{2}\\right) &= r_{\\min} + \\tfrac{1}{2}\\Delta r_{e, n} + \\sum_{j < n} \\Delta r_{h, j}, \\end{aligned} \\]r = r_{\\min}, define the electric-grid and magnetic-grid radial sample locations byand from them the diagonal metric matrices
\\[ \\begin{aligned} T_a &= \\operatorname{diag}(r_a / r_{\\min}), \\\\ T_b &= \\operatorname{diag}(r_b / r_{\\min}). \\end{aligned} \\]With the same forward/backward derivative notation used in
\\[ \\begin{aligned} -\\imath \\omega \\mu_{rr} H_r &= \\tilde{\\partial}_y E_z + \\imath \\beta T_a^{-1} E_y, \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta T_b^{-1} E_r - T_b^{-1} \\tilde{\\partial}_r (T_a E_z), \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_r E_y - \\tilde{\\partial}_y E_r, \\\\ \\imath \\beta H_y &= -\\imath \\omega T_b \\epsilon_{rr} E_r - T_b \\hat{\\partial}_y H_z, \\\\ \\imath \\beta H_r &= \\imath \\omega T_a \\epsilon_{yy} E_y - T_b T_a^{-1} \\hat{\\partial}_r (T_b H_z), \\\\ \\imath \\omega E_z &= T_a \\epsilon_{zz}^{-1} \\left(\\hat{\\partial}_r H_y - \\hat{\\partial}_y H_r\\right). \\end{aligned} \\]waveguide_2d, the coordinate-transformed discrete curl equations used here areThe first three equations are the cylindrical analogue of the straight-guide relations for
H_r,H_y, andH_z. The next two are the metric-weighted versions of the straight-guide identities for\\imath \\beta H_yand\\imath \\beta H_r, and the last equation plays the same role as the longitudinalE_zreconstruction inwaveguide_2d.Following the same elimination steps as in
\\[ H_z = \\frac{1}{-\\imath \\omega \\mu_{zz}} \\left(\\tilde{\\partial}_r E_y - \\tilde{\\partial}_y E_r\\right). \\]waveguide_2d, apply\\imath \\beta \\tilde{\\partial}_rand\\imath \\beta \\tilde{\\partial}_yto the equation forE_z, substitute for\\imath \\beta H_rand\\imath \\beta H_y, and then eliminateH_zwithThis yields the transverse electric eigenproblem implemented by
\\[ \\beta^2 \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix} = \\left( \\omega^2 \\begin{bmatrix} T_b^2 \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & T_a^2 \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -T_b \\mu_{yy} \\hat{\\partial}_y \\\\ T_a \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} T_b \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} T_a \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x T_b \\epsilon_{xx} & \\hat{\\partial}_y T_a \\epsilon_{yy} \\end{bmatrix} \\right) \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix}. \\]cylindrical_operator(...):Since
\\beta = m / r_{\\min}, the solver implemented in this file returns the angular wavenumberm, while the operator itself is most naturally written in terms of the linear quantity\\beta. The helpers below reconstruct the full field components from the solved transverse eigenvector and then normalize the mode to unit forward power with the same discrete longitudinal Poynting inner product used bywaveguide_2d.As in the straight-waveguide case, all functions here assume a 2D grid:
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.cylindrical_operator","title":"cylindrical_operator","text":"
dxes = [[[dr_e_0, dr_e_1, ...], [dy_e_0, ...]], [[dr_h_0, ...], [dy_h_0, ...]]].cylindrical_operator(\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n) -> sparse.sparray\nCylindrical coordinate waveguide operator of the form
\\[ (\\omega^2 \\begin{bmatrix} T_b T_b \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & T_a T_a \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -T_b \\mu_{yy} \\hat{\\partial}_y \\\\ T_a \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} T_b \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} T_a \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x T_b \\epsilon_{xx} & \\hat{\\partial}_y T_a \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix} \\]for use with a field vector of the form
[E_r, E_y].This operator can be used to form an eigenvalue problem of the form A @ [E_r, E_y] = beta**2 * [E_r, E_y]
which can then be solved for the eigenmodes of the system (an
exp(-i * angular_wavenumber * theta)theta-dependence is assumed for the fields, withbeta = angular_wavenumber / rmin).(NOTE: See module docs and 10.1364/OL.33.001848)
Parameters:
Name Type Description DefaultomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredrminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type DescriptionsparraySparse matrix representation of the operator
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nGiven a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode of the bent waveguide with the specified mode number.
Parameters:
Name Type Description Defaultmode_numberNumber of the mode, 0-indexed
requiredomegafloatAngular frequency of the simulation
requireddxesdx_lists2_tGrid parameters [dx_e, dx_h] as described in meanas.fdmath.types. The first coordinate is assumed to be r, the second is y.
requiredepsilonvfdsliceDielectric constant
requiredrminfloatRadius of curvature for the simulation. This should be the minimum value of r within the simulation domain.
requiredReturns:
Name Type Descriptione_xysNDArray[complex128]NDArray of vfdfield_t specifying fields. First dimension is mode number.
angular_wavenumbersNDArray[complex128]list of wavenumbers in 1/rad units.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdslice, complex]\nWrapper around
solve_modes()that solves for a single mode.Parameters:
Name Type Description Defaultmode_numberint0-indexed mode number to solve for
required*argsAnypassed to
solve_modes()()**kwargsAnypassed to
solve_modes(){}Returns:
Type Descriptiontuple[vcfdslice, complex](e_xy, angular_wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.linear_wavenumbers","title":"linear_wavenumbers","text":"linear_wavenumbers(\n e_xys: list[vcfdfield2_t],\n angular_wavenumbers: ArrayLike,\n epsilon: vfdslice,\n dxes: dx_lists2_t,\n rmin: float,\n) -> NDArray[numpy.complex128]\nCalculate linear wavenumbers (1/distance) based on angular wavenumbers (1/rad) and the mode's energy distribution.
Parameters:
Name Type Description Defaulte_xyslist[vcfdfield2_t]Vectorized mode fields with shape (num_modes, 2 * x *y)
requiredangular_wavenumbersArrayLikeWavenumbers assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). They should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyepsilonvfdsliceVectorized dielectric constant grid with shape (3, x, y)
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type DescriptionNDArray[complex128]NDArray containing the calculated linear (1/distance) wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2h","title":"exy2h","text":"exy2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_r and E_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2e","title":"exy2e","text":"exy2e(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_r and E_y fields, into a vectorized E containing all three E componentsUnlike the straight waveguide case, the H_z components do not cancel and must be calculated from E_r and E_y in order to then calculate E_z.
Parameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.e2h","title":"e2h","text":"e2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
This operator is created directly from the initial coordinate-transformed equations: $$ \\begin{aligned} -\\imath \\omega \\mu_{rr} H_r &= \\tilde{\\partial}y E_z + \\imath \\beta T_a^{-1} E_y, \\ -\\imath \\omega \\mu E_r - T_b^{-1} \\tilde{\\partial}} H_y &= -\\imath \\beta T_b^{-1r (T_a E_z), \\ -\\imath \\omega \\mu_y E_r, \\end{aligned} $$} H_z &= \\tilde{\\partial}_r E_y - \\tilde{\\partial
Parameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.dxes2T","title":"dxes2T","text":"dxes2T(\n dxes: dx_lists2_t, rmin: float\n) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]\nConstruct the cylindrical metric matrices \\(T_a\\) and \\(T_b\\).
T_ais sampled on the E-grid radial locations, whileT_bis sampled on the staggered H-grid radial locations. These are the diagonal matrices that convert the straight-waveguide algebra into its cylindrical counterpart.Parameters:
Name Type Description Defaultdxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type Descriptiontuple[NDArray[float64], NDArray[float64]]Sparse diagonal matrices
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.normalized_fields_e","title":"normalized_fields_e","text":"(T_a, T_b).normalized_fields_e(\n e_xy: vcfdfield2,\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
e_xycontaining the vectorized E_r and E_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulte_xyvcfdfield2Vector containing E_r and E_y fields
requiredangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
NotesThe normalization step is delegated to
\\[ \\frac{1}{2}\\int (E_r H_y^* - E_y H_r^*) \\, dr \\, dy. \\]_normalized_fields(...), which enforces unit forward power under the discrete inner productThe angular wavenumber
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"meanas","text":"mis first converted into the full three-component fields, then the overall complex phase and sign are fixed so the result is reproducible for symmetric modes.
meanasis a Python package for finite-difference electromagnetic simulation. It combines:
meanas.fdfdfor frequency-domain operators, sources, waveguide modes, and SCPMLmeanas.fdtdfor Yee-grid timestepping, CPML, energy/flux accounting, and phasor extractionmeanas.fdmathfor the shared discrete operators and derivations underneath both solversThis documentation is built directly from the package docstrings. The API pages are the source of truth for the mathematical derivations and calling conventions.
"},{"location":"#examples-and-api-map","title":"Examples and API Map","text":"For most users, the tracked examples under
examples/are the right entry point. They show the intended combinations of tools for solving complete problems.Relevant starting examples:
examples/fdtd.pyfor broadband pulse excitation and phasor extractionexamples/waveguide.pyfor guided phasor-domain FDTD/FDFD comparisonexamples/waveguide_real.pyfor real-valued continuous-wave FDTD compared against real fields reconstructed from an FDFD solution, including guided-core, mode-weighted, and guided-mode / residual comparisonsexamples/fdfd.pyfor direct frequency-domain waveguide excitationFor solver equivalence, prefer the phasor-based examples first. They compare the extracted
+\\omegacontent of the FDTD run directly against the FDFD solution and are the main accuracy benchmarks in the test suite.
examples/waveguide_real.pyanswers a different, stricter question: how well a late raw real snapshot matchesRe(E_\\omega e^{i\\omega t})on a monitor plane. That diagnostic is useful, but it also includes orthogonal residual structure that the phasor comparison intentionally filters out.The API pages are better read as a toolbox map and derivation reference:
"},{"location":"#build-outputs","title":"Build outputs","text":"
- Use the FDTD API for time-domain stepping, CPML, phasor extraction, and real-field reconstruction from FDFD phasors.
- Use the FDFD API for driven frequency-domain solves and sparse operator algebra.
- Use the Waveguide API for mode solving, port sources, and overlap windows.
- Use the fdmath API for the lower-level finite-difference operators and the shared discrete derivations underneath both solvers.
The docs build generates two HTML views from the same source:
- a normal multi-page site
- a print-oriented combined page under
site/print_page/If
"},{"location":"api/","title":"API Overview","text":"htmlarkis installed,./make_docs.shalso writes a fully inlinedsite/standalone.html.The package is documented directly from its docstrings. The most useful entry points are:
- meanas: top-level package overview
- eigensolvers: generic eigenvalue utilities used by the mode solvers
- fdfd: frequency-domain operators, sources, PML, solvers, and far-field transforms
- waveguides: straight, cylindrical, and 3D waveguide mode helpers
- fdtd: timestepping, CPML, energy/flux helpers, and phasor extraction
- fdmath: shared discrete operators, vectorization helpers, and derivation background
The waveguide and FDTD pages are the best places to start if you want the mathematical derivations rather than just the callable reference.
"},{"location":"api/eigensolvers/","title":"eigensolvers","text":""},{"location":"api/eigensolvers/#meanas.eigensolvers","title":"meanas.eigensolvers","text":"Solvers for eigenvalue / eigenvector problems
"},{"location":"api/eigensolvers/#meanas.eigensolvers.power_iteration","title":"power_iteration","text":"power_iteration(\n operator: spmatrix,\n guess_vector: NDArray[complex128] | None = None,\n iterations: int = 20,\n) -> tuple[complex, NDArray[numpy.complex128]]\nUse power iteration to estimate the dominant eigenvector of a matrix.
Parameters:
Name Type Description DefaultoperatorspmatrixMatrix to analyze.
requiredguess_vectorNDArray[complex128] | NoneStarting point for the eigenvector. Default is a randomly chosen vector.
NoneiterationsintNumber of iterations to perform. Default 20.
20Returns:
Type Descriptiontuple[complex, NDArray[complex128]](Largest-magnitude eigenvalue, Corresponding eigenvector estimate)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.rayleigh_quotient_iteration","title":"rayleigh_quotient_iteration","text":"rayleigh_quotient_iteration(\n operator: spmatrix | LinearOperator,\n guess_vector: NDArray[complex128],\n iterations: int = 40,\n tolerance: float = 1e-13,\n solver: Callable[..., NDArray[complex128]]\n | None = None,\n) -> tuple[complex, NDArray[numpy.complex128]]\nUse Rayleigh quotient iteration to refine an eigenvector guess.
Parameters:
Name Type Description Defaultoperatorspmatrix | LinearOperatorMatrix to analyze.
requiredguess_vectorNDArray[complex128]Eigenvector to refine.
requirediterationsintMaximum number of iterations to perform. Default 40.
40tolerancefloatStop iteration if
(A - I*eigenvalue) @ v < num_vectors * tolerance, Default 1e-13.1e-13solverCallable[..., NDArray[complex128]] | NoneSolver function of the form
x = solver(A, b). By default, use scipy.sparse.spsolve for sparse matrices and scipy.sparse.bicgstab for general LinearOperator instances.NoneReturns:
Type Descriptiontuple[complex, NDArray[complex128]](eigenvalues, eigenvectors)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.signed_eigensolve","title":"signed_eigensolve","text":"signed_eigensolve(\n operator: spmatrix | LinearOperator,\n how_many: int,\n negative: bool = False,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nFind the largest-magnitude positive-only (or negative-only) eigenvalues and eigenvectors of the provided matrix.
Parameters:
Name Type Description Defaultoperatorspmatrix | LinearOperatorMatrix to analyze.
requiredhow_manyintHow many eigenvalues to find.
requirednegativeboolWhether to find negative-only eigenvalues. Default False (positive only).
FalseReturns:
Type DescriptionNDArray[complex128](sorted list of eigenvalues, 2D ndarray of corresponding eigenvectors)
NDArray[complex128]"},{"location":"api/fdfd/","title":"fdfd","text":""},{"location":"api/fdfd/#meanas.fdfd","title":"meanas.fdfd","text":"
eigenvectors[:, k]corresponds to the k-th eigenvalueTools for finite difference frequency-domain (FDFD) simulations and calculations.
These mostly involve picking a single frequency, then setting up and solving a matrix equation (Ax=b) or eigenvalue problem.
Submodules:
operators,functional: General FDFD problem setup.solvers: Solver interface and reference implementation.scpml: Stretched-coordinate perfectly matched layer (SCPML) boundary conditions.waveguide_2d: Operators and mode-solver for waveguides with constant cross-section.waveguide_3d: Functions for transformingwaveguide_2dresults into 3D, including mode-source and overlap-window construction.farfield,bloch,eme: specialized helper modules for near/far transforms, Bloch-periodic problems, and eigenmode expansion.================================================================
From the \"Frequency domain\" section of
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &= \\tilde{E}_{\\vec{r}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\tilde{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &= \\tilde{H}_{\\vec{r} + \\frac{1}{2}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\\\ \\tilde{J}_{l, \\vec{r}} &= \\tilde{J}_{\\vec{r}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\\\ \\tilde{M}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &= \\tilde{M}_{\\vec{r} + \\frac{1}{2}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\Omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} &= -\\imath \\Omega \\tilde{J}_{\\vec{r}} e^{\\imath \\omega \\Delta_t / 2} \\\\ \\Omega &= 2 \\sin(\\omega \\Delta_t / 2) / \\Delta_t \\end{aligned} \\]meanas.fdmath, we haveresulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\end{aligned} \\]Maxwell's equations are then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2} \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2} \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\tilde{\\nabla} \\cdot \\hat{B}_{\\vec{r} + \\frac{1}{2}} &= 0 \\\\ \\hat{\\nabla} \\cdot \\tilde{D}_{\\vec{r}} &= \\rho_{\\vec{r}} \\end{aligned} \\]With \\(\\Delta_t \\to 0\\), this simplifies to
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &\\to \\tilde{E}_{\\vec{r}} \\\\ \\tilde{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{H}_{\\vec{r} + \\frac{1}{2}} \\\\ \\tilde{J}_{l, \\vec{r}} &\\to \\tilde{J}_{\\vec{r}} \\\\ \\tilde{M}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\Omega &\\to \\omega \\\\ \\tilde{\\partial}_t &\\to -\\imath \\omega \\\\ \\hat{\\partial}_t &\\to -\\imath \\omega \\\\ \\end{aligned} \\]and then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\omega \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\omega \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\end{aligned} \\] \\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\omega \\tilde{J}_{\\vec{r}} \\\\ \\]"},{"location":"api/fdfd/#core-operator-layers","title":"Core operator layers","text":""},{"location":"api/fdfd/#meanas.fdfd.functional","title":"meanas.fdfd.functional","text":"Functional versions of many FDFD operators. These can be useful for performing FDFD calculations without needing to construct large matrices in memory.
The functions generated here expect
"},{"location":"api/fdfd/#meanas.fdfd.functional.e_full","title":"e_full","text":"cfdfield_tinputs with shape (3, X, Y, Z), e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nWave operator for use with E-field. See
operators.e_fullfor details.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
fimplementing the wave operatorcfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.eh_full","title":"eh_full","text":"
f(E)->-i * omega * Jeh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> Callable[\n [cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]\n]\nWave operator for full (both E and H) field representation. See
operators.eh_full.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type DescriptionCallable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]Function
fimplementing the wave operatorCallable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]"},{"location":"api/fdfd/#meanas.fdfd.functional.e2h","title":"e2h","text":"
f(E, H)->(J, -M)e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nUtility operator for converting the
Efield into theHfield. For use withe_full-- assumes that there is no magnetic currentM.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
ffor convertingEtoH,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.m2j","title":"m2j","text":"
f(E)->Hm2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nUtility operator for converting magnetic current
Mdistribution into equivalent electric current distributionJ. For use with e.g.e_full.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
ffor convertingMtoJ,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.e_tfsf_source","title":"e_tfsf_source","text":"
f(M)->Je_tfsf_source(\n TF_region: fdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\nOperator that turns an E-field distribution into a total-field/scattered-field (TFSF) source.
If
\\[ \\frac{A Q - Q A}{-i \\omega} E. \\]Ais the full wave operator frome_full(...)andQis the diagonal mask selecting the total-field region, then the TFSF source is the commutatorThis vanishes in the interior of the total-field and scattered-field regions and is supported only at their shared boundary, where the mask discontinuity makes
AandQfail to commute. The returned current is therefore the distributed source needed to inject the desired total field without also forcing the scattered-field region.Parameters:
Name Type Description DefaultTF_regionfdfieldmask which is set to 1 in the total-field region, and 0 elsewhere (i.e. in the scattered-field region). Should have the same shape as the simulation grid, e.g.
requiredepsilon[0].shape.omegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonfdfieldDielectric constant distribution
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_updater_tFunction
fwhich takes an E field and returns a current distribution,cfdfield_updater_t"},{"location":"api/fdfd/#meanas.fdfd.functional.poynting_e_cross_h","title":"poynting_e_cross_h","text":"
f(E)->Jpoynting_e_cross_h(\n dxes: dx_lists_t,\n) -> Callable[[cfdfield, cfdfield], cfdfield_t]\nGenerates a function that takes the single-frequency
EandHfields and calculates the cross productExH= \\(E \\times H\\) as required for the Poynting vector, \\(S = E \\times H\\).On the Yee grid, the electric and magnetic components are not stored at the same locations. This helper therefore applies the same one-cell electric-field shifts used by the sparse
Noteoperators.poynting_e_cross(...)construction so that the discrete cross product matches the face-centered energy flux used inmeanas.fdtd.energy.poynting(...).This function also shifts the input
NoteEfield by one cell as required for computing the Poynting cross product (seemeanas.fdfdmodule docs).If
EandHare peak amplitudes as assumed elsewhere in this code, the time-average of the poynting vector is<S> = Re(S)/2 = Re(E x H*) / 2. The factor of1/2can be omitted if root-mean-square quantities are used instead.Parameters:
Name Type Description Defaultdxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionCallable[[cfdfield, cfdfield], cfdfield_t]Function
fthat returns the staggered-grid cross productE \\times H.Callable[[cfdfield, cfdfield], cfdfield_t]For time-average power, call it as
"},{"location":"api/fdfd/#meanas.fdfd.operators","title":"meanas.fdfd.operators","text":"f(E, H.conj())and takeRe(...) / 2.Sparse matrix operators for use with electromagnetic wave equations.
These functions return sparse-matrix (
scipy.sparse.sparray) representations of a variety of operators, intended for use with E and H fields vectorized using themeanas.fdmath.vectorization.vec()andmeanas.fdmath.vectorization.unvec()functions.E- and H-field values are defined on a Yee cell;
epsilonvalues should be calculated for cells centered at each E component (muat each H component).Many of these functions require a
dxesparameter, of typedx_lists_t; see themeanas.fdmath.typessubmodule for details.The following operators are included:
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_full","title":"e_full","text":"
- E-only wave operator
- H-only wave operator
- EH wave operator
- Curl for use with E, H fields
- E to H conversion
- M to J conversion
- Poynting cross products
- Circular shifts
- Discrete derivatives
- Averaging operators
- Cross product matrices
e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield | vcfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator \\[ \\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon \\]
del x (1/mu * del x) - omega**2 * epsilon\nfor use with the E-field, with wave equation \\[ (\\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon) E = -\\imath \\omega J \\]
(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * J\nTo make this matrix symmetric, use the preconditioners from
e_full_preconditioners().Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfield | vcfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_full_preconditioners","title":"e_full_preconditioners","text":"e_full_preconditioners(\n dxes: dx_lists_t,\n) -> tuple[sparse.sparray, sparse.sparray]\nLeft and right preconditioners
(Pl, Pr)for symmetrizing thee_fullwave operator.The preconditioned matrix
A_symm = (Pl @ A @ Pr)is complex-symmetric (non-Hermitian unless there is no loss or PMLs).The preconditioner matrices are diagonal and complex, with
Pr = 1 / PlParameters:
Name Type Description Defaultdxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type Descriptiontuple[sparray, sparray]Preconditioner matrices
"},{"location":"api/fdfd/#meanas.fdfd.operators.h_full","title":"h_full","text":"(Pl, Pr).h_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator \\[ \\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu \\]
del x (1/epsilon * del x) - omega**2 * mu\nfor use with the H-field, with wave equation \\[ (\\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu) E = \\imath \\omega M \\]
(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * M\nParameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.eh_full","title":"eh_full","text":"eh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nWave operator for
\\[ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\\\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} \\][E, H]field representation. This operator implements Maxwell's equations without cancelling out either E or H. The operator is[[-i * omega * epsilon, del x ],\n [del x, i * omega * mu]]\nfor use with a field vector of the form
\\[ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\\\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} \\begin{bmatrix} E \\\\ H \\end{bmatrix} = \\begin{bmatrix} J \\\\ -M \\end{bmatrix} \\]cat(vec(E), vec(H)):Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepecvfdfield | NoneVectorized mask specifying PEC cells. Any cells where
pec != 0are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e.pec.size == epsilon.size)Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e2h","title":"e2h","text":"e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\nUtility operator for converting the E field into the H field. For use with
e_full()-- assumes that there is no magnetic current M.Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
Nonepmcvfdfield | NoneVectorized mask specifying PMC cells. Any cells where
pmc != 0are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e.pmc.size == epsilon.size)NoneReturns:
Type DescriptionsparraySparse matrix for converting E to H.
"},{"location":"api/fdfd/#meanas.fdfd.operators.m2j","title":"m2j","text":"m2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n) -> sparse.sparray\nOperator for converting a magnetic current M into an electric current J. For use with eg.
e_full().Parameters:
Name Type Description DefaultomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix for converting M to J.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_e_cross","title":"poynting_e_cross","text":"poynting_e_cross(\n e: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\nOperator for computing the staggered-grid
(E \\times)part of the Poynting vector.On the Yee grid the E and H components live on different edges, so the electric field must be shifted by one cell in the appropriate direction before forming the discrete cross product. This sparse operator encodes that shifted cross product directly and is the matrix equivalent of
functional.poynting_e_cross_h(...).Parameters:
Name Type Description DefaultevcfdfieldVectorized E-field for the ExH cross product
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionsparraySparse matrix containing the
(E \\times)part of the staggered Poyntingsparraycross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_h_cross","title":"poynting_h_cross","text":"poynting_h_cross(\n h: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\nOperator for computing the staggered-grid
(H \\times)part of the Poynting vector.Together with
\\[ H \\times E = -(E \\times H), \\]poynting_e_cross(...), this provides the matrix form of the Yee-grid cross product used in the flux helpers. The two are related by the usual antisymmetry of the cross product,once the same staggered field placement is used on both sides.
Parameters:
Name Type Description DefaulthvcfdfieldVectorized H-field for the HxE cross product
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesReturns:
Type DescriptionsparraySparse matrix containing the
(H \\times)part of the staggered Poyntingsparraycross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_tfsf_source","title":"e_tfsf_source","text":"e_tfsf_source(\n TF_region: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n) -> sparse.sparray\nOperator that turns a desired E-field distribution into a total-field/scattered-field (TFSF) source.
Let
\\[ \\frac{A Q - Q A}{-i \\omega}. \\]Abe the full wave operator frome_full(...), and letQ = \\mathrm{diag}(TF_region)be the projector onto the total-field region. Then the TFSF current operator is the commutatorInside regions where
Qis locally constant,AandQcommute and the source vanishes. Only cells at the TF/SF boundary contribute nonzero current, which is exactly the desired distributed source for injecting the chosen field into the total-field region without directly forcing the scattered-field region.Parameters:
Name Type Description DefaultTF_regionvfdfieldMask, which is set to 1 inside the total-field region and 0 in the scattered-field region
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
NoneReturns:
Type DescriptionsparraySparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_boundary_source","title":"e_boundary_source","text":"e_boundary_source(\n mask: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n periodic_mask_edges: bool = False,\n) -> sparse.sparray\nOperator that turns an E-field distrubtion into a current (J) distribution along the edges (external and internal) of the provided mask. This is just an
e_tfsf_source()with an additional masking step.Equivalently, this helper first constructs the TFSF commutator source for the full mask and then zeroes out all cells except the mask boundary. The boundary is defined as the set of cells whose mask value changes under a one-cell shift in any Cartesian direction. With
periodic_mask_edges=Falsethe shifts mirror at the domain boundary; withTruethey wrap periodically.Parameters:
Name Type Description DefaultmaskvfdfieldThe current distribution is generated at the edges of the mask, i.e. any points where shifting the mask by one cell in any direction would change its value.
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdfieldVectorized dielectric constant
requiredmuvfdfield | NoneVectorized magnetic permeability (default 1 everywhere).
NoneReturns:
Type DescriptionsparraySparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.solvers","title":"meanas.fdfd.solvers","text":"Solvers and solver interface for FDFD problems.
"},{"location":"api/fdfd/#meanas.fdfd.solvers.generic","title":"generic","text":"generic(\n omega: complex,\n dxes: dx_lists_t,\n J: vcfdfield,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n *,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n adjoint: bool = False,\n matrix_solver: Callable[..., ArrayLike] = _scipy_qmr,\n matrix_solver_opts: dict[str, Any] | None = None,\n E_guess: vcfdfield | None = None,\n) -> vcfdfield_t\nConjugate gradient FDFD solver using CSR sparse matrices.
All ndarray arguments should be 1D arrays, as returned by
meanas.fdmath.vectorization.vec().Parameters:
Name Type Description DefaultomegacomplexComplex frequency to solve at.
requireddxesdx_lists_trequired
[[dx_e, dy_e, dz_e], [dx_h, dy_h, dz_h]](complex cell sizes) as discussed inmeanas.fdmath.typesJvcfdfieldElectric current distribution (at E-field locations)
requiredepsilonvfdfieldDielectric constant distribution (at E-field locations)
requiredmuvfdfield | NoneMagnetic permeability distribution (at H-field locations)
Nonepecvfdfield | NonePerfect electric conductor distribution (at E-field locations; non-zero value indicates PEC is present)
Nonepmcvfdfield | NonePerfect magnetic conductor distribution (at H-field locations; non-zero value indicates PMC is present)
NoneadjointboolIf true, solves the adjoint problem.
Falsematrix_solverCallable[..., ArrayLike]Called as
matrix_solver(A, b, **matrix_solver_opts) -> x, whereA:scipy.sparse.csr_array;b:ArrayLike;x:ArrayLike; Default is a wrapped version ofscipy.sparse.linalg.qmr()which doesn't return convergence info and logs the residual every 100 iterations._scipy_qmrmatrix_solver_optsdict[str, Any] | NonePassed as kwargs to
matrix_solver(...)NoneE_guessvcfdfield | NoneGuess at the solution E-field.
matrix_solvermust accept anx0argument with the same purpose.NoneReturns:
Type Descriptionvcfdfield_tE-field which solves the system.
"},{"location":"api/fdfd/#meanas.fdfd.scpml","title":"meanas.fdfd.scpml","text":"Functions for creating stretched coordinate perfectly matched layer (PML) absorbers.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.s_function_t","title":"s_function_tmodule-attribute","text":"s_function_t = Callable[\n [NDArray[float64]], NDArray[float64]\n]\nTypedef for s-functions, see
"},{"location":"api/fdfd/#meanas.fdfd.scpml.prepare_s_function","title":"prepare_s_function","text":"prepare_s_function()prepare_s_function(\n ln_R: float = -16, m: float = 4\n) -> s_function_t\nCreate an s_function to pass to the SCPML functions. This is used when you would like to customize the PML parameters.
Parameters:
Name Type Description Defaultln_RfloatNatural logarithm of the desired reflectance
-16mfloatPolynomial order for the PML (imaginary part increases as distance ** m)
4Returns:
Type Descriptions_function_tAn s_function, which takes an ndarray (distances) and returns an ndarray (complex part
s_function_tof the cell width; needs to be divided by
sqrt(epilon_effective) * real(omega))s_function_tbefore use.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.uniform_grid_scpml","title":"uniform_grid_scpml","text":"uniform_grid_scpml(\n shape: Sequence[int],\n thicknesses: Sequence[int],\n omega: float,\n epsilon_effective: float = 1.0,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\nCreate dx arrays for a uniform grid with a cell width of 1 and a pml.
If you want something more fine-grained, check out
stretch_with_scpml(...).Parameters:
Name Type Description DefaultshapeSequence[int]Shape of the grid, including the PMLs (which are 2*thicknesses thick)
requiredthicknessesSequence[int]required
[th_x, th_y, th_z]Thickness of the PML in each direction. Both polarities are added. Each th_ of pml is applied twice, once on each edge of the grid along the given axis.th_*may be zero, in which case no pml is added.omegafloatAngular frequency for the simulation
requiredepsilon_effectivefloatEffective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0s_functions_function_t | Nonecreated by
prepare_s_function(...), allowing customization of pml parameters. Default usesprepare_s_function()with no parameters.NoneReturns:
Type Descriptionlist[list[NDArray[float64]]]Complex cell widths (dx_lists_mut) as discussed in
"},{"location":"api/fdfd/#meanas.fdfd.scpml.stretch_with_scpml","title":"stretch_with_scpml","text":"meanas.fdmath.types.stretch_with_scpml(\n dxes: list[list[NDArray[float64]]],\n axis: int,\n polarity: int,\n omega: float,\n epsilon_effective: float = 1.0,\n thickness: int = 10,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\nStretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis.
Parameters:
Name Type Description Defaultdxeslist[list[NDArray[float64]]]Grid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintaxis to stretch (0=x, 1=y, 2=z)
requiredpolarityintdirection to stretch (-1 for -ve, +1 for +ve)
requiredomegafloatAngular frequency for the simulation
requiredepsilon_effectivefloatEffective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0thicknessintnumber of cells to use for pml (default 10)
10s_functions_function_t | NoneCreated by
prepare_s_function(...), allowing customization of pml parameters. Default usesprepare_s_function()with no parameters.NoneReturns:
Type Descriptionlist[list[NDArray[float64]]]Complex cell widths (dx_lists_mut) as discussed in
meanas.fdmath.types.list[list[NDArray[float64]]]Multiple calls to this function may be necessary if multiple absorpbing boundaries are needed.
"},{"location":"api/fdfd/#meanas.fdfd.farfield","title":"meanas.fdfd.farfield","text":"Functions for performing near-to-farfield transformation (and the reverse).
"},{"location":"api/fdfd/#meanas.fdfd.farfield.near_to_farfield","title":"near_to_farfield","text":"near_to_farfield(\n E_near: cfdfield_t,\n H_near: cfdfield_t,\n dx: float,\n dy: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\nCompute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_nearcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse E fields (e.g. [Ex, Ey] for calculating the farfield toward the z-direction).
requiredH_nearcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse H fields (e.g. [Hx, hy] for calculating the farfield towrad the z-direction).
requireddxfloatCell size along x-dimension, in units of wavelength.
requireddyfloatCell size along y-dimension, in units of wavelength.
requiredpadded_sizelist[int] | int | NoneShape of the output. A single integer
nwill be expanded to(n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.NoneReturns:
Type Descriptiondict[str, Any]Dict with keys
dict[str, Any]
E_far: Normalized E-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any]
H_far: Normalized H-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any]
kx,ky: Wavevector values corresponding to the x- and y- axes in E_far and H_far, normalized to wavelength (dimensionless).dict[str, Any]
dkx,dky: step size for kx and ky, normalized to wavelength.dict[str, Any]
theta: arctan2(ky, kx) corresponding to each (kx, ky). This is the angle in the x-y plane, counterclockwise from above, starting from +x.dict[str, Any]"},{"location":"api/fdfd/#meanas.fdfd.farfield.far_to_nearfield","title":"far_to_nearfield","text":"
phi: arccos(kz / k) corresponding to each (kx, ky). This is the angle away from +z.far_to_nearfield(\n E_far: cfdfield_t,\n H_far: cfdfield_t,\n dkx: float,\n dky: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\nCompute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_farcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse E fields (e.g. [Ex, Ey] for calculating the nearfield toward the z-direction). Fields should be normalized so that E_far = E_far_actual / (i k exp(-i k r) / (4 pi r))
requiredH_farcfdfield_tList of 2 ndarrays containing the 2D phasor field slices for the transverse H fields (e.g. [Hx, hy] for calculating the nearfield toward the z-direction). Fields should be normalized so that H_far = H_far_actual / (i k exp(-i k r) / (4 pi r))
requireddkxfloatkx discretization, in units of wavelength.
requireddkyfloatky discretization, in units of wavelength.
requiredpadded_sizelist[int] | int | NoneShape of the output. A single integer
nwill be expanded to(n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.NoneReturns:
Type Descriptiondict[str, Any]Dict with keys
dict[str, Any]
E: E-field nearfielddict[str, Any]
H: H-field nearfielddict[str, Any]"},{"location":"api/fdmath/","title":"fdmath","text":""},{"location":"api/fdmath/#meanas.fdmath","title":"meanas.fdmath","text":"
dx,dy: spatial discretization, normalized to wavelength (dimensionless)Basic discrete calculus for finite difference (fd) simulations.
"},{"location":"api/fdmath/#meanas.fdmath--fields-functions-and-operators","title":"Fields, Functions, and Operators","text":"Discrete fields are stored in one of two forms:
Operators which act on fields also come in two forms
- The
fdfield_tform is a multidimensionalnumpy.NDArray
- For a scalar field, this is just
U[m, n, p], wherem,n, andpare discrete indices referring to positions on the x, y, and z axes respectively.- For a vector field, the first index specifies which vector component is accessed:
E[:, m, n, p] = [Ex[m, n, p], Ey[m, n, p], Ez[m, n, p]].- The
vfdfield_tform is simply a vectorzied (i.e. 1D) version of thefdfield_t, as obtained bymeanas.fdmath.vectorization.vec(effectively justnumpy.ravel)
- Python functions, created by the functions in
meanas.fdmath.functional. The generated functions act on fields in thefdfield_tform.- Linear operators, usually 2D sparse matrices using
scipy.sparse, created bymeanas.fdmath.operators. These operators act on vectorized fields in thevfdfield_tform.The operations performed should be equivalent:
functional.op(*args)(E)should be equivalent tounvec(operators.op(*args) @ vec(E), E.shape[1:]).Generally speaking the
"},{"location":"api/fdmath/#meanas.fdmath--discrete-calculus","title":"Discrete calculus","text":"field_tform is easier to work with, but can be harder or less efficient to compose (e.g. it is easy to generate a single matrix by multiplying a series of other matrices).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.
"},{"location":"api/fdmath/#meanas.fdmath--scalar-derivatives-and-cell-shifts","title":"Scalar derivatives and cell shifts","text":"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
fas a 1D array of values, with thei-th valuef[i]taking up a lengthdx[i]along the x-axis, the forward derivative isderiv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]\nLikewise, 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]\nThe 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]\n dx0 dx1 dx2 dx3 cell sizes for function\n ----- ----- ----------- -----\n ______________________________\n | | | |\n f0 | f1 | f2 | f3 | function\n _____|_____|___________|_____|\n | | | |\n | Df0 | Df1 | Df2 | Df3 forward derivative (periodic boundary)\n __|_____|________|________|___\n\n dx'3] dx'0 dx'1 dx'2 [dx'3 cell sizes for forward derivative\n -- ----- -------- -------- ---\n dx'0] dx'1 dx'2 dx'3 [dx'0 cell sizes for reverse derivative\n ______________________________\n | | | |\n | df1 | df2 | df3 | df0 reverse derivative (periodic boundary)\n __|_____|________|________|___\n\nPeriodic boundaries are used here and elsewhere unless otherwise noted.\nIn 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. If the positions labeled with \\(m\\) are considered the \"base\" or \"original\" grid, the positions labeled with \\(m + \\frac{1}{2}\\) are said to lie on a \"dual\" or \"derived\" grid.
For the remainder of the
"},{"location":"api/fdmath/#meanas.fdmath--gradients-and-fore-vectors","title":"Gradients and fore-vectors","text":"Discrete calculussection, all figures will show constant-length cells in order to focus on the vector derivatives themselves. See theGrid descriptionsection below for additional information on this topic and generalization to three dimensions.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]\ngrad_forward(f)[i,j,k] = [Dx_forward(f)[i, j, k],\n Dy_forward(f)[i, j, k],\n Dz_forward(f)[i, j, k]]\n = [(f[i + 1, j, k] - f[i, j, k]) / dx[i],\n (f[i, j + 1, k] - f[i, j, k]) / dy[i],\n (f[i, j, k + 1] - f[i, j, k]) / dz[i]]\n\ngrad_back(f)[i,j,k] = [Dx_back(f)[i, j, k],\n Dy_back(f)[i, j, k],\n Dz_back(f)[i, j, k]]\n = [(f[i, j, k] - f[i - 1, j, k]) / dx[i],\n (f[i, j, k] - f[i, j - 1, k]) / dy[i],\n (f[i, j, k] - f[i, j, k - 1]) / dz[i]]\nThe 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}} \\]"},{"location":"api/fdmath/#meanas.fdmath--divergences","title":"Divergences","text":"[figure: gradient / fore-vector]\n (m, n+1, p+1) ______________ (m+1, n+1, p+1)\n /: /|\n / : / |\n / : / |\n (m, n, p+1)/_____________/ | The forward derivatives are defined\n | : | | at the Dx, Dy, Dz points,\n | :.........|...| but the forward-gradient fore-vector\n z y Dz / | / is the set of all three\n |/_x | Dy | / and is said to be \"located\" at (m,n,p)\n |/ |/\n (m, n, p)|_____Dx______| (m+1, n, p)\nThere 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]\ndiv_forward(g)[i,j,k] = Dx_forward(gx)[i, j, k] +\n Dy_forward(gy)[i, j, k] +\n Dz_forward(gz)[i, j, k]\n = (gx[i + 1, j, k] - gx[i, j, k]) / dx[i] +\n (gy[i, j + 1, k] - gy[i, j, k]) / dy[i] +\n (gz[i, j, k + 1] - gz[i, j, k]) / dz[i]\n\ndiv_back(g)[i,j,k] = Dx_back(gx)[i, j, k] +\n Dy_back(gy)[i, j, k] +\n Dz_back(gz)[i, j, k]\n = (gx[i, j, k] - gx[i - 1, j, k]) / dx[i] +\n (gy[i, j, k] - gy[i, j - 1, k]) / dy[i] +\n (gz[i, j, k] - gz[i, j, k - 1]) / dz[i]\nwhere
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-vector's) location \\((m,n,p)\\) and not at the locations of its components \\((m \\pm \\frac{1}{2},n,p)\\) etc.
"},{"location":"api/fdmath/#meanas.fdmath--curls","title":"Curls","text":"[figure: divergence]\n ^^\n (m-1/2, n+1/2, p+1/2) _____||_______ (m+1/2, n+1/2, p+1/2)\n /: || ,, /|\n / : || // / | The divergence at (m, n, p) (the center\n / : // / | of this cube) of a fore-vector field\n (m-1/2, n-1/2, p+1/2)/_____________/ | is the sum of the outward-pointing\n | : | | fore-vector components, which are\n z y <==|== :.........|.====> located at the face centers.\n |/_x | / | /\n | / // | / Note that in a nonuniform grid, each\n |/ // || |/ dimension is normalized by the cell width.\n (m-1/2, n-1/2, p-1/2)|____//_______| (m+1/2, n-1/2, p-1/2)\n '' ||\n VV\nThe two curls are then
\\[ \\begin{aligned} \\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{aligned} \\]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]\ncurl_forward(g)[i,j,k] = [Dy_forward(gz)[i, j, k] - Dz_forward(gy)[i, j, k],\n Dz_forward(gx)[i, j, k] - Dx_forward(gz)[i, j, k],\n Dx_forward(gy)[i, j, k] - Dy_forward(gx)[i, j, k]]\n\ncurl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],\n Dz_back(gx)[i, j, k] - Dx_back(gz)[i, j, k],\n Dx_back(gy)[i, j, k] - Dy_back(gx)[i, j, k]]\nFor 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 ofgin the xy plane where the curl's z-component is located; these are[curl components]\n(m, n + 1/2, p + 1/2) : x-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1, n + 1/2, p + 1/2) : x-component of back-vector at (m + 3/2, n + 1/2, p + 1/2)\n(m + 1/2, n , p + 1/2) : y-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1/2, n + 1 , p + 1/2) : y-component of back-vector at (m + 1/2, n + 3/2, p + 1/2)\nThese 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).
"},{"location":"api/fdmath/#meanas.fdmath--maxwells-equations","title":"Maxwell's Equations","text":"[figure: z-component of curl]\n : |\n z y : ^^ |\n |/_x :....||.<.....| (m+1, n+1, p+1/2)\n / || /\n | v || | ^\n |/ |/\n (m, n, p+1/2) |_____>______| (m+1, n, p+1/2)\nIf we discretize both space (m,n,p) and time (l), Maxwell's equations become
\\[ \\begin{aligned} \\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, \\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{l-\\frac{1}{2},\\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{aligned} \\]with
\\[ \\begin{aligned} \\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{aligned} \\]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 updates]\nH[i, j, k] -= dt * (curl_forward(E)[i, j, k] + M[t, i, j, k]) / mu[i, j, k]\nE[i, j, k] += dt * (curl_back( H)[i, j, k] + J[t, i, j, k]) / epsilon[i, j, k]\nNote 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: Field components]\n\n (m - 1/2,=> ____________Hx__________[H] <= r + 1/2 = (m + 1/2,\n n + 1/2, /: /: /| n + 1/2,\n z y p + 1/2) / : / : / | p + 1/2)\n |/_x / : / : / |\n / : Ez__________Hy | Locations of the E- and\n / : : : /| | H-field components for the\n (m - 1/2, / : : Ey...../.|..Hz [E] fore-vector at r = (m,n,p)\n n - 1/2, =>/________________________/ | /| (the large cube's center)\n p + 1/2) | : : / | | / | and [H] back-vector at r + 1/2\n | : :/ | |/ | (the top right corner)\n | : [E].......|.Ex |\n | :.................|......| <= (m + 1/2, n + 1/2, p + 1/2)\n | / | /\n | / | /\n | / | / This is the Yee discretization\n | / | / scheme (\"Yee cell\").\nr - 1/2 = | / | /\n (m - 1/2, |/ |/\n n - 1/2,=> |________________________| <= (m + 1/2, n - 1/2, p - 1/2)\n p - 1/2)\nEach component forms its own grid, offset from the others:
[figure: E-fields for adjacent cells]\n\n H1__________Hx0_________H0\n z y /: /|\n |/_x / : / | This figure shows H back-vector locations\n / : / | H0, H1, etc. and their associated components\n Hy1 : Hy0 | H0 = (Hx0, Hy0, Hz0) etc.\n / : / |\n / Hz1 / Hz0\n H2___________Hx3_________H3 | The equivalent drawing for E would have\n | : | | fore-vectors located at the cube's\n | : | | center (and the centers of adjacent cubes),\n | : | | with components on the cube's faces.\n | H5..........Hx4...|......H4\n | / | /\n Hz2 / Hz2 /\n | / | /\n | Hy6 | Hy4\n | / | /\n |/ |/\n H6__________Hx7__________H7\nThe 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.
"},{"location":"api/fdmath/#meanas.fdmath--wave-equation","title":"Wave equation","text":"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{aligned} \\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{aligned} \\]"},{"location":"api/fdmath/#meanas.fdmath--frequency-domain","title":"Frequency domain","text":"We can substitute in a time-harmonic fields
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &= \\tilde{E}_{\\vec{r}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\tilde{J}_{l, \\vec{r}} &= \\tilde{J}_{\\vec{r}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\end{aligned} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow (e^{ \\imath \\omega \\Delta_t} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{-\\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow (1 - e^{-\\imath \\omega \\Delta_t}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{ \\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\Omega &= 2 \\sin(\\omega \\Delta_t / 2) / \\Delta_t \\end{aligned} \\]This gives the frequency-domain wave equation,
\\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\Omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\Omega \\tilde{J}_{\\vec{r}} e^{\\imath \\omega \\Delta_t / 2} \\\\ \\]"},{"location":"api/fdmath/#meanas.fdmath--plane-waves-and-dispersion-relation","title":"Plane waves and Dispersion relation","text":"With uniform material distribution and no sources
\\[ \\begin{aligned} \\mu_{\\vec{r} + \\frac{1}{2}} &= \\mu \\\\ \\epsilon_{\\vec{r}} &= \\epsilon \\\\ \\tilde{J}_{\\vec{r}} &= 0 \\\\ \\end{aligned} \\]the frequency domain wave equation simplifies to
\\[ \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} - \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]Since \\(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}} = 0\\), we can simplify
\\[ \\begin{aligned} \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\tilde{\\nabla}(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}}) - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} \\end{aligned} \\]and we get
\\[ \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} + \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]We can convert this to three scalar-wave equations of the form
\\[ (\\tilde{\\nabla}^2 + K^2) \\phi_{\\vec{r}} = 0 \\]with \\(K^2 = \\Omega^2 \\mu \\epsilon\\). Now we let
\\[ \\phi_{\\vec{r}} = A e^{\\imath (k_x m \\Delta_x + k_y n \\Delta_y + k_z p \\Delta_z)} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_x &\\Rightarrow (e^{ \\imath k_x \\Delta_x} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{ \\imath k_x \\Delta_x / 2} = \\imath K_x e^{ \\imath k_x \\Delta_x / 2}\\\\ \\hat{\\partial}_x &\\Rightarrow (1 - e^{-\\imath k_x \\Delta_x}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{-\\imath k_x \\Delta_x / 2} = \\imath K_x e^{-\\imath k_x \\Delta_x / 2}\\\\ K_x &= 2 \\sin(k_x \\Delta_x / 2) / \\Delta_x \\\\ \\end{aligned} \\]with similar expressions for the y and z dimnsions (and \\(K_y, K_z\\)).
This implies
\\[ \\tilde{\\nabla}^2 = -(K_x^2 + K_y^2 + K_z^2) \\phi_{\\vec{r}} \\\\ K_x^2 + K_y^2 + K_z^2 = \\Omega^2 \\mu \\epsilon = \\Omega^2 / c^2 \\]where \\(c = \\sqrt{\\mu \\epsilon}\\).
Assuming real \\((k_x, k_y, k_z), \\omega\\) will be real only if
\\[ c^2 \\Delta_t^2 = \\frac{\\Delta_t^2}{\\mu \\epsilon} < 1/(\\frac{1}{\\Delta_x^2} + \\frac{1}{\\Delta_y^2} + \\frac{1}{\\Delta_z^2}) \\]If \\(\\Delta_x = \\Delta_y = \\Delta_z\\), this simplifies to \\(c \\Delta_t < \\Delta_x / \\sqrt{3}\\). This last form can be interpreted as enforcing causality; the distance that light travels in one timestep (i.e., \\(c \\Delta_t\\)) must be less than the diagonal of the smallest cell ( \\(\\Delta_x / \\sqrt{3}\\) when on a uniform cubic grid).
"},{"location":"api/fdmath/#meanas.fdmath--grid-description","title":"Grid description","text":"As described in the section on scalar discrete derivatives above, cell widths (
dx[i],dy[j],dz[k]) along each axis can be arbitrary and independently defined. Moreover, all field components are actually defined at \"derived\" or \"dual\" positions, in-between the \"base\" grid points on one or more axes.To get a better sense of how this works, let's start by drawing a grid with uniform
dyanddzand nonuniformdx. We will only draw one cell in the y and z dimensions to make the illustration simpler; we need at least two cells in the x dimension to demonstrate how nonuniformdxaffects the various components.Place the E fore-vectors at integer indices \\(r = (m, n, p)\\) and the H back-vectors at fractional indices \\(r + \\frac{1}{2} = (m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2})\\). Remember that these are indices and not coordinates; they can correspond to arbitrary (monotonically increasing) coordinates depending on the cell widths.
Draw lines to denote the planes on which the H components and back-vectors are defined. For simplicity, don't draw the equivalent planes for the E components and fore-vectors, except as necessary to show their locations -- it's easiest to just connect them to their associated H-equivalents.
The result looks something like this:
[figure: Component centers]\n p=\n [H]__________Hx___________[H]_____Hx______[H] __ +1/2\n z y /: /: /: /: /| | |\n |/_x / : / : / : / : / | | |\n / : / : / : / : / | | |\n Hy : Ez...........Hy : Ez......Hy | | |\n /: : : : /: : : : /| | | |\n / : Hz : Ey....../.:..Hz : Ey./.|..Hz __ 0 | dz[0]\n / : /: : / / : /: : / / | /| | |\n /_________________________/_______________/ | / | | |\n | :/ : :/ | :/ : :/ | |/ | | |\n | Ex : [E].......|..Ex : [E]..|..Ex | | |\n | : | : | | | |\n | [H]..........Hx....|......[H].....H|x.....[H] __ --------- (n=+1/2, p=-1/2)\n | / | / | / / /\n Hz / Hz / Hz / / /\n | / | / | / / /\n | Hy | Hy | Hy __ 0 / dy[0]\n | / | / | / / /\n | / | / | / / /\n |/ |/ |/ / /\n [H]__________Hx___________[H]_____Hx______[H] __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ------------------------- ----------------\n dx[0] dx[1]\n\n Part of a nonuniform \"base grid\", with labels specifying\n positions of the various field components. [E] fore-vectors\n are at the cell centers, and [H] back-vectors are at the\n vertices. H components along the near (-y) top (+z) edge\n have been omitted to make the insides of the cubes easier\n to visualize.\nThe above figure shows where all the components are located; however, it is also useful to show what volumes those components correspond to. Consider the Ex component at
m = +1/2: it is shifted in the x-direction by a half-cell from the E fore-vector atm = 0(labeled[E]in the figure). It corresponds to a volume betweenm = 0andm = +1(the other dimensions are not shifted, i.e. they are still bounded byn, p = +-1/2). (See figure below). Sincemis an index and not an x-coordinate, the Ex component is not necessarily at the center of the volume it represents, and the x-length of its volume is the derived quantitydx'[0] = (dx[0] + dx[1]) / 2rather than the basedx. (See alsoScalar derivatives and cell shifts).[figure: Ex volumes]\n p=\n <_________________________________________> __ +1/2\n z y << /: / /: >> | |\n |/_x < < / : / / : > > | |\n < < / : / / : > > | |\n < < / : / / : > > | |\n <: < / : : / : >: > | |\n < : < / : : / : > : > __ 0 | dz[0]\n < : < / : : / :> : > | |\n <____________/____________________/_______> : > | |\n < : < | : : | > : > | |\n < Ex < | : Ex | > Ex > | |\n < : < | : : | > : > | |\n < : <....|.......:........:...|.......>...:...> __ --------- (n=+1/2, p=-1/2)\n < : < | / : /| /> : > / /\n < : < | / : / | / > : > / /\n < :< | / :/ | / > :> / /\n < < | / : | / > > _ 0 / dy[0]\n < < | / | / > > / /\n < < | / | / > > / /\n << |/ |/ >> / /\n <____________|____________________|_______> __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ -------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Ex values are positioned on the x-faces of the base\n grid. They represent the Ex field in volumes shifted by\n a half-cell in the x-dimension, as shown here. Only the\n center cell (with width dx'[0]) is fully shown; the\n other two are truncated (shown using >< markers).\n\n Note that the Ex positions are the in the same positions\n as the previous figure; only the cell boundaries have moved.\n Also note that the points at which Ex is defined are not\n necessarily centered in the volumes they represent; non-\n uniform cell sizes result in off-center volumes like the\n center cell here.\nThe next figure shows the volumes corresponding to the Hy components, which are shifted in two dimensions (x and z) compared to the base grid.
"},{"location":"api/fdmath/#meanas.fdmath--datastructure-dx_lists_t","title":"Datastructure: dx_lists_t","text":"[figure: Hy volumes]\n p=\n z y mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm __ +1/2 s\n |/_x << m: m: >> | |\n < < m : m : > > | | dz'[1]\n < < m : m : > > | |\n Hy........... m........Hy...........m......Hy > | |\n < < m : m : > > | |\n < ______ m_____:_______________m_____:_>______ __ 0\n < < m /: m / > > | |\n mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm > | |\n < < | / : | / > > | | dz'[0]\n < < | / : | / > > | |\n < < | / : | / > > | |\n < wwwww|w/wwwwwwwwwwwwwwwwwww|w/wwwww>wwwwwwww __ s\n < < |/ w |/ w> > / /\n _____________|_____________________|________ > / /\n < < | w | w > > / /\n < Hy........|...w........Hy.......|...w...>..Hy _ 0 / dy[0]\n < < | w | w > > / /\n << | w | w > > / /\n < |w |w >> / /\n wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww __ -1/2 /\n\n |------------|------------|--------|-------|\n-1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ --------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Hy values are positioned on the y-edges of the base\n grid. Again here, the 'Hy' labels represent the same points\n as in the basic grid figure above; the edges have shifted\n by a half-cell along the x- and z-axes.\n\n The grid lines _|:/ are edges of the area represented by\n each Hy value, and the lines drawn using <m>.w represent\n edges where a cell's faces extend beyond the drawn area\n (i.e. where the drawing is truncated in the x- or z-\n directions).\nIn this documentation, the E fore-vectors are placed on the base grid. An equivalent formulation could place the H back-vectors on the base grid instead. However, in the case of a non-uniform grid, the operation to get from the \"base\" cell widths to \"derived\" ones is not its own inverse.
The base grid's cell sizes could be fully described by a list of three 1D arrays, specifying the cell widths along all three axes:
[dx, dy, dz] = [[dx[0], dx[1], ...], [dy[0], ...], [dz[0], ...]]\nNote that this is a list-of-arrays rather than a 2D array, as the simulation domain may have a different number of cells along each axis.
Knowing the base grid's cell widths and the boundary conditions (periodic unless otherwise noted) is enough information to calculate the cell widths
dx',dy', anddz'for the derived grids.However, since most operations are trivially generalized to allow either E or H to be defined on the base grid, they are written to take the a full set of base and derived cell widths, distinguished by which field they apply to rather than their \"base\" or \"derived\" status. This removes the need for each function to generate the derived widths, and makes the \"base\" vs \"derived\" distinction unnecessary in the code.
The resulting data structure containing all the cell widths takes the form of a list-of-lists-of-arrays. The first list-of-arrays provides the cell widths for the E-field fore-vectors, while the second list-of-arrays does the same for the H-field back-vectors:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath--permittivity-and-permeability","title":"Permittivity and Permeability","text":"dx_e[0]is the x-width of them=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of then=0cells, as used when calculating dH/dy, etc.Since each vector component of E and H is defined in a different location and represents a different volume, the value of the spatially-discrete
epsilonandmucan also be different for all three field components, even when representing a simple planar interface between two isotropic materials.As a result,
epsilonandmuare taken to have the same dimensions as the field, and composed of the three diagonal tensor components:[equations: epsilon_and_mu]\nepsilon = [epsilon_xx, epsilon_yy, epsilon_zz]\nmu = [mu_xx, mu_yy, mu_zz]\nor
\\[ \\epsilon = \\begin{bmatrix} \\epsilon_{xx} & 0 & 0 \\\\ 0 & \\epsilon_{yy} & 0 \\\\ 0 & 0 & \\epsilon_{zz} \\end{bmatrix} \\] \\[ \\mu = \\begin{bmatrix} \\mu_{xx} & 0 & 0 \\\\ 0 & \\mu_{yy} & 0 \\\\ 0 & 0 & \\mu_{zz} \\end{bmatrix} \\]where the off-diagonal terms (e.g.
epsilon_xy) are assumed to be zero.High-accuracy volumetric integration of shapes on multiple grids can be performed by the gridlock module.
The values of the vacuum permittivity and permability effectively become scaling factors that appear in several locations (e.g. between the E and H fields). In order to limit floating-point inaccuracy and simplify calculations, they are often set to 1 and relative permittivities and permeabilities are used in their places; the true values can be multiplied back in after the simulation is complete if non- normalized results are needed.
"},{"location":"api/fdmath/#functional-and-sparse-operators","title":"Functional and sparse operators","text":""},{"location":"api/fdmath/#meanas.fdmath.functional","title":"meanas.fdmath.functional","text":"Math functions for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\nUtility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\nUtility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\nCurl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type DescriptionCallable[[TT], TT]Function
ffor taking the discrete forward curl of a field,Callable[[TT], TT]"},{"location":"api/fdmath/#meanas.fdmath.functional.curl_back","title":"curl_back","text":"
f(E)-> curlE \\(= \\nabla_f \\times E\\)curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\nCreate a function which takes the backward curl of a field.
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]] | NoneLists of cell sizes for all axes
[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].NoneReturns:
Type DescriptionCallable[[TT], TT]Function
ffor taking the discrete backward curl of a field,Callable[[TT], TT]"},{"location":"api/fdmath/#meanas.fdmath.operators","title":"meanas.fdmath.operators","text":"
f(H)-> curlH \\(= \\nabla_b \\times H\\)Matrix operators for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_circ","title":"shift_circ","text":"shift_circ(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\nUtility operator for performing a circular shift along a specified axis by a specified number of elements.
Parameters:
Name Type Description DefaultaxisintAxis to shift along. x=0, y=1, z=2
requiredshapeSequence[int]Shape of the grid being shifted
requiredshift_distanceintNumber of cells to shift by. May be negative. Default 1.
1Returns:
Type DescriptionsparraySparse matrix for performing the circular shift.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_with_mirror","title":"shift_with_mirror","text":"shift_with_mirror(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\nUtility operator for performing an n-element shift along a specified axis, with mirror boundary conditions applied to the cells beyond the receding edge.
Parameters:
Name Type Description DefaultaxisintAxis to shift along. x=0, y=1, z=2
requiredshapeSequence[int]Shape of the grid being shifted
requiredshift_distanceintNumber of cells to shift by. May be negative. Default 1.
1Returns:
Type DescriptionsparraySparse matrix for performing the shift-with-mirror.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\nUtility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type Descriptionlist[sparray]List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\nUtility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type Descriptionlist[sparray]List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.cross","title":"cross","text":"cross(B: Sequence[sparray]) -> sparse.sparray\nCross product operator
Parameters:
Name Type Description DefaultBSequence[sparray]List
required[Bx, By, Bz]of sparse matrices corresponding to the x, y, z portions of the operator on the left side of the cross product.Returns:
Type DescriptionsparraySparse matrix corresponding to (B x), where x is the cross product.
"},{"location":"api/fdmath/#meanas.fdmath.operators.vec_cross","title":"vec_cross","text":"vec_cross(b: vfdfield_t) -> sparse.sparray\nVector cross product operator
Parameters:
Name Type Description Defaultbvfdfield_tVector on the left side of the cross product.
requiredReturns:
"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_forward","title":"avg_forward","text":"Sparse matrix corresponding to (b x), where x is the cross product.\navg_forward(\n axis: int, shape: Sequence[int]\n) -> sparse.sparray\nForward average operator
(x4 = (x4 + x5) / 2)Parameters:
Name Type Description DefaultaxisintAxis to average along (x=0, y=1, z=2)
requiredshapeSequence[int]Shape of the grid to average
requiredReturns:
Type DescriptionsparraySparse matrix for forward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_back","title":"avg_back","text":"avg_back(axis: int, shape: Sequence[int]) -> sparse.sparray\nBackward average operator
(x4 = (x4 + x3) / 2)Parameters:
Name Type Description DefaultaxisintAxis to average along (x=0, y=1, z=2)
requiredshapeSequence[int]Shape of the grid to average
requiredReturns:
Type DescriptionsparraySparse matrix for backward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\nCurl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_eSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type DescriptionsparraySparse matrix for taking the discretized curl of the E-field
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_back","title":"curl_back","text":"curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\nCurl operator for use with the H field.
Parameters:
Name Type Description Defaultdx_hSequence[NDArray[floating | complexfloating]]Lists of cell sizes for all axes
required[[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].Returns:
Type DescriptionsparraySparse matrix for taking the discretized curl of the H-field
"},{"location":"api/fdmath/#meanas.fdmath.vectorization","title":"meanas.fdmath.vectorization","text":"Functions for moving between a vector field (list of 3 ndarrays,
"},{"location":"api/fdmath/#meanas.fdmath.vectorization.vec","title":"vec","text":"[f_x, f_y, f_z]) and a 1D array representation of that field[f_x0, f_x1, f_x2,... f_y0,... f_z0,...]. Vectorized versions of the field use row-major (ie., C-style) ordering.vec(f: None) -> None\nvec(f: fdfield_t) -> vfdfield_t\nvec(f: cfdfield_t) -> vcfdfield_t\nvec(f: fdfield2_t) -> vfdfield2_t\nvec(f: cfdfield2_t) -> vcfdfield2_t\nvec(f: fdslice_t) -> vfdslice_t\nvec(f: cfdslice_t) -> vcfdslice_t\nvec(f: ArrayLike) -> NDArray\nvec(\n f: fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | ArrayLike\n | None,\n) -> (\n vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | NDArray\n | None\n)\nCreate a 1D ndarray from a vector field which spans a 1-3D region.
Returns
Noneif called withf=None.Parameters:
Name Type Description Defaultffdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | NoneA vector field, e.g.
required[f_x, f_y, f_z]where eachf_component is a 1- to 3-D ndarray (f_*should all be the same size). Doesn't fail withf=None.Returns:
Type Descriptionvfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | NDArray | None1D ndarray containing the linearized field (or
"},{"location":"api/fdmath/#meanas.fdmath.vectorization.unvec","title":"unvec","text":"None)unvec(\n v: None, shape: Sequence[int], nvdim: int = 3\n) -> None\nunvec(\n v: vfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield_t\nunvec(\n v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield_t\nunvec(\n v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield2_t\nunvec(\n v: vcfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield2_t\nunvec(\n v: vfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> fdslice_t\nunvec(\n v: vcfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdslice_t\nunvec(\n v: ArrayLike, shape: Sequence[int], nvdim: int = 3\n) -> NDArray\nunvec(\n v: vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | ArrayLike\n | None,\n shape: Sequence[int],\n nvdim: int = 3,\n) -> (\n fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | NDArray\n | None\n)\nPerform the inverse of vec(): take a 1D ndarray and output an
nvdim-component field of form e.g.[f_x, f_y, f_z](nvdim=3) where each off_*is a len(shape)-dimensional ndarray.Returns
Noneif called withv=None.Parameters:
Name Type Description Defaultvvfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | ArrayLike | None1D ndarray representing a vector field of shape shape (or None)
requiredshapeSequence[int]shape of the vector field
requirednvdimintNumber of components in each vector
3Returns:
Type Descriptionfdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | NDArray | None"},{"location":"api/fdmath/#meanas.fdmath.types","title":"meanas.fdmath.types","text":"
[f_x, f_y, f_z]where eachf_is alen(shape)dimensional ndarray (orNone)Types shared across multiple submodules
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists_t","title":"dx_lists_tmodule-attribute","text":"dx_lists_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists2_t","title":"dx_lists2_tdx_e[0]is the x-width of thex=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of they=0cells, as used when calculating dH/dy, etc.module-attribute","text":"dx_lists2_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n2D 'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...]]]\nwhere
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists_mut","title":"dx_lists_mutdx_e[0]is the x-width of thex=0cells, as used when calculating dE/dx, anddy_h[0]is the y-width of they=0cells, as used when calculating dH/dy, etc.module-attribute","text":"dx_lists_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\nMutable version of
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists2_mut","title":"dx_lists2_mutdx_lists_tmodule-attribute","text":"dx_lists2_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\nMutable version of
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield_updater_t","title":"fdfield_updater_tdx_lists2_tmodule-attribute","text":"fdfield_updater_t = Callable[..., fdfield_t]\nConvenience type for functions which take and return an fdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield_updater_t","title":"cfdfield_updater_tmodule-attribute","text":"cfdfield_updater_t = Callable[..., cfdfield_t]\nConvenience type for functions which take and return an cfdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield","title":"fdfield","text":"fdfield = fdfield_t | NDArray[floating]\nVector field with shape (3, X, Y, Z) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdfield","title":"vfdfield","text":"[E_x, E_y, E_z])vfdfield = vfdfield_t | NDArray[floating]\nLinearized vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield","title":"cfdfield","text":"cfdfield = cfdfield_t | NDArray[complexfloating]\nComplex vector field with shape (3, X, Y, Z) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdfield","title":"vcfdfield","text":"[E_x, E_y, E_z])vcfdfield = vcfdfield_t | NDArray[complexfloating]\nLinearized complex vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdslice","title":"fdslice","text":"fdslice = fdslice_t | NDArray[floating]\nVector field slice with shape (3, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdslice","title":"vfdslice","text":"[E_x, E_y, E_z]at a single Z position)vfdslice = vfdslice_t | NDArray[floating]\nLinearized vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdslice","title":"cfdslice","text":"cfdslice = cfdslice_t | NDArray[complexfloating]\nComplex vector field slice with shape (3, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdslice","title":"vcfdslice","text":"[E_x, E_y, E_z]at a single Z position)vcfdslice = vcfdslice_t | NDArray[complexfloating]\nLinearized complex vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield2","title":"fdfield2","text":"fdfield2 = fdfield2_t | NDArray[floating]\n2D Vector field with shape (2, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vfdfield2","title":"vfdfield2","text":"[E_x, E_y])vfdfield2 = vfdfield2_t | NDArray[floating]\n2D Linearized vector field (single vector of length 2XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield2","title":"cfdfield2","text":"cfdfield2 = cfdfield2_t | NDArray[complexfloating]\n2D Complex vector field with shape (2, X, Y) (e.g.
"},{"location":"api/fdmath/#meanas.fdmath.types.vcfdfield2","title":"vcfdfield2","text":"[E_x, E_y])vcfdfield2 = vcfdfield2_t | NDArray[complexfloating]\n2D Linearized complex vector field (single vector of length 2XY)
"},{"location":"api/fdtd/","title":"fdtd","text":""},{"location":"api/fdtd/#meanas.fdtd","title":"meanas.fdtd","text":"Utilities for running finite-difference time-domain (FDTD) simulations
See the discussion of
"},{"location":"api/fdtd/#meanas.fdtd--timestep","title":"Timestep","text":"Maxwell's Equationsinmeanas.fdmathfor basic mathematical background.From the discussion of \"Plane waves and the Dispersion relation\" in
\\[ c^2 \\Delta_t^2 = \\frac{\\Delta_t^2}{\\mu \\epsilon} < 1/(\\frac{1}{\\Delta_x^2} + \\frac{1}{\\Delta_y^2} + \\frac{1}{\\Delta_z^2}) \\]meanas.fdmath, we haveor, if \\(\\Delta_x = \\Delta_y = \\Delta_z\\), then \\(c \\Delta_t < \\frac{\\Delta_x}{\\sqrt{3}}\\).
Based on this, we can set
dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2)\nThe
"},{"location":"api/fdtd/#meanas.fdtd--poynting-vector-and-energy-conservation","title":"Poynting Vector and Energy Conservation","text":"dx_min,dy_min,dz_minshould be the minimum value across both the base and derived grids.Let
\\[ \\begin{aligned} \\tilde{S}_{l, l', \\vec{r}} &=& &\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &=& &\\vec{x} (\\tilde{E}^y_{l,m+1,n,p} \\hat{H}^z_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^z_{l,m+1,n,p} \\hat{H}^y_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{y} (\\tilde{E}^z_{l,m,n+1,p} \\hat{H}^x_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^x_{l,m,n+1,p} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{z} (\\tilde{E}^x_{l,m,n,p+1} \\hat{H}^y_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^y_{l,m,n,p+1} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\end{aligned} \\]where \\(\\vec{r} = (m, n, p)\\) and \\(\\otimes\\) is a modified cross product in which the \\(\\tilde{E}\\) terms are shifted as indicated.
By taking the divergence and rearranging terms, we can show that
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l', \\vec{r}} &= \\hat{\\nabla} \\cdot (\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}}) \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l, \\vec{r}} - \\tilde{E}_{l, \\vec{r}} \\cdot \\hat{\\nabla} \\times \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot (-\\tilde{\\partial}_t \\mu_{\\vec{r} + \\frac{1}{2}} \\hat{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} - \\hat{M}_{l, \\vec{r} + \\frac{1}{2}}) - \\tilde{E}_{l, \\vec{r}} \\cdot (\\hat{\\partial}_t \\tilde{\\epsilon}_{\\vec{r}} \\tilde{E}_{l'+\\frac{1}{2}, \\vec{r}} + \\tilde{J}_{l', \\vec{r}}) \\\\ &= \\hat{H}_{l'} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t )(\\tilde{E}_{l'+\\frac{1}{2}} - \\tilde{E}_{l'-\\frac{1}{2}}) - \\hat{H}_{l'} \\cdot \\hat{M}_{l} - \\tilde{E}_l \\cdot \\tilde{J}_{l'} \\\\ \\end{aligned} \\]where in the last line the spatial subscripts have been dropped to emphasize the time subscripts \\(l, l'\\), i.e.
\\[ \\begin{aligned} \\tilde{E}_l &= \\tilde{E}_{l, \\vec{r}} \\\\ \\hat{H}_l &= \\tilde{H}_{l, \\vec{r} + \\frac{1}{2}} \\\\ \\tilde{\\epsilon} &= \\tilde{\\epsilon}_{\\vec{r}} \\\\ \\end{aligned} \\]etc. For \\(l' = l + \\frac{1}{2}\\) we get
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} &= \\hat{H}_{l + \\frac{1}{2}} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} - \\tilde{E}_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= (-\\mu / \\Delta_t)(\\hat{H}^2_{l + \\frac{1}{2}} - \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}}) - (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} \\cdot \\tilde{E}_l - \\tilde{E}^2_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= -(\\mu \\hat{H}^2_{l + \\frac{1}{2}} +\\epsilon \\tilde{E}_{l+1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ +(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ \\end{aligned} \\]and for \\(l' = l - \\frac{1}{2}\\),
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} &= (\\mu \\hat{H}^2_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}_{l-1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ -(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]These two results form the discrete time-domain analogue to Poynting's theorem. They hint at the expressions for the energy, which can be calculated at the same time-index as either the E or H field:
\\[ \\begin{aligned} U_l &= \\epsilon \\tilde{E}^2_l + \\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} \\\\ U_{l + \\frac{1}{2}} &= \\epsilon \\tilde{E}_l \\cdot \\tilde{E}_{l + 1} + \\mu \\hat{H}^2_{l + \\frac{1}{2}} \\\\ \\end{aligned} \\]Rewriting the Poynting theorem in terms of the energy expressions,
\\[ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]This result is exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary.
Note that each value of \\(J\\) contributes to the energy twice (i.e. once per field update) despite only causing the value of \\(E\\) to change once (same for \\(M\\) and \\(H\\)).
"},{"location":"api/fdtd/#meanas.fdtd--sources","title":"Sources","text":"It is often useful to excite the simulation with an arbitrary broadband pulse and then extract the frequency-domain response by performing an on-the-fly Fourier transform of the time-domain fields.
\\[ \\Delta_t \\sum_l w_l e^{-i \\omega t_l} f_l \\]
accumulate_phasorinmeanas.fdtd.phasorperforms the phase accumulation for one or more target frequencies, while leaving source normalization and simulation-loop policy to the caller.temporal_phasor(...)andtemporal_phasor_scale(...)apply the same Fourier sum to a scalar waveform, which is useful when a pulsed source envelope must be normalized before being applied to a point source or mode source.real_injection_scale(...)is the corresponding helper for the common real-valued injection patternnumpy.real(scale * waveform). Convenience wrappersaccumulate_phasor_e,accumulate_phasor_h, andaccumulate_phasor_japply the usual Yee time offsets.reconstruct_real(...)and the correspondingreconstruct_real_e/h/j(...)wrappers perform the inverse operation, converting phasors back into real-valued field snapshots at explicit Yee-aligned times. For scalaromega, the reconstruction helpers accept either a plain field phasor or the batched(1, *sample_shape)form used by the accumulator helpers. The helpers accumulatewith caller-provided sample times and weights. In this codebase the matching electric-current convention is typically
E -= dt * J / epsilonin FDTD and-i \\omega Jon the right-hand side of the FDFD wave equation.For FDTD/FDFD equivalence, this phasor path is the primary comparison workflow. It isolates the guided
+\\omegaresponse that the frequency-domain solver targets directly, regardless of whether the underlying FDTD run used real- or complex-valued fields.For exact pulsed FDTD/FDFD equivalence it is often simplest to keep the injected source, fields, and CPML auxiliary state complex-valued. The
real_injection_scale(...)helper is instead for the more ordinary one-run real-valued source path, where the intended positive-frequency waveform is injected throughnumpy.real(scale * waveform)and any remaining negative- frequency leakage is controlled by the pulse bandwidth and run length.\\[ E_{\\text{monitor}} = E_{\\text{guided}} + E_{\\text{residual}} . \\]
reconstruct_real(...)is for a different question: given a phasor, what late real-valued field snapshot should it produce? That raw-snapshot comparison is stricter and noisier because a monitor plane generally contains both the guided field and the remaining orthogonal content,Phasor/modal comparisons mostly validate the guided
+\\omegaterm. Raw real-field comparisons expose both terms at once, so they should be treated as secondary diagnostics rather than the main solver-equivalence benchmark.The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written
\\[ f_r(t) = (1 - \\frac{1}{2} (\\omega (t - \\tau))^2) e^{-(\\frac{\\omega (t - \\tau)}{2})^2} \\]with \\(\\tau > \\frac{2 * \\pi}{\\omega}\\) as a minimum delay to avoid a discontinuity at t=0 (assuming the source is off for t<0 this gives \\(\\sim 10^{-3}\\) error at t=0).
"},{"location":"api/fdtd/#meanas.fdtd--boundary-conditions","title":"Boundary conditions","text":"
meanas.fdtdexposes two boundary-related building blocks:
conducting_boundary(...)for simple perfect-electric-conductor style field clamping at one face of the domain.cpml_params(...)andupdates_with_cpml(...)for convolutional perfectly matched layers (CPMLs) attached to one or more faces of the Yee grid.
updates_with_cpml(...)accepts a three-by-two table of CPML parameter blocks:cpml_params[axis][polarity_index]\nwhere
axisis0,1, or2andpolarity_indexcorresponds to(-1, +1). PassingNonefor one entry disables CPML on that face while leaving the other directions unchanged. This is how mixed boundary setups such as \"absorbing in x, periodic in y/z\" are expressed.When comparing an FDTD run against an FDFD solve, use the same stretched coordinate system in both places:
- Build the FDTD update with the desired CPML parameters.
- Stretch the FDFD
dxeswith the matching SCPML transform.- Compare the extracted phasor against the FDFD field or residual on those stretched
dxes.The electric-current sign convention used throughout the examples and tests is
\\[ E \\leftarrow E - \\Delta_t J / \\epsilon \\]which matches the FDFD right-hand side
\\[ -i \\omega J. \\]"},{"location":"api/fdtd/#core-update-and-analysis-helpers","title":"Core update and analysis helpers","text":""},{"location":"api/fdtd/#meanas.fdtd.base","title":"meanas.fdtd.base","text":"Basic FDTD field updates
"},{"location":"api/fdtd/#meanas.fdtd.base.maxwell_e","title":"maxwell_e","text":"maxwell_e(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\nBuild a function which performs a portion the time-domain E-field update,
E += curl_back(H[t]) / epsilon\nThe full update should be
E += (curl_back(H[t]) + J) / epsilon\nwhich requires an additional step of
E += J / epsilonwhich is not performed by the generated function.See
meanas.fdmathfor descriptions of
- This update step: \"Maxwell's equations\" section
dxes: \"Datastructure: dx_lists_t\" sectionepsilon: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of
meanas.fdtdfor a discussion of thedtparameter.Parameters:
Name Type Description DefaultdtfloatTimestep. See
requiredmeanas.fdtdfor details.dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_updater_tFunction
"},{"location":"api/fdtd/#meanas.fdtd.base.maxwell_h","title":"maxwell_h","text":"f(E_old, H_old, epsilon) -> E_new.maxwell_h(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\nBuild a function which performs part of the time-domain H-field update,
H -= curl_forward(E[t]) / mu\nThe full update should be
H -= (curl_forward(E[t]) + M) / mu\nwhich requires an additional step of
H -= M / muwhich is not performed by the generated function; this step can be omitted if there is no magnetic currentM.See
meanas.fdmathfor descriptions of
- This update step: \"Maxwell's equations\" section
dxes: \"Datastructure: dx_lists_t\" sectionmu: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of
meanas.fdtdfor a discussion of thedtparameter.Parameters:
Name Type Description DefaultdtfloatTimestep. See
requiredmeanas.fdtdfor details.dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_updater_tFunction
"},{"location":"api/fdtd/#meanas.fdtd.pml","title":"meanas.fdtd.pml","text":"f(E_old, H_old, epsilon) -> E_new.Convolutional perfectly matched layer (CPML) support for FDTD updates.
The helpers in this module construct per-face CPML parameters and then wrap the standard Yee updates with the additional auxiliary
psifields needed by the CPML recurrence.The intended call pattern is:
- Build a
cpml_params[axis][polarity_index]table withcpml_params(...).- Pass that table into
updates_with_cpml(...)together withdt,dxes, andepsilon.- Advance the returned
update_E/update_Hclosures in the simulation loop.Each face can be enabled or disabled independently by replacing one table entry with
"},{"location":"api/fdtd/#meanas.fdtd.pml.cpml_params","title":"cpml_params","text":"None.cpml_params(\n axis: int,\n polarity: int,\n dt: float,\n thickness: int = 8,\n ln_R_per_layer: float = -1.6,\n epsilon_eff: float = 1,\n mu_eff: float = 1,\n m: float = 3.5,\n ma: float = 1,\n cfs_alpha: float = 0,\n) -> dict[str, Any]\nConstruct the parameter block for one CPML face.
Parameters:
Name Type Description DefaultaxisintWhich Cartesian axis the CPML is normal to (
required0,1, or2).polarityintWhich face along that axis (
required-1for the low-index face,+1for the high-index face).dtfloatTimestep used by the Yee update.
requiredthicknessintNumber of Yee cells occupied by the CPML region.
8ln_R_per_layerfloatLogarithmic attenuation target per layer.
-1.6epsilon_efffloatEffective permittivity used when choosing the CPML scaling.
1mu_efffloatEffective permeability used when choosing the CPML scaling.
1mfloatPolynomial grading exponent for
sigmaandkappa.3.5mafloatPolynomial grading exponent for the complex-frequency shift
alpha.1cfs_alphafloatMaximum complex-frequency shift parameter.
0Returns:
Type Descriptiondict[str, Any]Dictionary with:
dict[str, Any]
param_e:(p0, p1, p2)arrays for the E updatedict[str, Any]
param_h:(p0, p1, p2)arrays for the H updatedict[str, Any]"},{"location":"api/fdtd/#meanas.fdtd.pml.updates_with_cpml","title":"updates_with_cpml","text":"
region: slice tuple selecting the CPML cells on that faceupdates_with_cpml(\n cpml_params: Sequence[Sequence[dict[str, Any] | None]],\n dt: float,\n dxes: dx_lists_t,\n epsilon: fdfield,\n *,\n dtype: DTypeLike = numpy.float32,\n) -> tuple[\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n]\nBuild Yee-step update closures augmented with CPML terms.
Parameters:
Name Type Description Defaultcpml_paramsSequence[Sequence[dict[str, Any] | None]]Three-by-two sequence indexed as
required[axis][polarity_index]. Entries are the dictionaries returned bycpml_params(...); useNoneto disable CPML on one face.dtfloatTimestep.
requireddxesdx_lists_tYee-grid spacing lists
required[dx_e, dx_h].epsilonfdfieldElectric material distribution used by the E update.
requireddtypeDTypeLikeStorage dtype for the auxiliary CPML state arrays.
float32Returns:
Type DescriptionCallable[[fdfield_t, fdfield_t, fdfield_t], None]
(update_E, update_H)closures with the same call shape as the basicCallable[[fdfield_t, fdfield_t, fdfield_t], None]Yee updates:
tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]
update_E(e, h, epsilon)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]
update_H(e, h, mu)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]]The closures retain the CPML auxiliary state internally.
"},{"location":"api/fdtd/#meanas.fdtd.boundaries","title":"meanas.fdtd.boundaries","text":"Boundary conditions
"},{"location":"api/fdtd/#meanas.fdtd.boundaries--todo-conducting-boundary-documentation","title":"TODO conducting boundary documentation","text":""},{"location":"api/fdtd/#meanas.fdtd.energy","title":"meanas.fdtd.energy","text":""},{"location":"api/fdtd/#meanas.fdtd.energy.poynting","title":"poynting","text":"poynting(\n e: fdfield, h: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\nCalculate the poynting vector
S(\\(S\\)).This is the energy transfer rate (amount of energy
Uperdttransferred between adjacent cells) in each direction that happens during the half-step bounded by the two provided fields.The returned vector field
Sis the energy flow across +x, +y, and +z boundaries of the correspondingUcell. For example,mx = numpy.roll(mask, -1, axis=0)\n my = numpy.roll(mask, -1, axis=1)\n mz = numpy.roll(mask, -1, axis=2)\n\n u_hstep = fdtd.energy_hstep(e0=es[ii - 1], h1=hs[ii], e2=es[ii], **args)\n u_estep = fdtd.energy_estep(h0=hs[ii], e1=es[ii], h2=hs[ii + 1], **args)\n delta_j_B = fdtd.delta_energy_j(j0=js[ii], e1=es[ii], dxes=dxes)\n du_half_h2e = u_estep - u_hstep - delta_j_B\n\n s_h2e = -fdtd.poynting(e=es[ii], h=hs[ii], dxes=dxes) * dt\n planes = [s_h2e[0, mask].sum(), -s_h2e[0, mx].sum(),\n s_h2e[1, mask].sum(), -s_h2e[1, my].sum(),\n s_h2e[2, mask].sum(), -s_h2e[2, mz].sum()]\n\n assert_close(sum(planes), du_half_h2e[mask])\n(see
meanas.tests.test_fdtd.test_poynting_planes)The full relationship is
\\[ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]These equalities are exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary. (See
meanas.fdtddocs for derivation.)Parameters:
Name Type Description DefaultefdfieldE-field
requiredhfdfieldH-field (one half-timestep before or after
requirede)dxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Name Type Descriptionsfdfield_tVector field. Components indicate the energy transfer rate from the corresponding energy cell into its +x, +y, and +z neighbors during the half-step from the time of the earlier input field until the time of later input field.
"},{"location":"api/fdtd/#meanas.fdtd.energy.poynting_divergence","title":"poynting_divergence","text":"poynting_divergence(\n s: fdfield | None = None,\n *,\n e: fdfield | None = None,\n h: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate the divergence of the poynting vector.
This is the net energy flow for each cell, i.e. the change in energy
Uperdtcaused by transfer of energy to nearby cells (rather than absorption/emission by currentsJorM).See
poyntingandmeanas.fdtdfor more details. Args: s: Poynting vector, as calculated withpoynting. Optional; caller can provideeandhinstead. e: E-field (optional; need eithersor botheandh) h: H-field (optional; need eithersor botheandh) dxes: Grid description; seemeanas.fdmath.Returns:
Name Type Descriptiondsfdfield_tDivergence of the poynting vector. Entries indicate the net energy flow out of the corresponding energy cell.
"},{"location":"api/fdtd/#meanas.fdtd.energy.energy_hstep","title":"energy_hstep","text":"energy_hstep(\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate energy
Uat the time of the provided H-fieldh1.TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulte0fdfieldE-field one half-timestep before the energy.
requiredh1fdfieldH-field (at the same timestep as the energy).
requirede2fdfieldE-field one half-timestep after the energy.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tEnergy, at the time of the H-field
"},{"location":"api/fdtd/#meanas.fdtd.energy.energy_estep","title":"energy_estep","text":"h1.energy_estep(\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nCalculate energy
Uat the time of the provided E-fielde1.TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulth0fdfieldH-field one half-timestep before the energy.
requirede1fdfieldE-field (at the same timestep as the energy).
requiredh2fdfieldH-field one half-timestep after the energy.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tEnergy, at the time of the E-field
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_h2e","title":"delta_energy_h2e","text":"e1.delta_energy_h2e(\n dt: float,\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n h3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nChange in energy during the half-step from
h1toe2.This is just from (e2 * e2 + h3 * h1) - (h1 * h1 + e0 * e2)
Parameters:
Name Type Description Defaulte0fdfieldE-field one half-timestep before the start of the energy delta.
requiredh1fdfieldH-field at the start of the energy delta.
requirede2fdfieldE-field at the end of the energy delta (one half-timestep after
requiredh1).h3fdfieldH-field one half-timestep after the end of the energy delta.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tChange in energy from the time of
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_e2h","title":"delta_energy_e2h","text":"h1to the time ofe2.delta_energy_e2h(\n dt: float,\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n e3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nChange in energy during the half-step from
e1toh2.This is just from (h2 * h2 + e3 * e1) - (e1 * e1 + h0 * h2)
Parameters:
Name Type Description Defaulth0fdfieldE-field one half-timestep before the start of the energy delta.
requirede1fdfieldH-field at the start of the energy delta.
requiredh2fdfieldE-field at the end of the energy delta (one half-timestep after
requirede1).e3fdfieldH-field one half-timestep after the end of the energy delta.
requiredepsilonfdfield | NoneDielectric constant distribution.
Nonemufdfield | NoneMagnetic permeability distribution.
Nonedxesdx_lists_t | NoneGrid description; see
meanas.fdmath.NoneReturns:
Type Descriptionfdfield_tChange in energy from the time of
"},{"location":"api/fdtd/#meanas.fdtd.energy.delta_energy_j","title":"delta_energy_j","text":"e1to the time ofh2.delta_energy_j(\n j0: fdfield, e1: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\nCalculate the electric-current work term \\(J \\cdot E\\) on the Yee grid.
This is the source contribution that appears beside the flux divergence in the discrete Poynting identities documented in
meanas.fdtd.Note that each value of
Jcontributes twice in a full Yee cycle (once per half-step energy balance) even though it directly changesEonly once.Parameters:
Name Type Description Defaultj0fdfieldElectric-current density sampled at the same half-step as the current work term.
requirede1fdfieldElectric field sampled at the matching integer timestep.
requireddxesdx_lists_t | NoneGrid description; defaults to unit spacing.
NoneReturns:
Type Descriptionfdfield_tPer-cell source-work contribution with the scalar field shape.
"},{"location":"api/fdtd/#meanas.fdtd.energy.dxmul","title":"dxmul","text":"dxmul(\n ee: fdfield,\n hh: fdfield,\n epsilon: fdfield | float | None = None,\n mu: fdfield | float | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\nMultiply E- and H-like field products by material weights and cell volumes.
Parameters:
Name Type Description DefaulteefdfieldThree-component electric-field product, such as
requirede0 * e2.hhfdfieldThree-component magnetic-field product, such as
requiredh1 * h1.epsilonfdfield | float | NoneElectric material weight; defaults to
1.Nonemufdfield | float | NoneMagnetic material weight; defaults to
1.Nonedxesdx_lists_t | NoneGrid description; defaults to unit spacing.
NoneReturns:
Type Descriptionfdfield_tScalar field containing the weighted electric plus magnetic contribution
fdfield_tfor each Yee cell.
"},{"location":"api/fdtd/#meanas.fdtd.phasor","title":"meanas.fdtd.phasor","text":"Helpers for extracting single- or multi-frequency phasors from FDTD samples.
These helpers are intentionally low-level: callers own the accumulator arrays and the sampling policy. The accumulated quantity is
dt * sum(weight * exp(-1j * omega * t_step) * sample_step)\nwhere
t_step = (step + offset_steps) * dt.The usual Yee offsets are:
accumulate_phasor_e(..., step=l)forE_laccumulate_phasor_h(..., step=l)forH_{l + 1/2}accumulate_phasor_j(..., step=l)forJ_{l + 1/2}
temporal_phasor(...)andtemporal_phasor_scale(...)apply the same Fourier sum to a 1D scalar waveform. They are useful for normalizing a pulsed source before that scalar waveform is applied to a point source or spatial mode source.real_injection_scale(...)is a companion helper for the common real-valued injection patternnumpy.real(scale * waveform), wherewaveformis the analytic positive-frequency signal and the injected real current should still produce a desired phasor response.reconstruct_real(...)and itsE/H/Jwrappers perform the inverse operation: they turn one or more phasors back into real-valued field snapshots at explicit Yee-aligned sample times. For a scalar target frequency they accept either a plain field phasor or the batched(1, *sample_shape)form used elsewhere in this module.These helpers do not choose warmup/accumulation windows or pulse-envelope normalization. They also do not impose a current sign convention. In this codebase, electric-current injection normally appears as
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor","title":"accumulate_phasor","text":"E -= dt * J / epsilon, which matches the FDFD right-hand side-1j * omega * J.accumulate_phasor(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n offset_steps: float = 0.0,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAdd one time-domain sample into a phasor accumulator.
The added quantity is
dt * weight * exp(-1j * omega * t_step) * sample\nwhere
Notet_step = (step + offset_steps) * dt.This helper already multiplies by
"},{"location":"api/fdtd/#meanas.fdtd.phasor.temporal_phasor","title":"temporal_phasor","text":"dt. If the caller's normalization factor was derived from a discrete sum that already includesdt, passweight / dthere.temporal_phasor(\n samples: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n *,\n start_step: int = 0,\n offset_steps: float = 0.0,\n) -> NDArray[numpy.complexfloating]\nFourier-project a 1D temporal waveform onto one or more angular frequencies.
The returned quantity is
dt * sum(exp(-1j * omega * t_step) * samples[step_index])\nwhere
"},{"location":"api/fdtd/#meanas.fdtd.phasor.temporal_phasor_scale","title":"temporal_phasor_scale","text":"t_step = (start_step + step_index + offset_steps) * dt.temporal_phasor_scale(\n samples: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n *,\n start_step: int = 0,\n offset_steps: float = 0.0,\n target: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nReturn the scalar multiplier that gives a desired temporal phasor response.
The returned scale satisfies
temporal_phasor(scale * samples, omegas, dt, ...) == target\nfor each target frequency. The result keeps a leading frequency axis even when
"},{"location":"api/fdtd/#meanas.fdtd.phasor.real_injection_scale","title":"real_injection_scale","text":"omegasis scalar.real_injection_scale(\n samples: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n *,\n start_step: int = 0,\n offset_steps: float = 0.0,\n target: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nReturn the scale for a real-valued injection built from an analytic waveform.
If the time-domain source is applied as
numpy.real(scale * samples[step])\nthen the desired positive-frequency phasor is obtained by compensating for the 1/2 factor between the real-valued source and its analytic component:
scale = 2 * target / temporal_phasor(samples, ...)\nThis helper normalizes only the intended positive-frequency component. Any residual negative-frequency leakage is controlled by the waveform design and the accumulation window.
"},{"location":"api/fdtd/#meanas.fdtd.phasor.reconstruct_real","title":"reconstruct_real","text":"reconstruct_real(\n phasors: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n step: int,\n *,\n offset_steps: float = 0.0,\n) -> NDArray[numpy.floating]\nReconstruct a real-valued field snapshot from one or more phasors.
The returned quantity is
real(phasor * exp(1j * omega * t_step))\nwhere
t_step = (step + offset_steps) * dt.For multi-frequency inputs, the leading frequency axis is preserved. For a scalar
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_e","title":"accumulate_phasor_e","text":"omega, callers may pass either(1, *sample_shape)orsample_shape; the return shape matches that choice.accumulate_phasor_e(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate an E-field sample taken at integer timestep
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_h","title":"accumulate_phasor_h","text":"step.accumulate_phasor_h(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate an H-field sample corresponding to
"},{"location":"api/fdtd/#meanas.fdtd.phasor.accumulate_phasor_j","title":"accumulate_phasor_j","text":"H_{step + 1/2}.accumulate_phasor_j(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\nAccumulate a current sample corresponding to
"},{"location":"api/fdtd/#meanas.fdtd.phasor.reconstruct_real_e","title":"reconstruct_real_e","text":"J_{step + 1/2}.reconstruct_real_e(\n phasors: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n step: int,\n) -> NDArray[numpy.floating]\nReconstruct a real E-field snapshot taken at integer timestep
"},{"location":"api/fdtd/#meanas.fdtd.phasor.reconstruct_real_h","title":"reconstruct_real_h","text":"step.reconstruct_real_h(\n phasors: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n step: int,\n) -> NDArray[numpy.floating]\nReconstruct a real H-field snapshot corresponding to
"},{"location":"api/fdtd/#meanas.fdtd.phasor.reconstruct_real_j","title":"reconstruct_real_j","text":"H_{step + 1/2}.reconstruct_real_j(\n phasors: ArrayLike,\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n step: int,\n) -> NDArray[numpy.floating]\nReconstruct a real current snapshot corresponding to
"},{"location":"api/meanas/","title":"meanas","text":""},{"location":"api/meanas/#meanas","title":"meanas","text":"J_{step + 1/2}.Electromagnetic simulation tools
See the tracked examples for end-to-end workflows, and
"},{"location":"api/waveguides/","title":"waveguides","text":""},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d","title":"meanas.fdfd.waveguide_2d","text":"help(meanas)for the toolbox overview and API derivations.Operators and helper functions for waveguides with unchanging cross-section.
The propagation direction is chosen to be along the z axis, and all fields are given an implicit z-dependence of the form
exp(-1 * wavenumber * z).As the z-dependence is known, all the functions in this file assume a 2D grid (i.e.
dxes = [[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]], [[dx_h[0], ...], [dy_h[0], ...]]]).===============
Consider Maxwell's equations in continuous space, in the frequency domain. Assuming a structure with some (x, y) cross-section extending uniformly into the z dimension, with a diagonal \\(\\epsilon\\) tensor, we have
\\[ \\begin{aligned} \\nabla \\times \\vec{E}(x, y, z) &= -\\imath \\omega \\mu \\vec{H} \\\\ \\nabla \\times \\vec{H}(x, y, z) &= \\imath \\omega \\epsilon \\vec{E} \\\\ \\vec{E}(x,y,z) &= (\\vec{E}_t(x, y) + E_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\vec{H}(x,y,z) &= (\\vec{H}_t(x, y) + H_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\end{aligned} \\]Expanding the first two equations into vector components, we get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\partial_y E_z - \\partial_z E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= \\partial_z E_x - \\partial_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\partial_x E_y - \\partial_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\partial_y H_z - \\partial_z H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= \\partial_z H_x - \\partial_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\partial_x H_y - \\partial_y H_x \\\\ \\end{aligned} \\]Substituting in our expressions for \\(\\vec{E}\\), \\(\\vec{H}\\) and discretizing:
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\tilde{\\partial}_y E_z + \\imath \\beta E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta E_x - \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\hat{\\partial}_y H_z + \\imath \\beta H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= -\\imath \\beta H_x - \\hat{\\partial}_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Rewrite the last three equations as
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\imath \\omega E_z &= \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Now apply \\(\\imath \\beta \\tilde{\\partial}_x\\) to the last equation, then substitute in for \\(\\imath \\beta H_x\\) and \\(\\imath \\beta H_y\\):
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_x \\imath \\omega E_z &= \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y) \\\\ \\imath \\beta \\tilde{\\partial}_x E_z &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]With a similar approach (but using \\(\\imath \\beta \\tilde{\\partial}_y\\) instead), we can get
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_y E_z &= \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]We can combine this equation for \\(\\imath \\beta \\tilde{\\partial}_y E_z\\) with the unused \\(\\imath \\omega \\mu_{xx} H_x\\) and \\(\\imath \\omega \\mu_{yy} H_y\\) equations to get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\imath \\beta \\tilde{\\partial}_y E_z \\\\ -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]and
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\imath \\beta \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]However, based on our rewritten equation for \\(\\imath \\beta H_x\\) and the so-far unused equation for \\(\\imath \\omega \\mu_{zz} H_z\\) we can also write
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} (\\imath \\beta H_x) &= -\\imath \\omega \\mu_{xx} (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y + \\imath \\omega \\mu_{xx} \\hat{\\partial}_x ( \\frac{1}{-\\imath \\omega \\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x)) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]and, similarly,
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} (\\imath \\beta H_y) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]By combining both pairs of expressions, we get
\\[ \\begin{aligned} \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]Using these, we can construct the eigenvalue problem
\\[ \\beta^2 \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} = (\\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} \\]In the literature, \\(\\beta\\) is usually used to denote the lossless/real part of the propagation constant, but in
meanasit is allowed to be complex.An equivalent eigenvalue problem can be formed using the \\(H_x\\) and \\(H_y\\) fields, if those are more convenient.
Note that \\(E_z\\) was never discretized, so \\(\\beta\\) will need adjustment to account for numerical dispersion if the result is introduced into a space with a discretized z-axis.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_e","title":"operator_e","text":"operator_e(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nWaveguide operator of the form
omega**2 * mu * epsilon +\nmu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +\n[[Dx], [Dy]] / epsilon * [Dx, Dy] * epsilon\nfor use with a field vector of the form
cat([E_x, E_y]).More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form
operator_e(...) @ [E_x, E_y] = wavenumber**2 * [E_x, E_y]which can then be solved for the eigenmodes of the system (an
exp(-i * wavenumber * z)z-dependence is assumed for the fields).Parameters:
Name Type Description DefaultomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_h","title":"operator_h","text":"operator_h(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nWaveguide operator of the form
omega**2 * epsilon * mu +\nepsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +\n[[Dx], [Dy]] / mu * [Dx, Dy] * mu\nfor use with a field vector of the form
cat([H_x, H_y]).More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\epsilon_{yy} \\mu_{xx} & 0 \\\\ 0 & \\epsilon_{xx} \\mu_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\epsilon_{yy} \\tilde{\\partial}_y \\\\ \\epsilon_{xx} \\tilde{\\partial}_x \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} -\\hat{\\partial}_y & \\hat{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\hat{\\partial}_x \\\\ \\hat{\\partial}_y \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} \\tilde{\\partial}_x \\mu_{xx} & \\tilde{\\partial}_y \\mu_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form
operator_h(...) @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]which can then be solved for the eigenmodes of the system (an
exp(-i * wavenumber * z)z-dependence is assumed for the fields).Parameters:
Name Type Description DefaultomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_e","title":"normalized_fields_e","text":"normalized_fields_e(\n e_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
e_xycontaining the vectorized E_x and E_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulte_xyvcfdfield2Vector containing E_x and E_y fields
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
Notes
e_xyis only the transverse electric eigenvector. This helper first reconstructs the full three-componentEandHfields withexy2e(...)andexy2h(...), then normalizes them to unit forward power using_normalized_fields(...).The normalization target is
\\[ \\Re\\left[\\mathrm{inner\\_product}(e, h, \\mathrm{conj\\_h}=True)\\right] = 1, \\]so the returned fields represent a unit-power forward mode under the discrete Yee-grid Poynting inner product.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_h","title":"normalized_fields_h","text":"normalized_fields_h(\n h_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
h_xycontaining the vectorized H_x and H_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulth_xyvcfdfield2Vector containing H_x and H_y fields
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
NotesThis is the
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.exy2h","title":"exy2h","text":"H_x/H_yanalogue ofnormalized_fields_e(...). The final normalized mode should describe the same physical solution, but because the overall complex phase and sign are chosen heuristically,normalized_fields_e(...)andnormalized_fields_h(...)need not return identical representatives for nearly symmetric modes.exy2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_x and E_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2e","title":"hxy2e","text":"hxy2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
h_xycontaining the vectorized H_x and H_y fields, into a vectorized E containing all three E componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xyomegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2h","title":"hxy2h","text":"hxy2h(\n wavenumber: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
h_xycontaining the vectorized H_x and H_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z). It should satisfyoperator_h() @ h_xy == wavenumber**2 * h_xydxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)muvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.exy2e","title":"exy2e","text":"exy2e(\n wavenumber: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_x and E_y fields, into a vectorized E containing all three E componentsFrom the operator derivation (see module docs), we have
\\[ \\imath \\omega \\epsilon_{zz} E_z = \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\]as well as the intermediate equations
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\end{aligned} \\]Combining these, we get
\\[ \\begin{aligned} E_z &= \\frac{1}{- \\omega \\beta \\epsilon_{zz}} (( \\hat{\\partial}_y \\hat{\\partial}_x H_z -\\hat{\\partial}_x \\hat{\\partial}_y H_z) + \\imath \\omega (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y)) &= \\frac{1}{\\imath \\beta \\epsilon_{zz}} (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y) \\end{aligned} \\]Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)It should satisfyoperator_e() @ e_xy == wavenumber**2 * e_xydxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.e2h","title":"e2h","text":"e2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)muvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h2e","title":"h2e","text":"h2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized H eigenfield, produces the vectorized E eigenfield slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_e","title":"curl_e","text":"curl_e(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\nDiscretized curl operator for use with the waveguide E field slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)dxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)Returns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_h","title":"curl_h","text":"curl_h(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\nDiscretized curl operator for use with the waveguide H field slice.
Parameters:
Name Type Description DefaultwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)dxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)Returns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h_err","title":"h_err","text":"h_err(\n h: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\nCalculates the relative error in the H field
Parameters:
Name Type Description DefaulthvcfdsliceVectorized H field
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionfloatRelative error
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.e_err","title":"e_err","text":"norm(A_h @ h) / norm(h).e_err(\n e: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\nCalculates the relative error in the E field
Parameters:
Name Type Description DefaultevcfdsliceVectorized E field
requiredwavenumbercomplexWavenumber assuming fields have z-dependence of
requiredexp(-i * wavenumber * z)omegacomplexThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionfloatRelative error
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.sensitivity","title":"sensitivity","text":"norm(A_e @ e) / norm(e).sensitivity(\n e_norm: vcfdslice,\n h_norm: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> vcfdslice_t\nGiven a waveguide structure (
dxes,epsilon,mu) and mode fields (e_norm,h_norm,wavenumber,omega), calculates the sensitivity of the wavenumber \\(\\beta\\) to changes in the dielectric structure \\(\\epsilon\\).The output is a vector of the same size as
\\[ sens_{i} = \\frac{\\partial\\beta}{\\partial\\epsilon_i} \\]vec(epsilon), with each element specifying the sensitivity ofwavenumberto changes in the corresponding element invec(epsilon), i.e.An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
\\[ \\beta^2 E_{xy} = A_E E_{xy} \\]where \\(A_E\\) is the waveguide operator from
\\[ (2 \\beta) \\partial_{\\epsilon_i}(\\beta) E_{xy} + \\beta^2 \\partial_{\\epsilon_i} E_{xy} = \\partial_{\\epsilon_i}(A_E) E_{xy} + A_E \\partial_{\\epsilon_i} E_{xy} \\]operator_e(), and \\(E_{xy} = \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix}\\), we can differentiate with respect to one of the \\(\\epsilon\\) elements (i.e. at one Yee grid point), \\(\\epsilon_i\\):We then multiply by \\(H_{yx}^\\star = \\begin{bmatrix}H_y^\\star \\\\ -H_x^\\star \\end{bmatrix}\\) from the left:
\\[ (2 \\beta) \\partial_{\\epsilon_i}(\\beta) H_{yx}^\\star E_{xy} + \\beta^2 H_{yx}^\\star \\partial_{\\epsilon_i} E_{xy} = H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} + H_{yx}^\\star A_E \\partial_{\\epsilon_i} E_{xy} \\]However, \\(H_{yx}^\\star\\) is actually a left-eigenvector of \\(A_E\\). This can be verified by inspecting the form of
\\[ H_{yx}^\\star A_E \\partial_{\\epsilon_i} E_{xy} = \\beta^2 H_{yx}^\\star \\partial_{\\epsilon_i} E_{xy} \\]operator_h(\\(A_H\\)) and comparing its conjugate transpose tooperator_e(\\(A_E\\)). Also, note \\(H_{yx}^\\star \\cdot E_{xy} = H^\\star \\times E\\) recalls the mode orthogonality relation. See doi:10.5194/ars-9-85-201 for a similar approach. Therefore,and we can simplify to
\\[ \\partial_{\\epsilon_i}(\\beta) = \\frac{1}{2 \\beta} \\frac{H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} }{H_{yx}^\\star E_{xy}} \\]This expression can be quickly calculated for all \\(i\\) by writing out the various terms of \\(\\partial_{\\epsilon_i} A_E\\) and recognizing that the vector-matrix-vector products (i.e. scalars) \\(sens_i = \\vec{v}_{left} \\partial_{\\epsilon_i} (\\epsilon_{xyz}) \\vec{v}_{right}\\), indexed by \\(i\\), can be expressed as elementwise multiplications \\(\\vec{sens} = \\vec{v}_{left} \\star \\vec{v}_{right}\\)
Parameters:
Name Type Description Defaulte_normvcfdsliceNormalized, vectorized E_xyz field for the mode. E.g. as returned by
requirednormalized_fields_e.h_normvcfdsliceNormalized, vectorized H_xyz field for the mode. E.g. as returned by
requirednormalized_fields_e.wavenumbercomplexPropagation constant for the mode. The z-axis is assumed to be continuous (i.e. without numerical dispersion).
requiredomegacomplexThe angular frequency of the system.
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type Descriptionvcfdslice_tSparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nGiven a 2D region, attempts to solve for the eigenmode with the specified mode numbers.
Parameters:
Name Type Description Defaultmode_numbersSequence[int]List of 0-indexed mode numbers to solve for
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesepsilonvfdsliceDielectric constant
requiredmuvfdslice | NoneMagnetic permeability (default 1 everywhere)
Nonemode_marginintThe eigensolver will actually solve for
(max(mode_number) + mode_margin)modes, but only return the target mode. Increasing this value can improve the solver's ability to find the correct mode. Default 2.2Returns:
Name Type Descriptione_xysNDArray[complex128]NDArray of vfdfield_t specifying fields. First dimension is mode number.
wavenumbersNDArray[complex128]list of wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdfield2_t, complex]\nWrapper around
solve_modes()that solves for a single mode.Parameters:
Name Type Description Defaultmode_numberint0-indexed mode number to solve for
required*argsAnypassed to
solve_modes()()**kwargsAnypassed to
solve_modes(){}Returns:
Type Descriptiontuple[vcfdfield2_t, complex](e_xy, wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.inner_product","title":"inner_product","text":"inner_product(\n e1: vcfdfield2,\n h2: vcfdfield2,\n dxes: dx_lists2_t,\n prop_phase: float = 0,\n conj_h: bool = False,\n trapezoid: bool = False,\n) -> complex\nCompute the discrete waveguide overlap / Poynting inner product.
This is the 2D transverse integral corresponding to the time-averaged longitudinal Poynting flux,
\\[ \\frac{1}{2}\\int (E_x H_y - E_y H_x) \\, dx \\, dy \\]with the Yee-grid staggering and optional propagation-phase adjustment used by the waveguide helpers in this module.
Parameters:
Name Type Description Defaulte1vcfdfield2Vectorized electric field, typically from
requiredexy2e(...)ornormalized_fields_e(...).h2vcfdfield2Vectorized magnetic field, typically from
requiredhxy2h(...),exy2h(...), or one of the normalization helpers.dxesdx_lists2_tTwo-dimensional Yee-grid spacing lists
required[dx_e, dx_h].prop_phasefloatPhase advance over one propagation cell. This is used to shift the H field into the same longitudinal reference plane as the E field.
0conj_hboolWhether to conjugate
h2before forming the overlap. UseTruefor the usual time-averaged power normalization.FalsetrapezoidboolWhether to use trapezoidal quadrature instead of the default rectangular Yee-cell sum.
FalseReturns:
Type DescriptioncomplexComplex overlap / longitudinal power integral.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d","title":"meanas.fdfd.waveguide_3d","text":"Tools for working with waveguide modes in 3D domains.
This module relies heavily on
waveguide_2dand mostly just transforms its parameters into 2D equivalents and expands the results back into 3D.The intended workflow is:
- Select a single-cell slice normal to the propagation axis.
- Solve the corresponding 2D mode problem with
solve_mode(...).- Turn that mode into a one-sided source with
compute_source(...).- Build an overlap window with
compute_overlap_e(...)for port readout.
polarityis part of the public convention throughout this module:
+1means forward propagation toward increasing index alongaxis-1means backward propagation toward decreasing index alongaxisThat same convention controls which side of the selected slice is used for the overlap window and how the expanded field is phased.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> dict[str, complex | NDArray[complexfloating]]\nGiven a 3D grid, selects a slice from the grid and attempts to solve for an eigenmode propagating through that slice.
Parameters:
Name Type Description Defaultmode_numberintNumber of the mode, 0-indexed
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section.slices[axis]must select exactly one item.epsilonfdfieldDielectric constant
requiredmufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptiondict[str, complex | NDArray[complexfloating]]Dictionary containing:
dict[str, complex | NDArray[complexfloating]]
E: full-grid electric field for the solved modedict[str, complex | NDArray[complexfloating]]
H: full-grid magnetic field for the solved modedict[str, complex | NDArray[complexfloating]]
wavenumber: propagation constant corrected for the discretized propagation axisdict[str, complex | NDArray[complexfloating]]Notes
wavenumber_2d: propagation constant of the reduced 2D eigenproblemThe returned fields are normalized through the
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.compute_source","title":"compute_source","text":"waveguide_2dnormalization convention before being expanded back to 3D.compute_source(\n E: cfdfield,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_t\nGiven an eigenmode obtained by
solve_mode, returns the current source distribution necessary to position a unidirectional source at the slice location.Parameters:
Name Type Description DefaultEcfdfieldE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requiredomegacomplexAngular frequency of the simulation
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section.slices[axis]should select only one item.mufdfield | NoneMagnetic permeability (default 1 everywhere)
NoneReturns:
Type Descriptioncfdfield_tNotes
Jdistribution for a one-sided electric-current source.The source is built from the expanded mode field and a boundary-source operator. The resulting current is intended to be injected with the same sign convention used elsewhere in the package:
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.compute_overlap_e","title":"compute_overlap_e","text":"
E -= dt * J / epsiloncompute_overlap_e(\n E: cfdfield_t,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n omega: float,\n) -> cfdfield_t\nBuild an overlap field for projecting another 3D electric field onto a mode.
The returned field is intended for the discrete overlap expression
\\[ \\sum \\mathrm{overlap\\_e} \\; E_\\mathrm{other}^* \\]where the sum is over the full Yee-grid field storage.
The construction uses a two-cell window immediately upstream of the selected slice:
- for
polarity=+1, the two cells just beforeslices[axis].start- for
polarity=-1, the two cells just afterslices[axis].stopThe window is clipped to the simulation domain if necessary. A clipped but non-empty window raises
RuntimeWarning; an empty window raisesValueError.The derivation below assumes reflection symmetry and the standard waveguide overlap relation involving
\\[ \\int ((E \\times H_\\mathrm{mode}) + (E_\\mathrm{mode} \\times H)) \\cdot dn. \\]E x H_mode + E_mode x H -> Ex Hmy - EyHmx + Emx Hy - Emy Hx (Z-prop) Ex we/B Emx + Ex i/B dy Hmz - Ey (-we/B Emy) - Ey i/B dx Hmz we/B (Ex Emx + Ey Emy) + i/B (Ex dy Hmz - Ey dx Hmz) we/B (Ex Emx + Ey Emy) + i/B (Ex dy (dx Emy - dy Emx) - Ey dx (dx Emy - dy Emx)) we/B (Ex Emx + Ey Emy) + i/B (Ex dy dx Emy - Ex dy dy Emx - Ey dx dx Emy - Ey dx dy Emx)
Ex j/wu (-jB Emx - dx Emz) - Ey j/wu (dy Emz + jB Emy) B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)
Parameters:
Name Type Description DefaultEcfdfield_tE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.Returns:
Type Descriptioncfdfield_t
overlap_enormalized so thatnumpy.sum(overlap_e * E.conj()) == 1cfdfield_tover the retained overlap window.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.expand_e","title":"expand_e","text":"expand_e(\n E: cfdfield,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n) -> cfdfield_t\nGiven an eigenmode obtained by
solve_mode, expands the E-field from the 2D slice where the mode was calculated to the entire domain (along the propagation axis). This assumes the epsilon cross-section remains constant throughout the entire domain; it is up to the caller to truncate the expansion to any regions where it is valid.Parameters:
Name Type Description DefaultEcfdfieldE-field of the mode
requiredwavenumbercomplexWavenumber of the mode
requireddxesdx_lists_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.typesaxisintPropagation axis (0=x, 1=y, 2=z)
requiredpolarityintPropagation direction (+1 for +ve, -1 for -ve)
requiredslicesSequence[slice]required
epsilon[tuple(slices)]is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.Returns:
Type Descriptioncfdfield_tNotes
E, with the original field expanded along the specifiedaxis.This helper assumes that the waveguide cross-section remains constant along the propagation axis and applies the phase factor
\\[ e^{-i \\, \\mathrm{polarity} \\, wavenumber \\, \\Delta z} \\]to each copied slice.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl","title":"meanas.fdfd.waveguide_cyl","text":"Operators and helper functions for cylindrical waveguides with unchanging cross-section.
Waveguide operator is derived according to 10.1364/OL.33.001848.
As in
\\[ \\vec{E}(r, y, \\theta), \\vec{H}(r, y, \\theta) \\propto e^{-\\imath m \\theta}, \\]waveguide_2d, the propagation dependence is separated from the transverse solve. Here the propagation coordinate is the bend angle\\theta, and the fields are assumed to have the formwhere
\\[ \\beta = \\frac{m}{r_{\\min}}, \\]mis the angular wavenumber returned bysolve_mode(s). It is often convenient to introduce the corresponding linear wavenumberso that the cylindrical problem resembles the straight-waveguide problem with additional metric factors.
Those metric factors live on the staggered radial Yee grids. If the left edge of the computational window is at
\\[ \\begin{aligned} r_a(n) &= r_{\\min} + \\sum_{j \\le n} \\Delta r_{e, j}, \\\\ r_b\\!\\left(n + \\tfrac{1}{2}\\right) &= r_{\\min} + \\tfrac{1}{2}\\Delta r_{e, n} + \\sum_{j < n} \\Delta r_{h, j}, \\end{aligned} \\]r = r_{\\min}, define the electric-grid and magnetic-grid radial sample locations byand from them the diagonal metric matrices
\\[ \\begin{aligned} T_a &= \\operatorname{diag}(r_a / r_{\\min}), \\\\ T_b &= \\operatorname{diag}(r_b / r_{\\min}). \\end{aligned} \\]With the same forward/backward derivative notation used in
\\[ \\begin{aligned} -\\imath \\omega \\mu_{rr} H_r &= \\tilde{\\partial}_y E_z + \\imath \\beta T_a^{-1} E_y, \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta T_b^{-1} E_r - T_b^{-1} \\tilde{\\partial}_r (T_a E_z), \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_r E_y - \\tilde{\\partial}_y E_r, \\\\ \\imath \\beta H_y &= -\\imath \\omega T_b \\epsilon_{rr} E_r - T_b \\hat{\\partial}_y H_z, \\\\ \\imath \\beta H_r &= \\imath \\omega T_a \\epsilon_{yy} E_y - T_b T_a^{-1} \\hat{\\partial}_r (T_b H_z), \\\\ \\imath \\omega E_z &= T_a \\epsilon_{zz}^{-1} \\left(\\hat{\\partial}_r H_y - \\hat{\\partial}_y H_r\\right). \\end{aligned} \\]waveguide_2d, the coordinate-transformed discrete curl equations used here areThe first three equations are the cylindrical analogue of the straight-guide relations for
H_r,H_y, andH_z. The next two are the metric-weighted versions of the straight-guide identities for\\imath \\beta H_yand\\imath \\beta H_r, and the last equation plays the same role as the longitudinalE_zreconstruction inwaveguide_2d.Following the same elimination steps as in
\\[ H_z = \\frac{1}{-\\imath \\omega \\mu_{zz}} \\left(\\tilde{\\partial}_r E_y - \\tilde{\\partial}_y E_r\\right). \\]waveguide_2d, apply\\imath \\beta \\tilde{\\partial}_rand\\imath \\beta \\tilde{\\partial}_yto the equation forE_z, substitute for\\imath \\beta H_rand\\imath \\beta H_y, and then eliminateH_zwithThis yields the transverse electric eigenproblem implemented by
\\[ \\beta^2 \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix} = \\left( \\omega^2 \\begin{bmatrix} T_b^2 \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & T_a^2 \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -T_b \\mu_{yy} \\hat{\\partial}_y \\\\ T_a \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} T_b \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} T_a \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x T_b \\epsilon_{xx} & \\hat{\\partial}_y T_a \\epsilon_{yy} \\end{bmatrix} \\right) \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix}. \\]cylindrical_operator(...):Since
\\beta = m / r_{\\min}, the solver implemented in this file returns the angular wavenumberm, while the operator itself is most naturally written in terms of the linear quantity\\beta. The helpers below reconstruct the full field components from the solved transverse eigenvector and then normalize the mode to unit forward power with the same discrete longitudinal Poynting inner product used bywaveguide_2d.As in the straight-waveguide case, all functions here assume a 2D grid:
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.cylindrical_operator","title":"cylindrical_operator","text":"
dxes = [[[dr_e_0, dr_e_1, ...], [dy_e_0, ...]], [[dr_h_0, ...], [dy_h_0, ...]]].cylindrical_operator(\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n) -> sparse.sparray\nCylindrical coordinate waveguide operator of the form
\\[ (\\omega^2 \\begin{bmatrix} T_b T_b \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & T_a T_a \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -T_b \\mu_{yy} \\hat{\\partial}_y \\\\ T_a \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} T_b \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} T_a \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x T_b \\epsilon_{xx} & \\hat{\\partial}_y T_a \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix} \\]for use with a field vector of the form
[E_r, E_y].This operator can be used to form an eigenvalue problem of the form A @ [E_r, E_y] = beta**2 * [E_r, E_y]
which can then be solved for the eigenmodes of the system (an
exp(-i * angular_wavenumber * theta)theta-dependence is assumed for the fields, withbeta = angular_wavenumber / rmin).(NOTE: See module docs and 10.1364/OL.33.001848)
Parameters:
Name Type Description DefaultomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)epsilonvfdsliceVectorized dielectric constant grid
requiredrminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type DescriptionsparraySparse matrix representation of the operator
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\nGiven a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode of the bent waveguide with the specified mode number.
Parameters:
Name Type Description Defaultmode_numberNumber of the mode, 0-indexed
requiredomegafloatAngular frequency of the simulation
requireddxesdx_lists2_tGrid parameters [dx_e, dx_h] as described in meanas.fdmath.types. The first coordinate is assumed to be r, the second is y.
requiredepsilonvfdsliceDielectric constant
requiredrminfloatRadius of curvature for the simulation. This should be the minimum value of r within the simulation domain.
requiredReturns:
Name Type Descriptione_xysNDArray[complex128]NDArray of vfdfield_t specifying fields. First dimension is mode number.
angular_wavenumbersNDArray[complex128]list of wavenumbers in 1/rad units.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdslice, complex]\nWrapper around
solve_modes()that solves for a single mode.Parameters:
Name Type Description Defaultmode_numberint0-indexed mode number to solve for
required*argsAnypassed to
solve_modes()()**kwargsAnypassed to
solve_modes(){}Returns:
Type Descriptiontuple[vcfdslice, complex](e_xy, angular_wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.linear_wavenumbers","title":"linear_wavenumbers","text":"linear_wavenumbers(\n e_xys: list[vcfdfield2_t],\n angular_wavenumbers: ArrayLike,\n epsilon: vfdslice,\n dxes: dx_lists2_t,\n rmin: float,\n) -> NDArray[numpy.complex128]\nCalculate linear wavenumbers (1/distance) based on angular wavenumbers (1/rad) and the mode's energy distribution.
Parameters:
Name Type Description Defaulte_xyslist[vcfdfield2_t]Vectorized mode fields with shape (num_modes, 2 * x *y)
requiredangular_wavenumbersArrayLikeWavenumbers assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). They should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyepsilonvfdsliceVectorized dielectric constant grid with shape (3, x, y)
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type DescriptionNDArray[complex128]NDArray containing the calculated linear (1/distance) wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2h","title":"exy2h","text":"exy2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_r and E_y fields, into a vectorized H containing all three H componentsParameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2e","title":"exy2e","text":"exy2e(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n) -> sparse.sparray\nOperator which transforms the vector
e_xycontaining the vectorized E_r and E_y fields, into a vectorized E containing all three E componentsUnlike the straight waveguide case, the H_z components do not cancel and must be calculated from E_r and E_y in order to then calculate E_z.
Parameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredReturns:
Type DescriptionsparraySparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.e2h","title":"e2h","text":"e2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n mu: vfdslice | None = None,\n) -> sparse.sparray\nReturns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
This operator is created directly from the initial coordinate-transformed equations:
\\[ \\begin{aligned} -\\imath \\omega \\mu_{rr} H_r &= \\tilde{\\partial}_y E_z + \\imath \\beta T_a^{-1} E_y, \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta T_b^{-1} E_r - T_b^{-1} \\tilde{\\partial}_r (T_a E_z), \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_r E_y - \\tilde{\\partial}_y E_r, \\end{aligned} \\]Parameters:
Name Type Description Defaultangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
NoneReturns:
Type DescriptionsparraySparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.dxes2T","title":"dxes2T","text":"dxes2T(\n dxes: dx_lists2_t, rmin: float\n) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]\nConstruct the cylindrical metric matrices \\(T_a\\) and \\(T_b\\).
T_ais sampled on the E-grid radial locations, whileT_bis sampled on the staggered H-grid radial locations. These are the diagonal matrices that convert the straight-waveguide algebra into its cylindrical counterpart.Parameters:
Name Type Description Defaultdxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type Descriptiontuple[NDArray[float64], NDArray[float64]]Sparse diagonal matrices
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.normalized_fields_e","title":"normalized_fields_e","text":"(T_a, T_b).normalized_fields_e(\n e_xy: vcfdfield2,\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\nGiven a vector
e_xycontaining the vectorized E_r and E_y fields, returns normalized, vectorized E and H fields for the system.Parameters:
Name Type Description Defaulte_xyvcfdfield2Vector containing E_r and E_y fields
requiredangular_wavenumbercomplexWavenumber assuming fields have theta-dependence of
requiredexp(-i * angular_wavenumber * theta). It should satisfyoperator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xyomegafloatThe angular frequency of the system
requireddxesdx_lists2_tGrid parameters
required[dx_e, dx_h]as described inmeanas.fdmath.types(2D)rminfloatRadius at the left edge of the simulation domain (at minimum 'x')
requiredepsilonvfdsliceVectorized dielectric constant grid
requiredmuvfdslice | NoneVectorized magnetic permeability grid (default 1 everywhere)
Noneprop_phasefloatPhase shift
(dz * corrected_wavenumber)over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).0Returns:
Type Descriptionvcfdslice_t
(e, h), where each field is vectorized, normalized,vcfdslice_tand contains all three vector components.
NotesThe normalization step is delegated to
\\[ \\frac{1}{2}\\int (E_r H_y^* - E_y H_r^*) \\, dr \\, dy. \\]_normalized_fields(...), which enforces unit forward power under the discrete inner productThe angular wavenumber
"}]} \ No newline at end of file diff --git a/standalone.html b/standalone.html index 102f6ad..9aa1714 100644 --- a/standalone.html +++ b/standalone.html @@ -367,10 +367,26 @@ conventions.mis first converted into the full three-component fields, then the overall complex phase and sign are fixed so the result is reproducible for symmetric modes.For most users, the tracked examples under
+examples/are the right entry point. They show the intended combinations of tools for solving complete problems.Relevant starting examples:
++
+- +
examples/fdtd.pyfor broadband pulse excitation and phasor extraction- +
examples/waveguide.pyfor guided phasor-domain FDTD/FDFD comparison- +
examples/waveguide_real.pyfor real-valued continuous-wave FDTD compared + against real fields reconstructed from an FDFD solution, including guided-core, + mode-weighted, and guided-mode / residual comparisons- +
examples/fdfd.pyfor direct frequency-domain waveguide excitationFor solver equivalence, prefer the phasor-based examples first. They compare +the extracted
++\omegacontent of the FDTD run directly against the FDFD +solution and are the main accuracy benchmarks in the test suite.
examples/waveguide_real.pyanswers a different, stricter question: how well a +late raw real snapshot matchesRe(E_\omega e^{i\omega t})on a monitor plane. +That diagnostic is useful, but it also includes orthogonal residual structure +that the phasor comparison intentionally filters out.The API pages are better read as a toolbox map and derivation reference:
-
- Use the FDTD API for time-domain stepping, CPML, and phasor - extraction.
+- Use the FDTD API for time-domain stepping, CPML, phasor + extraction, and real-field reconstruction from FDFD phasors.
- Use the FDFD API for driven frequency-domain solves and sparse operator algebra.
- Use the Waveguide API for mode solving, port sources, @@ -481,7 +497,7 @@ toolbox overview and API derivations.
iterations- int+int @@ -580,7 +596,7 @@ toolbox overview and API derivations.iterations- int+int @@ -698,7 +714,7 @@ toolbox overview and API derivations.how_many- int+int @@ -923,7 +939,7 @@ e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)epsilon- fdfield+fdfield @@ -939,7 +955,7 @@ e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)mu- fdfield | None+fdfield | None @@ -1064,7 +1080,7 @@ Seeoperators.eh_full.epsilon- fdfield+fdfield @@ -1080,7 +1096,7 @@ Seeoperators.eh_full.mu- fdfield | None+fdfield | None @@ -1104,7 +1120,7 @@ Seeoperators.eh_full.- Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]+Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] @@ -1114,7 +1130,7 @@ Seeoperators.eh_full.- Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]+Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] @@ -1192,7 +1208,7 @@ For use withe_full-- assumes that there is no magnetic currentmu - fdfield | None+fdfield | None @@ -1315,7 +1331,7 @@ For use with e.g.e_full.mu- fdfield | None+fdfield | None @@ -1412,7 +1428,7 @@ forcing the scattered-field region.TF_region- fdfield+fdfield @@ -1467,7 +1483,7 @@ forcing the scattered-field region.epsilon- fdfield+fdfield @@ -1483,7 +1499,7 @@ forcing the scattered-field region.mu- fdfield | None+fdfield | None @@ -1613,7 +1629,7 @@ instead.- Callable[[cfdfield, cfdfield], cfdfield_t]+Callable[[cfdfield, cfdfield], cfdfield_t] @@ -1623,7 +1639,7 @@ instead.- Callable[[cfdfield, cfdfield], cfdfield_t]+Callable[[cfdfield, cfdfield], cfdfield_t] @@ -1681,11 +1697,15 @@ themeanas.fdmath.typessubmodule for details.Wave operator - $$ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon $$
+ +\[ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon \]+del x (1/mu * del x) - omega**2 * epsilonfor use with the E-field, with wave equation - $$ (\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E = -\imath \omega J $$
+ +\[ (\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E = -\imath \omega J \]+(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * JTo make this matrix symmetric, use the preconditioners from
@@ -1742,7 +1762,7 @@ thee_full_preconditioners().meanas.fdmath.typessubmodule for details.epsilon- vfdfield | vcfdfield+vfdfield | vcfdfield @@ -1758,7 +1778,7 @@ themeanas.fdmath.typessubmodule for details.mu- vfdfield | None+vfdfield | None @@ -1774,7 +1794,7 @@ themeanas.fdmath.typessubmodule for details.pec- vfdfield | None+vfdfield | None @@ -1792,7 +1812,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -1915,11 +1935,15 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizeWave operator - $$ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu $$
+ +\[ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu \]+del x (1/epsilon * del x) - omega**2 * mufor use with the H-field, with wave equation - $$ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M $$
+ +\[ (\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E = \imath \omega M \]+(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * MParameters:
@@ -1975,7 +1999,7 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizeepsilon- vfdfield+vfdfield @@ -1991,7 +2015,7 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizemu- vfdfield | None+vfdfield | None @@ -2007,7 +2031,7 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizepec- vfdfield | None+vfdfield | None @@ -2025,7 +2049,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -2078,25 +2102,29 @@ The PMC is applied per-field-component (i.e.pmc.size == epsilon.sizeWave operator for
+[E, H]field representation. This operator implements Maxwell's - equations without cancelling out either E or H. The operator is -$$ \begin{bmatrix} - -\imath \omega \epsilon & \nabla \times \ + equations without cancelling out either E or H. The operator is\[ +\begin{bmatrix} + -\imath \omega \epsilon & \nabla \times \\ \nabla \times & \imath \omega \mu - \end{bmatrix} $$ +\end{bmatrix} +\]-[[-i * omega * epsilon, del x ], [del x, i * omega * mu]]for use with a field vector of the form
cat(vec(E), vec(H)): -$$ \begin{bmatrix} - -\imath \omega \epsilon & \nabla \times \ +for use with a field vector of the form
+cat(vec(E), vec(H)):\[ +\begin{bmatrix} + -\imath \omega \epsilon & \nabla \times \\ \nabla \times & \imath \omega \mu \end{bmatrix} - \begin{bmatrix} E \ + \begin{bmatrix} E \\ H \end{bmatrix} - = \begin{bmatrix} J \ + = \begin{bmatrix} J \\ -M - \end{bmatrix} $$ +\end{bmatrix} +\]Parameters:
@@ -2150,7 +2178,7 @@ $$ \begin{bmatrix}
epsilon- vfdfield+vfdfield @@ -2166,7 +2194,7 @@ $$ \begin{bmatrix}mu- vfdfield | None+vfdfield | None @@ -2182,7 +2210,7 @@ $$ \begin{bmatrix}pec- vfdfield | None+vfdfield | None @@ -2200,7 +2228,7 @@ The PEC is applied per-field-component (i.e.pec.size == epsilon.sizepmc- vfdfield | None+vfdfield | None @@ -2305,7 +2333,7 @@ For use withe_full()-- assumes that there is no magnetic currentmu- vfdfield | None+vfdfield | None @@ -2321,7 +2349,7 @@ For use withe_full()-- assumes that there is no magnetic currentpmc- vfdfield | None+vfdfield | None @@ -2425,7 +2453,7 @@ For use with eg.e_full().mu- vfdfield | None+vfdfield | None @@ -2701,7 +2729,7 @@ scattered-field region.TF_region- vfdfield+vfdfield @@ -2755,7 +2783,7 @@ scattered-field region.epsilon- vfdfield+vfdfield @@ -2771,7 +2799,7 @@ scattered-field region.mu- vfdfield | None+vfdfield | None @@ -2845,7 +2873,7 @@ the shifts mirror at the domain boundary; withTruethey wrap periomask- vfdfield+vfdfield @@ -2900,7 +2928,7 @@ the shifts mirror at the domain boundary; withTruethey wrap perioepsilon- vfdfield+vfdfield @@ -2916,7 +2944,7 @@ the shifts mirror at the domain boundary; withTruethey wrap periomu- vfdfield | None+vfdfield | None @@ -3054,7 +3082,7 @@ discussed inmeanas.fdmath.typesepsilon- vfdfield+vfdfield @@ -3070,7 +3098,7 @@ discussed inmeanas.fdmath.typesmu- vfdfield | None+vfdfield | None @@ -3086,7 +3114,7 @@ discussed inmeanas.fdmath.typespec- vfdfield | None+vfdfield | None @@ -3103,7 +3131,7 @@ discussed inmeanas.fdmath.typespmc- vfdfield | None+vfdfield | None @@ -3158,7 +3186,7 @@ discussed inmeanas.fdmath.typesmatrix_solver_opts- dict[str, Any] | None+dict[str, Any] | None @@ -3381,7 +3409,7 @@ customize the PML parameters.shape- Sequence[int]+Sequence[int] @@ -3397,7 +3425,7 @@ customize the PML parameters.thicknesses- Sequence[int]+Sequence[int] @@ -3541,7 +3569,7 @@ customize the PML parameters.axis- int+int @@ -3557,7 +3585,7 @@ customize the PML parameters.polarity- int+int @@ -3606,7 +3634,7 @@ customize the PML parameters.thickness- int+int @@ -3782,7 +3810,7 @@ customize the PML parameters.padded_size- list[int] | int | None+list[int] | int | None @@ -3808,7 +3836,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3818,7 +3846,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3831,7 +3859,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3844,7 +3872,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3857,7 +3885,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3869,7 +3897,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3882,7 +3910,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -3999,7 +4027,7 @@ customize the PML parameters.padded_size- list[int] | int | None+list[int] | int | None @@ -4025,7 +4053,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4035,7 +4063,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4047,7 +4075,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -4059,7 +4087,7 @@ customize the PML parameters.- dict[str, Any]+dict[str, Any] @@ -6120,10 +6148,10 @@ E_z &= \frac{1}{- \omega \beta \epsilon_{zz}} (( \(\beta\) to changes in the dielectric structure \(\epsilon\).The output is a vector of the same size as
-vec(epsilon), with each element specifying the sensitivity ofwavenumberto changes in the corresponding element invec(epsilon), i.e.\[sens_{i} = \frac{\partial\beta}{\partial\epsilon_i}\]+\[ sens_{i} = \frac{\partial\beta}{\partial\epsilon_i} \]An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
-\[\beta^2 E_{xy} = A_E E_{xy}\]+\[ \beta^2 E_{xy} = A_E E_{xy} \]where \(A_E\) is the waveguide operator from
@@ -6338,7 +6366,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lefoperator_e(), and \(E_{xy} = \begin{bmatrix} E_x \\ E_y \end{bmatrix}\), we can differentiate with respect to one of the \(\epsilon\) elements (i.e. at one Yee grid point), \(\epsilon_i\):mode_numbers- Sequence[int]+Sequence[int] @@ -6423,7 +6451,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lefmode_margin- int+int @@ -6497,7 +6525,7 @@ elementwise multiplications \(\vec{sens} = \vec{v}_{lefmode_number- int+int @@ -6790,7 +6818,7 @@ solve for an eigenmode propagating through that slice.mode_number- int+int @@ -6843,7 +6871,7 @@ solve for an eigenmode propagating through that slice.axis- int+int @@ -6859,7 +6887,7 @@ solve for an eigenmode propagating through that slice.polarity- int+int @@ -6875,7 +6903,7 @@ solve for an eigenmode propagating through that slice.slices- Sequence[slice]+Sequence[slice] @@ -6892,7 +6920,7 @@ solve for an eigenmode propagating through that slice.epsilon- fdfield+fdfield @@ -6908,7 +6936,7 @@ solve for an eigenmode propagating through that slice.mu- fdfield | None+fdfield | None @@ -6932,7 +6960,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -6942,7 +6970,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -6954,7 +6982,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -6966,7 +6994,7 @@ solve for an eigenmode propagating through that slice.- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -6979,7 +7007,7 @@ propagation axis- dict[str, complex | NDArray[complexfloating]]+dict[str, complex | NDArray[complexfloating]] @@ -7033,7 +7061,7 @@ necessary to position a unidirectional source at the slice location.E- cfdfield+cfdfield @@ -7102,7 +7130,7 @@ necessary to position a unidirectional source at the slice location.axis- int+int @@ -7118,7 +7146,7 @@ necessary to position a unidirectional source at the slice location.polarity- int+int @@ -7134,7 +7162,7 @@ necessary to position a unidirectional source at the slice location.slices- Sequence[slice]+Sequence[slice] @@ -7151,7 +7179,7 @@ necessary to position a unidirectional source at the slice location.mu- fdfield | None+fdfield | None @@ -7306,7 +7334,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)axis- int+int @@ -7322,7 +7350,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)polarity- int+int @@ -7338,7 +7366,7 @@ B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)slices- Sequence[slice]+Sequence[slice] @@ -7420,7 +7448,7 @@ where it is valid.E- cfdfield+cfdfield @@ -7473,7 +7501,7 @@ where it is valid.axis- int+int @@ -7489,7 +7517,7 @@ where it is valid.polarity- int+int @@ -7505,7 +7533,7 @@ where it is valid.slices- Sequence[slice]+Sequence[slice] @@ -7975,7 +8003,7 @@ the fields, withbeta = angular_wavenumber / rmin).mode_number- int+int @@ -8491,15 +8519,15 @@ from E_r and E_y in order to then calculate E_z.Returns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
-This operator is created directly from the initial coordinate-transformed equations: -$$ +
This operator is created directly from the initial coordinate-transformed equations:
+\[ \begin{aligned} --\imath \omega \mu_{rr} H_r &= \tilde{\partial}y E_z + \imath \beta T_a^{-1} E_y, \ --\imath \omega \mu E_r - - T_b^{-1} \tilde{\partial}} H_y &= -\imath \beta T_b^{-1r (T_a E_z), \ --\imath \omega \mu_y E_r, +-\imath \omega \mu_{rr} H_r &= \tilde{\partial}_y E_z + \imath \beta T_a^{-1} E_y, \\ +-\imath \omega \mu_{yy} H_y &= -\imath \beta T_b^{-1} E_r + - T_b^{-1} \tilde{\partial}_r (T_a E_z), \\ +-\imath \omega \mu_{zz} H_z &= \tilde{\partial}_r E_y - \tilde{\partial}_y E_r, \end{aligned} -$$} H_z &= \tilde{\partial}_r E_y - \tilde{\partial +\]Parameters:
@@ -8942,7 +8970,8 @@ we have
The
dx_min,dy_min,dz_minshould be the minimum value across both the base and derived grids.Poynting Vector and Energy Conservation¶
Let
-\[ \begin{aligned} +\[ +\begin{aligned} \tilde{S}_{l, l', \vec{r}} &=& &\tilde{E}_{l, \vec{r}} \otimes \hat{H}_{l', \vec{r} + \frac{1}{2}} \\ &=& &\vec{x} (\tilde{E}^y_{l,m+1,n,p} \hat{H}^z_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^z_{l,m+1,n,p} \hat{H}^y_{l', \vec{r} + \frac{1}{2}}) \\ & &+ &\vec{y} (\tilde{E}^z_{l,m,n+1,p} \hat{H}^x_{l',\vec{r} + \frac{1}{2}} - \tilde{E}^x_{l,m,n+1,p} \hat{H}^z_{l', \vec{r} + \frac{1}{2}}) \\ @@ -9041,13 +9070,40 @@ extract the frequency-domain response by performing an on-the-fly Fourier transf of the time-domain fields.
accumulate_phasorinmeanas.fdtd.phasorperforms the phase accumulation for one or more target frequencies, while leaving source normalization and simulation-loop -policy to the caller. Convenience wrappersaccumulate_phasor_e, -accumulate_phasor_h, andaccumulate_phasor_japply the usual Yee time offsets. +policy to the caller.temporal_phasor(...)andtemporal_phasor_scale(...)+apply the same Fourier sum to a scalar waveform, which is useful when a pulsed +source envelope must be normalized before being applied to a point source or +mode source.real_injection_scale(...)is the corresponding helper for the +common real-valued injection patternnumpy.real(scale * waveform). Convenience +wrappersaccumulate_phasor_e,accumulate_phasor_h, andaccumulate_phasor_j+apply the usual Yee time offsets.reconstruct_real(...)and the corresponding +reconstruct_real_e/h/j(...)wrappers perform the inverse operation, converting +phasors back into real-valued field snapshots at explicit Yee-aligned times. +For scalaromega, the reconstruction helpers accept either a plain field +phasor or the batched(1, *sample_shape)form used by the accumulator helpers. The helpers accumulate\[ \Delta_t \sum_l w_l e^{-i \omega t_l} f_l \]with caller-provided sample times and weights. In this codebase the matching electric-current convention is typically
+E -= dt * J / epsilonin FDTD and-i \omega Jon the right-hand side of the FDFD wave equation.For FDTD/FDFD equivalence, this phasor path is the primary comparison workflow. +It isolates the guided
++\omegaresponse that the frequency-domain solver +targets directly, regardless of whether the underlying FDTD run used real- or +complex-valued fields.For exact pulsed FDTD/FDFD equivalence it is often simplest to keep the +injected source, fields, and CPML auxiliary state complex-valued. The +
+real_injection_scale(...)helper is instead for the more ordinary one-run +real-valued source path, where the intended positive-frequency waveform is +injected throughnumpy.real(scale * waveform)and any remaining negative- +frequency leakage is controlled by the pulse bandwidth and run length.+
reconstruct_real(...)is for a different question: given a phasor, what late +real-valued field snapshot should it produce? That raw-snapshot comparison is +stricter and noisier because a monitor plane generally contains both the guided +field and the remaining orthogonal content,\[ E_{\text{monitor}} = E_{\text{guided}} + E_{\text{residual}} . \]+Phasor/modal comparisons mostly validate the guided
+\omegaterm. Raw +real-field comparisons expose both terms at once, so they should be treated as +secondary diagnostics rather than the main solver-equivalence benchmark.The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written
\[ f_r(t) = (1 - \frac{1}{2} (\omega (t - \tau))^2) e^{-(\frac{\omega (t - \tau)}{2})^2} \]@@ -9359,7 +9415,7 @@ withNone.axis- int+int @@ -9375,7 +9431,7 @@ withNone.polarity- int+int @@ -9408,7 +9464,7 @@ withNone.thickness- int+int @@ -9528,7 +9584,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -9538,7 +9594,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -9550,7 +9606,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -9562,7 +9618,7 @@ withNone.- dict[str, Any]+dict[str, Any] @@ -9610,7 +9666,7 @@ withNone.cpml_params- Sequence[Sequence[dict[str, Any] | None]]+Sequence[Sequence[dict[str, Any] | None]] @@ -9665,7 +9721,7 @@ Entries are the dictionaries returned bycpml_params(...); useepsilon- fdfield+fdfield @@ -9813,19 +9869,19 @@ boundaries of the correspondingUcell. For example, assert_close(sum(planes), du_half_h2e[mask])(see
-meanas.tests.test_fdtd.test_poynting_planes)The full relationship is -$$ +
The full relationship is
+\[ \begin{aligned} (U_{l+\frac{1}{2}} - U_l) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}{l, l + \frac{1}{2}} \ - - \hat{H}}{2}} \cdot \hat{Ml \ - - \tilde{E}_l \cdot \tilde{J} \ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l + \frac{1}{2}} \\ + - \hat{H}_{l+\frac{1}{2}} \cdot \hat{M}_l \\ + - \tilde{E}_l \cdot \tilde{J}_{l+\frac{1}{2}} \\ (U_l - U_{l-\frac{1}{2}}) / \Delta_t - &= -\hat{\nabla} \cdot \tilde{S}}{2}{l, l - \frac{1}{2}} \ - - \hat{H}}{2}} \cdot \hat{Ml \ - - \tilde{E}_l \cdot \tilde{J} \ + &= -\hat{\nabla} \cdot \tilde{S}_{l, l - \frac{1}{2}} \\ + - \hat{H}_{l-\frac{1}{2}} \cdot \hat{M}_l \\ + - \tilde{E}_l \cdot \tilde{J}_{l-\frac{1}{2}} \\ \end{aligned} -$$}{2} +\]These equalities are exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary. (See
meanas.fdtddocs for derivation.)Parameters:
@@ -9844,7 +9900,7 @@ No time- or spatial-averaging is necessary. (Seemeanas.fdtddocs fe- fdfield+fdfield @@ -9860,7 +9916,7 @@ No time- or spatial-averaging is necessary. (Seemeanas.fdtddocs fh- fdfield+fdfield @@ -10001,7 +10057,7 @@ energy cell.e0- fdfield+fdfield @@ -10017,7 +10073,7 @@ energy cell.h1- fdfield+fdfield @@ -10033,7 +10089,7 @@ energy cell.e2- fdfield+fdfield @@ -10049,7 +10105,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -10065,7 +10121,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -10154,7 +10210,7 @@ energy cell.h0- fdfield+fdfield @@ -10170,7 +10226,7 @@ energy cell.e1- fdfield+fdfield @@ -10186,7 +10242,7 @@ energy cell.h2- fdfield+fdfield @@ -10202,7 +10258,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -10218,7 +10274,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -10309,7 +10365,7 @@ energy cell.e0- fdfield+fdfield @@ -10325,7 +10381,7 @@ energy cell.h1- fdfield+fdfield @@ -10341,7 +10397,7 @@ energy cell.e2- fdfield+fdfield @@ -10357,7 +10413,7 @@ energy cell.h3- fdfield+fdfield @@ -10373,7 +10429,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -10389,7 +10445,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -10480,7 +10536,7 @@ energy cell.h0- fdfield+fdfield @@ -10496,7 +10552,7 @@ energy cell.e1- fdfield+fdfield @@ -10512,7 +10568,7 @@ energy cell.h2- fdfield+fdfield @@ -10528,7 +10584,7 @@ energy cell.e3- fdfield+fdfield @@ -10544,7 +10600,7 @@ energy cell.epsilon- fdfield | None+fdfield | None @@ -10560,7 +10616,7 @@ energy cell.mu- fdfield | None+fdfield | None @@ -10647,7 +10703,7 @@ half-step energy balance) even though it directly changesEonly onj0- fdfield+fdfield @@ -10664,7 +10720,7 @@ current work term.e1- fdfield+fdfield @@ -10751,7 +10807,7 @@ current work term.ee- fdfield+fdfield @@ -10767,7 +10823,7 @@ current work term.hh- fdfield+fdfield @@ -10783,7 +10839,7 @@ current work term.epsilon- fdfield | float | None+fdfield | float | None @@ -10799,7 +10855,7 @@ current work term.mu- fdfield | float | None+fdfield | float | None @@ -10886,6 +10942,18 @@ the sampling policy. The accumulated quantity isaccumulate_phasor_h(..., step=l)forH_{l + 1/2}- +
accumulate_phasor_j(..., step=l)forJ_{l + 1/2}
temporal_phasor(...)andtemporal_phasor_scale(...)apply the same Fourier +sum to a 1D scalar waveform. They are useful for normalizing a pulsed source +before that scalar waveform is applied to a point source or spatial mode source. +real_injection_scale(...)is a companion helper for the common real-valued +injection patternnumpy.real(scale * waveform), wherewaveformis the +analytic positive-frequency signal and the injected real current should still +produce a desired phasor response. +reconstruct_real(...)and itsE/H/Jwrappers perform the inverse operation: +they turn one or more phasors back into real-valued field snapshots at explicit +Yee-aligned sample times. For a scalar target frequency they accept either a +plain field phasor or the batched(1, *sample_shape)form used elsewhere in +this module.These helpers do not choose warmup/accumulation windows or pulse-envelope normalization. They also do not impose a current sign convention. In this codebase, electric-current injection normally appears as
E -= dt * J / epsilon, @@ -10924,6 +10992,114 @@ factor was derived from a discrete sum that already includesdt, pa+++
+temporal_phasor +¶+temporal_phasor( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, +) -> NDArray[numpy.complexfloating] +++Fourier-project a 1D temporal waveform onto one or more angular frequencies.
+The returned quantity is
++dt * sum(exp(-1j * omega * t_step) * samples[step_index]) +where
+t_step = (start_step + step_index + offset_steps) * dt.+++
+temporal_phasor_scale +¶+temporal_phasor_scale( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, + target: ArrayLike = 1.0, +) -> NDArray[numpy.complexfloating] +++Return the scalar multiplier that gives a desired temporal phasor response.
+The returned scale satisfies
++temporal_phasor(scale * samples, omegas, dt, ...) == target +for each target frequency. The result keeps a leading frequency axis even +when
+omegasis scalar.+++
+real_injection_scale +¶+real_injection_scale( + samples: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + *, + start_step: int = 0, + offset_steps: float = 0.0, + target: ArrayLike = 1.0, +) -> NDArray[numpy.complexfloating] +++Return the scale for a real-valued injection built from an analytic waveform.
+If the time-domain source is applied as
++numpy.real(scale * samples[step]) +then the desired positive-frequency phasor is obtained by compensating for +the 1/2 factor between the real-valued source and its analytic component:
++scale = 2 * target / temporal_phasor(samples, ...) +This helper normalizes only the intended positive-frequency component. Any +residual negative-frequency leakage is controlled by the waveform design and +the accumulation window.
++++
+reconstruct_real +¶+reconstruct_real( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, + *, + offset_steps: float = 0.0, +) -> NDArray[numpy.floating] +++Reconstruct a real-valued field snapshot from one or more phasors.
+The returned quantity is
++real(phasor * exp(1j * omega * t_step)) +where
+t_step = (step + offset_steps) * dt.For multi-frequency inputs, the leading frequency axis is preserved. For a +scalar
+omega, callers may pass either(1, *sample_shape)or +sample_shape; the return shape matches that choice.+@@ -10986,6 +11162,60 @@ factor was derived from a discrete sum that already includes
accumulate_phasor_e ¶dt, paAccumulate a current sample corresponding to
J_{step + 1/2}.+++
+reconstruct_real_e +¶+reconstruct_real_e( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] +++Reconstruct a real E-field snapshot taken at integer timestep
+step.+++
+reconstruct_real_h +¶+reconstruct_real_h( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] +++Reconstruct a real H-field snapshot corresponding to
+H_{step + 1/2}.++
+reconstruct_real_j +¶+reconstruct_real_j( + phasors: ArrayLike, + omegas: float + | complex + | Sequence[float | complex] + | NDArray, + dt: float, + step: int, +) -> NDArray[numpy.floating] +++Reconstruct a real current snapshot corresponding to
+J_{step + 1/2}.fdmath¶
@@ -11028,8 +11258,9 @@ series of other matrices). which covers a superset of this material with similar notation and more detail.Scalar derivatives and cell shifts¶
Define the discrete forward derivative as - $$ [\tilde{\partial}x f] - f_m) $$ - where }{2}} = \frac{1}{\Delta_{x, m}} (f_{m + 1\(f\) is a function defined at discrete locations on the x-axis (labeled using \(m\)). + +
\[ [\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.
@@ -11038,8 +11269,9 @@ along the x-axis, the forward derivative isderiv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]Likewise, discrete reverse derivative is - $$ [\hat{\partial}x f ]) $$ - or}{2}} = \frac{1}{\Delta_{x, m}} (f_{m} - f_{m - 1
+ +\[ [\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 @@ -11074,7 +11306,9 @@ Periodic boundaries are used here and elsewhere unless otherwise noted. 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}) $$
+ +\[ \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. If the positions labeled with \(m\) are considered the "base" or "original" grid, @@ -11086,12 +11320,17 @@ See the
Grid descriptionsection below for additional information o and generalization to three dimensions.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] + - \vec{y} [\tilde{\partial}}{2},n,py f] + - \vec{z} [\tilde{\partial}}{2},pz f] $$ - $$ [\hat{\nabla} f]}{2}{m,n,p} = \vec{x} [\hat{\partial}_x f] + - \vec{y} [\hat{\partial}}{2},n,py f] + - \vec{z} [\hat{\partial}}{2},pz f] $$}{2}
+
+\[ + [\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], @@ -11114,12 +11353,17 @@ 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 + +
+\[ + \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}}{2},n,p{m,n,p} = \vec{x} g^x + + \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}} $$}{2},n,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) /: /| @@ -11135,14 +11379,18 @@ on the direction of the shift. We write it asDivergences¶
There are also two divergences,
-$$ d_{n,m,p} = [\tilde{\nabla} \cdot \hat{g}]{n,m,p} - = [\tilde{\partial}_x g^x] + - [\tilde{\partial}y g^y] + - [\tilde{\partial}z g^z] $$
-$$ d_{n,m,p} = [\hat{\nabla} \cdot \tilde{g}]{n,m,p} - = [\hat{\partial}_x g^x] + - [\hat{\partial}y g^y] + - [\hat{\partial}z g^z] $$
+\[ + 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] + @@ -11181,16 +11429,20 @@ is defined at the back-vector's (fore-vector's) location Curls¶The two curls are then
-$$ \begin{aligned} - \hat{h}{m + \frac{1}{2}, n + \frac{1}{2}, p + \frac{1}{2}} &= \ - [\tilde{\nabla} \times \tilde{g}] &= - \vec{x} (\tilde{\partial}}{2}, n + \frac{1}{2}, p + \frac{1}{2}y g^z}{2}} - \tilde{\partialz g^y) \ - &+ \vec{y} (\tilde{\partial}}{2},pz g^x}{2},n,p} - \tilde{\partialx g^z) \ - &+ \vec{z} (\tilde{\partial}}{2}x g^y}{2},p} - \tilde{\partialy g^z) - \end{aligned} $$}{2},n,p
+\[ + \begin{aligned} + \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{aligned} + \]and
-$$ \tilde{h}{m - \frac{1}{2}, n - \frac{1}{2}, p - \frac{1}{2}} = - [\hat{\nabla} \times \hat{g}] $$}{2}, n - \frac{1}{2}, p - \frac{1}{2}
+\[ + \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})\) @@ -11228,19 +11480,23 @@ curl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],
Maxwell's Equations¶
If we discretize both space (m,n,p) and time (l), Maxwell's equations become
-$$ \begin{aligned} - \tilde{\nabla} \times \tilde{E}{l,\vec{r}} &= -\tilde{\partial}_t \hat{B} - - \hat{M}}{2}, \vec{r} + \frac{1}{2}{l, \vec{r} + \frac{1}{2}} \ - \hat{\nabla} \times \hat{H}}{2},\vec{r} + \frac{1}{2}} &= \hat{\partialt \tilde{D} - + \tilde{J}}{l-\frac{1}{2},\vec{r}} \ - \tilde{\nabla} \cdot \hat{B} &= 0 \ - \hat{\nabla} \cdot \tilde{D}}{2}, \vec{r} + \frac{1}{2}{l,\vec{r}} &= \rho - \end{aligned} $$}
+\[ + \begin{aligned} + \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, \vec{r} + \frac{1}{2}} \\ + \hat{\nabla} \times \hat{H}_{l-\frac{1}{2},\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{aligned} + \]with
-$$ \begin{aligned} - \hat{B}{\vec{r}} &= \mu} + \frac{1}{2}} \cdot \hat{H{\vec{r} + \frac{1}{2}} \ - \tilde{D} - \end{aligned} $$}} &= \epsilon_{\vec{r}} \cdot \tilde{E}_{\vec{r}
+\[ + \begin{aligned} + \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{aligned} + \]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, @@ -11302,8 +11558,9 @@ r - 1/2 = | / | /
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.
+ +\[ \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, @@ -11368,11 +11625,11 @@ and setting \(\hat{M}\) to zero, we can form the \end{aligned} \]
and we get
-\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]+\[ \tilde{\nabla}^2 \tilde{E}_{\vec{r}} + \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 \]We can convert this to three scalar-wave equations of the form
\[ (\tilde{\nabla}^2 + K^2) \phi_{\vec{r}} = 0 \]with \(K^2 = \Omega^2 \mu \epsilon\). Now we let
-\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]+\[ \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x + k_y n \Delta_y + k_z p \Delta_z)} \]resulting in
\[ \begin{aligned} @@ -11589,8 +11846,8 @@ mu = [mu_xx, mu_yy, mu_zz] \epsilon = \begin{bmatrix} \epsilon_{xx} & 0 & 0 \\ 0 & \epsilon_{yy} & 0 \\ 0 & 0 & \epsilon_{zz} \end{bmatrix} -$$ -$$ +\]+\[ \mu = \begin{bmatrix} \mu_{xx} & 0 & 0 \\ 0 & \mu_{yy} & 0 \\ 0 & 0 & \mu_{zz} \end{bmatrix} @@ -11964,7 +12221,7 @@ normalized results are needed.axis- int+int @@ -11980,7 +12237,7 @@ normalized results are needed.shape- Sequence[int]+Sequence[int] @@ -11996,7 +12253,7 @@ normalized results are needed.shift_distance- int+int @@ -12059,7 +12316,7 @@ boundary conditions applied to the cells beyond the receding edge.axis- int+int @@ -12075,7 +12332,7 @@ boundary conditions applied to the cells beyond the receding edge.shape- Sequence[int]+Sequence[int] @@ -12091,7 +12348,7 @@ boundary conditions applied to the cells beyond the receding edge.shift_distance- int+int @@ -12382,7 +12639,7 @@ portions of the operator on the left side of the cross product.axis- int+int @@ -12398,7 +12655,7 @@ portions of the operator on the left side of the cross product.shape- Sequence[int]+Sequence[int] @@ -12458,7 +12715,7 @@ portions of the operator on the left side of the cross product.axis- int+int @@ -12474,7 +12731,7 @@ portions of the operator on the left side of the cross product.shape- Sequence[int]+Sequence[int] @@ -12827,7 +13084,7 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.shape- Sequence[int]+Sequence[int] @@ -12843,7 +13100,7 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.nvdim- int+int @@ -13184,4 +13441,4 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.