4608 lines
228 KiB
Plaintext
4608 lines
228 KiB
Plaintext
<!DOCTYPE html>
|
||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="generator" content="pandoc" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||
<meta name="description" content="API documentation for modules: meanas, meanas.eigensolvers, meanas.fdfd, meanas.fdfd.bloch, meanas.fdfd.farfield, meanas.fdfd.functional, meanas.fdfd.operators, meanas.fdfd.scpml, meanas.fdfd.solvers, meanas.fdfd.waveguide_2d, meanas.fdfd.waveguide_3d, meanas.fdfd.waveguide_cyl, meanas.fdmath, meanas.fdmath.functional, meanas.fdmath.operators, meanas.fdmath.types, meanas.fdmath.vectorization, meanas.fdtd, meanas.fdtd.base, meanas.fdtd.boundaries, meanas.fdtd.energy, meanas.fdtd.pml, meanas.test, meanas.test.conftest, meanas.test.test_fdfd, meanas.test.test_fdfd_pml, meanas.test.test_fdtd, meanas.test.utils." />
|
||
<title>meanas</title>
|
||
<style>
|
||
code{white-space: pre-wrap;}
|
||
span.smallcaps{font-variant: small-caps;}
|
||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||
div.column{flex: auto; overflow-x: auto;}
|
||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||
/* The extra [class] is a hack that increases specificity enough to
|
||
override a similar rule in reveal.js */
|
||
ul.task-list[class]{list-style: none;}
|
||
ul.task-list li input[type="checkbox"] {
|
||
font-size: inherit;
|
||
width: 0.8em;
|
||
margin: 0 0.8em 0.2em -1.6em;
|
||
vertical-align: middle;
|
||
}
|
||
/* CSS for syntax highlighting */
|
||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||
pre > code.sourceCode > span { line-height: 1.25; }
|
||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||
.sourceCode { overflow: visible; }
|
||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||
div.sourceCode { margin: 1em 0; }
|
||
pre.sourceCode { margin: 0; }
|
||
@media screen {
|
||
div.sourceCode { overflow: auto; }
|
||
}
|
||
@media print {
|
||
pre > code.sourceCode { white-space: pre-wrap; }
|
||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||
}
|
||
pre.numberSource code
|
||
{ counter-reset: source-line 0; }
|
||
pre.numberSource code > span
|
||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||
pre.numberSource code > span > a:first-child::before
|
||
{ content: counter(source-line);
|
||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||
border: none; display: inline-block;
|
||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||
-khtml-user-select: none; -moz-user-select: none;
|
||
-ms-user-select: none; user-select: none;
|
||
padding: 0 4px; width: 4em;
|
||
color: #aaaaaa;
|
||
}
|
||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||
div.sourceCode
|
||
{ }
|
||
@media screen {
|
||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||
}
|
||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||
code span.at { color: #7d9029; } /* Attribute */
|
||
code span.bn { color: #40a070; } /* BaseN */
|
||
code span.bu { color: #008000; } /* BuiltIn */
|
||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||
code span.ch { color: #4070a0; } /* Char */
|
||
code span.cn { color: #880000; } /* Constant */
|
||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||
code span.dt { color: #902000; } /* DataType */
|
||
code span.dv { color: #40a070; } /* DecVal */
|
||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||
code span.ex { } /* Extension */
|
||
code span.fl { color: #40a070; } /* Float */
|
||
code span.fu { color: #06287e; } /* Function */
|
||
code span.im { color: #008000; font-weight: bold; } /* Import */
|
||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||
code span.op { color: #666666; } /* Operator */
|
||
code span.ot { color: #007020; } /* Other */
|
||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||
code span.ss { color: #bb6688; } /* SpecialString */
|
||
code span.st { color: #4070a0; } /* String */
|
||
code span.va { color: #19177c; } /* Variable */
|
||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||
</style>
|
||
<link rel="stylesheet" href="pdoc_templates/pdoc.css" />
|
||
</head>
|
||
<body>
|
||
<header id="title-block-header">
|
||
<h1 class="title">meanas</h1>
|
||
</header>
|
||
<hr />
|
||
<h1 id="meanas">Module <code>meanas</code></h1>
|
||
<h1 id="meanas-1">meanas</h1>
|
||
<p><strong>meanas</strong> is a python package for electromagnetic
|
||
simulations</p>
|
||
<p>** UNSTABLE / WORK IN PROGRESS **</p>
|
||
<p>Formerly known as <a
|
||
href="https://mpxd.net/code/jan/fdfd_tools">fdfd_tools</a>.</p>
|
||
<p>This package is intended for building simulation inputs, analyzing
|
||
simulation outputs, and running short simulations on unspecialized
|
||
hardware. It is designed to provide tooling and a baseline for other,
|
||
high-performance purpose- and hardware-specific solvers.</p>
|
||
<p><strong>Contents</strong></p>
|
||
<ul>
|
||
<li>Finite difference frequency domain (FDFD)
|
||
<ul>
|
||
<li>Library of sparse matrices for representing the electromagnetic wave
|
||
equation in 3D, as well as auxiliary matrices for conversion between
|
||
fields</li>
|
||
<li>Waveguide mode operators</li>
|
||
<li>Waveguide mode eigensolver</li>
|
||
<li>Stretched-coordinate PML boundaries (SCPML)</li>
|
||
<li>Functional versions of most operators</li>
|
||
<li>Anisotropic media (limited to diagonal elements eps_xx, eps_yy,
|
||
eps_zz, mu_xx, …)</li>
|
||
<li>Arbitrary distributions of perfect electric and magnetic conductors
|
||
(PEC / PMC)</li>
|
||
</ul></li>
|
||
<li>Finite difference time domain (FDTD)
|
||
<ul>
|
||
<li>Basic Maxwell time-steps</li>
|
||
<li>Poynting vector and energy calculation</li>
|
||
<li>Convolutional PMLs</li>
|
||
</ul></li>
|
||
</ul>
|
||
<p>This package does <em>not</em> provide a fast matrix solver, though
|
||
by default <code><a
|
||
href="#meanas.fdfd.solvers.generic">generic()</a>(…)</code> will call
|
||
<code>scipy.sparse.linalg.qmr(…)</code> to perform a solve. For 2D FDFD
|
||
problems this should be fine; likewise, the waveguide mode solver uses
|
||
scipy’s eigenvalue solver, with reasonable results.</p>
|
||
<p>For solving large (or 3D) FDFD problems, I recommend a GPU-based
|
||
iterative solver, such as <a
|
||
href="https://mpxd.net/code/jan/opencl_fdfd">opencl_fdfd</a> or those
|
||
included in <a href="http://icl.cs.utk.edu/magma/index.html">MAGMA</a>.
|
||
Your solver will need the ability to solve complex symmetric
|
||
(non-Hermitian) linear systems, ideally with double precision.</p>
|
||
<ul>
|
||
<li><a href="https://mpxd.net/code/jan/meanas">Source
|
||
repository</a></li>
|
||
<li><a href="https://pypi.org/project/meanas">PyPI</a></li>
|
||
<li><a href="https://github.com/anewusername/meanas">Github
|
||
mirror</a></li>
|
||
</ul>
|
||
<h2 id="installation">Installation</h2>
|
||
<p><strong>Requirements:</strong></p>
|
||
<ul>
|
||
<li>python >=3.11</li>
|
||
<li>numpy</li>
|
||
<li>scipy</li>
|
||
</ul>
|
||
<p>Install from PyPI with pip:</p>
|
||
<div class="sourceCode" id="cb1"><pre
|
||
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">pip3</span> install <span class="st">'meanas[dev]'</span></span></code></pre></div>
|
||
<h3 id="development-install">Development install</h3>
|
||
<p>Install python3 and git:</p>
|
||
<div class="sourceCode" id="cb2"><pre
|
||
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># This is for Debian/Ubuntu/other-apt-based systems; you may need an alternative command</span></span>
|
||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> apt install python3 build-essential python3-dev git</span></code></pre></div>
|
||
<p>In-place development install:</p>
|
||
<div class="sourceCode" id="cb3"><pre
|
||
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Download using git</span></span>
|
||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> clone https://mpxd.net/code/jan/meanas.git</span>
|
||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co"># If you'd like to create a virtualenv, do so:</span></span>
|
||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="ex">python3</span> <span class="at">-m</span> venv my_venv</span>
|
||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co"># If you are using a virtualenv, activate it</span></span>
|
||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="bu">source</span> my_venv/bin/activate</span>
|
||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co"># Install in-place (-e, editable) from ./meanas, including development dependencies ([dev])</span></span>
|
||
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="ex">pip3</span> install <span class="at">--user</span> <span class="at">-e</span> <span class="st">'./meanas[dev]'</span></span>
|
||
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="co"># Run tests</span></span>
|
||
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> meanas</span>
|
||
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="ex">python3</span> <span class="at">-m</span> pytest <span class="at">-rsxX</span> <span class="kw">|</span> <span class="fu">tee</span> test_results.txt</span></code></pre></div>
|
||
<h4 id="see-also">See also:</h4>
|
||
<ul>
|
||
<li><a href="https://git-scm.com/book/en/v2">git book</a></li>
|
||
<li><a href="https://docs.python.org/3/tutorial/venv.html">venv
|
||
documentation</a></li>
|
||
<li><a href="https://docs.python.org/3/reference/index.html">python
|
||
language reference</a></li>
|
||
<li><a href="https://docs.python.org/3/library/index.html">python
|
||
standard library</a></li>
|
||
</ul>
|
||
<h2 id="use">Use</h2>
|
||
<p>See <code>examples/</code> for some simple examples; you may need
|
||
additional packages such as <a
|
||
href="https://mpxd.net/code/jan/gridlock">gridlock</a> to run the
|
||
examples.</p>
|
||
<h2 id="sub-modules">Sub-modules</h2>
|
||
<ul>
|
||
<li><a href="#meanas.eigensolvers">meanas.eigensolvers</a></li>
|
||
<li><a href="#meanas.fdfd">meanas.fdfd</a></li>
|
||
<li><a href="#meanas.fdmath">meanas.fdmath</a></li>
|
||
<li><a href="#meanas.fdtd">meanas.fdtd</a></li>
|
||
<li><a href="#meanas.test">meanas.test</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.eigensolvers">Module
|
||
<code>meanas.eigensolvers</code></h1>
|
||
<p>Solvers for eigenvalue / eigenvector problems</p>
|
||
<h2 id="functions">Functions</h2>
|
||
<h3 id="meanas.eigensolvers.power_iteration">Function
|
||
<code>power_iteration</code></h3>
|
||
<blockquote>
|
||
<p><code>def power_iteration(operator: scipy.sparse._matrix.spmatrix, guess_vector: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]] | None = None, iterations: int = 20) -> tuple[complex, numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Use power iteration to estimate the dominant eigenvector of a
|
||
matrix.</p>
|
||
<p>Args —–= <strong><code>operator</code></strong> : Matrix to
|
||
analyze.</p>
|
||
<dl>
|
||
<dt><strong><code>guess_vector</code></strong></dt>
|
||
<dd>
|
||
Starting point for the eigenvector. Default is a randomly chosen vector.
|
||
</dd>
|
||
<dt><strong><code>iterations</code></strong></dt>
|
||
<dd>
|
||
Number of iterations to perform. Default 20.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= (Largest-magnitude eigenvalue, Corresponding eigenvector
|
||
estimate)</p>
|
||
<h3 id="meanas.eigensolvers.rayleigh_quotient_iteration">Function
|
||
<code>rayleigh_quotient_iteration</code></h3>
|
||
<blockquote>
|
||
<p><code>def rayleigh_quotient_iteration(operator: scipy.sparse._matrix.spmatrix | scipy.sparse.linalg._interface.LinearOperator, guess_vector: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], iterations: int = 40, tolerance: float = 1e-13, solver: collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]] | None = None) -> tuple[complex, numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Use Rayleigh quotient iteration to refine an eigenvector guess.</p>
|
||
<p>Args —–= <strong><code>operator</code></strong> : Matrix to
|
||
analyze.</p>
|
||
<dl>
|
||
<dt><strong><code>guess_vector</code></strong></dt>
|
||
<dd>
|
||
Eigenvector to refine.
|
||
</dd>
|
||
<dt><strong><code>iterations</code></strong></dt>
|
||
<dd>
|
||
Maximum number of iterations to perform. Default 40.
|
||
</dd>
|
||
<dt><strong><code>tolerance</code></strong></dt>
|
||
<dd>
|
||
Stop iteration if
|
||
<code>(A - I*eigenvalue) @ v < num_vectors * tolerance</code>,
|
||
Default 1e-13.
|
||
</dd>
|
||
<dt><strong><code>solver</code></strong></dt>
|
||
<dd>
|
||
Solver function of the form <code>x = solver(A, b)</code>. By default,
|
||
use scipy.sparse.spsolve for sparse matrices and scipy.sparse.bicgstab
|
||
for general LinearOperator instances.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= (eigenvalues, eigenvectors)</p>
|
||
<h3 id="meanas.eigensolvers.signed_eigensolve">Function
|
||
<code>signed_eigensolve</code></h3>
|
||
<blockquote>
|
||
<p><code>def signed_eigensolve(operator: scipy.sparse._matrix.spmatrix | scipy.sparse.linalg._interface.LinearOperator, how_many: int, negative: bool = False) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Find the largest-magnitude positive-only (or negative-only)
|
||
eigenvalues and eigenvectors of the provided matrix.</p>
|
||
<p>Args —–= <strong><code>operator</code></strong> : Matrix to
|
||
analyze.</p>
|
||
<dl>
|
||
<dt><strong><code>how_many</code></strong></dt>
|
||
<dd>
|
||
How many eigenvalues to find.
|
||
</dd>
|
||
<dt><strong><code>negative</code></strong></dt>
|
||
<dd>
|
||
Whether to find negative-only eigenvalues. Default False (positive
|
||
only).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= (sorted list of eigenvalues, 2D ndarray of corresponding
|
||
eigenvectors) <code>eigenvectors[:, k]</code> corresponds to the k-th
|
||
eigenvalue</p>
|
||
<hr />
|
||
<h1 id="meanas.fdfd">Module <code>meanas.fdfd</code></h1>
|
||
<p>Tools for finite difference frequency-domain (FDFD) simulations and
|
||
calculations.</p>
|
||
<p>These mostly involve picking a single frequency, then setting up and
|
||
solving a matrix equation (Ax=b) or eigenvalue problem.</p>
|
||
<p>Submodules:</p>
|
||
<ul>
|
||
<li><code><a
|
||
href="#meanas.fdfd.operators">meanas.fdfd.operators</a></code>, <code><a
|
||
href="#meanas.fdfd.functional">meanas.fdfd.functional</a></code>:
|
||
General FDFD problem setup.</li>
|
||
<li><code><a href="#meanas.fdfd.solvers">meanas.fdfd.solvers</a></code>:
|
||
Solver interface and reference implementation.</li>
|
||
<li><code><a href="#meanas.fdfd.scpml">meanas.fdfd.scpml</a></code>:
|
||
Stretched-coordinate perfectly matched layer (scpml) boundary
|
||
conditions</li>
|
||
<li><code><a
|
||
href="#meanas.fdfd.waveguide_2d">meanas.fdfd.waveguide_2d</a></code>:
|
||
Operators and mode-solver for waveguides with constant
|
||
cross-section.</li>
|
||
<li><code><a
|
||
href="#meanas.fdfd.waveguide_3d">meanas.fdfd.waveguide_3d</a></code>:
|
||
Functions for transforming <code><a
|
||
href="#meanas.fdfd.waveguide_2d">meanas.fdfd.waveguide_2d</a></code>
|
||
results into 3D.</li>
|
||
</ul>
|
||
<p>================================================================</p>
|
||
<p>From the “Frequency domain” section of <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>, we have</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>resulting in</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Maxwell’s equations are then</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>With <eq env="math">\Delta_t \to 0</eq>, this simplifies to</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>and then</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p><eq env="displaymath">
|
||
\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}} \\
|
||
</eq></p>
|
||
<h1 id="todo-fdfd">TODO FDFD?</h1>
|
||
<h1 id="todo-pml">TODO PML</h1>
|
||
<h2 id="sub-modules-1">Sub-modules</h2>
|
||
<ul>
|
||
<li><a href="#meanas.fdfd.bloch">meanas.fdfd.bloch</a></li>
|
||
<li><a href="#meanas.fdfd.farfield">meanas.fdfd.farfield</a></li>
|
||
<li><a href="#meanas.fdfd.functional">meanas.fdfd.functional</a></li>
|
||
<li><a href="#meanas.fdfd.operators">meanas.fdfd.operators</a></li>
|
||
<li><a href="#meanas.fdfd.scpml">meanas.fdfd.scpml</a></li>
|
||
<li><a href="#meanas.fdfd.solvers">meanas.fdfd.solvers</a></li>
|
||
<li><a
|
||
href="#meanas.fdfd.waveguide_2d">meanas.fdfd.waveguide_2d</a></li>
|
||
<li><a
|
||
href="#meanas.fdfd.waveguide_3d">meanas.fdfd.waveguide_3d</a></li>
|
||
<li><a
|
||
href="#meanas.fdfd.waveguide_cyl">meanas.fdfd.waveguide_cyl</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.bloch">Module <code>meanas.fdfd.bloch</code></h1>
|
||
<p>Bloch eigenmode solver/operators</p>
|
||
<p>This module contains functions for generating and solving the 3D
|
||
Bloch eigenproblem. The approach is to transform the problem into the
|
||
(spatial) fourier domain, transforming the equation</p>
|
||
<pre><code>1/mu * curl(1/eps * curl(H_eigenmode)) = (w/c)^2 H_eigenmode</code></pre>
|
||
<p>into</p>
|
||
<pre><code>conv(1/mu_k, ik x conv(1/eps_k, ik x H_k)) = (w/c)^2 H_k</code></pre>
|
||
<p>where:</p>
|
||
<ul>
|
||
<li>the <code>_k</code> subscript denotes a 3D fourier transformed
|
||
field</li>
|
||
<li>each component of <code>H_k</code> corresponds to a plane wave with
|
||
wavevector <code>k</code></li>
|
||
<li><code>x</code> is the cross product</li>
|
||
<li><code>conv()</code> denotes convolution</li>
|
||
</ul>
|
||
<p>Since <code>k</code> and <code>H</code> are orthogonal for each plane
|
||
wave, we can use each <code>k</code> to create an orthogonal basis (k,
|
||
m, n), with <code>k x m = n</code>, and <code>|m| = |n| = 1</code>. The
|
||
cross products are then simplified as follows:</p>
|
||
<ul>
|
||
<li><code>h</code> is shorthand for <code>H_k</code></li>
|
||
<li><code>(…)_xyz</code> denotes the <code>(x, y, z)</code> basis</li>
|
||
<li><code>(…)_kmn</code> denotes the <code>(k, m, n)</code> basis</li>
|
||
<li><code>hm</code> is the component of <code>h</code> in the
|
||
<code>m</code> direction, etc.</li>
|
||
</ul>
|
||
<p>We know</p>
|
||
<pre><code>k @ h = kx hx + ky hy + kz hz = 0 = hk
|
||
h = hk + hm + hn = hm + hn
|
||
k = kk + km + kn = kk = |k|</code></pre>
|
||
<p>We can write</p>
|
||
<pre><code>k x h = (ky hz - kz hy,
|
||
kz hx - kx hz,
|
||
kx hy - ky hx)_xyz
|
||
= ((k x h) @ k, (k x h) @ m, (k x h) @ n)_kmn
|
||
= (0, (m x k) @ h, (n x k) @ h)_kmn # triple product ordering
|
||
= (0, kk (-n @ h), kk (m @ h))_kmn # (m x k) = -|k| n, etc.
|
||
= |k| (0, -h @ n, h @ m)_kmn</code></pre>
|
||
<p>which gives us a straightforward way to perform the cross product
|
||
while simultaneously transforming into the <code>_kmn</code> basis. We
|
||
can also write</p>
|
||
<pre><code>k x h = (km hn - kn hm,
|
||
kn hk - kk hn,
|
||
kk hm - km hk)_kmn
|
||
= (0, -kk hn, kk hm)_kmn
|
||
= (-kk hn)(mx, my, mz)_xyz + (kk hm)(nx, ny, nz)_xyz
|
||
= |k| (hm * (nx, ny, nz)_xyz
|
||
- hn * (mx, my, mz)_xyz)</code></pre>
|
||
<p>which gives us a way to perform the cross product while
|
||
simultaneously trasnforming back into the <code>_xyz</code> basis.</p>
|
||
<p>We can also simplify <code>conv(X_k, Y_k)</code> as
|
||
<code>fftn(X * ifftn(Y_k))</code>.</p>
|
||
<p>Using these results and storing <code>H_k</code> as
|
||
<code>h = (hm, hn)</code>, we have</p>
|
||
<pre><code>e_xyz = fftn(1/eps * ifftn(|k| (hm * n - hn * m)))
|
||
b_mn = |k| (-e_xyz @ n, e_xyz @ m)
|
||
h_mn = fftn(1/mu * ifftn(b_m * m + b_n * n))</code></pre>
|
||
<p>which forms the operator from the left side of the equation.</p>
|
||
<p>We can then use a preconditioned block Rayleigh iteration algorithm,
|
||
as in SG Johnson and JD Joannopoulos, Block-iterative frequency-domain
|
||
methods for Maxwell’s equations in a planewave basis, Optics Express 8,
|
||
3, 173-190 (2001) (similar to that used in MPB) to find the eigenvectors
|
||
for this operator.</p>
|
||
<p>===</p>
|
||
<p>Typically you will want to do something like</p>
|
||
<pre><code>recip_lattice = numpy.diag(1/numpy.array(epsilon[0].shape * dx))
|
||
n, v = bloch.eigsolve(5, k0, recip_lattice, epsilon)
|
||
f = numpy.sqrt(-numpy.real(n[0]))
|
||
n_eff = norm(recip_lattice @ k0) / f
|
||
|
||
v2e = bloch.hmn_2_exyz(k0, recip_lattice, epsilon)
|
||
e_field = v2e(v[0])
|
||
|
||
k, f = find_k(frequency=1/1550,
|
||
tolerance=(1/1550 - 1/1551),
|
||
direction=[1, 0, 0],
|
||
G_matrix=recip_lattice,
|
||
epsilon=epsilon,
|
||
band=0)</code></pre>
|
||
<h2 id="functions-1">Functions</h2>
|
||
<h3 id="meanas.fdfd.bloch.eigsolve">Function <code>eigsolve</code></h3>
|
||
<blockquote>
|
||
<p><code>def eigsolve(num_modes: int, k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, tolerance: float = 1e-07, max_iters: int = 10000, reset_iters: int = 100, y0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]], ForwardRef(None)] = None, callback: collections.abc.Callable[..., None] | None = None) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Find the first (lowest-frequency) num_modes eigenmodes with Bloch
|
||
wavevector k0 of the specified structure.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : Bloch wavevector,
|
||
<code>[k0x, k0y, k0z]</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. All fields are
|
||
sampled at cell centers (i.e., NOT Yee-gridded)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permability distribution for the simulation. Default
|
||
<code>None</code> (1 everywhere).
|
||
</dd>
|
||
<dt><strong><code>tolerance</code></strong></dt>
|
||
<dd>
|
||
Solver stops when fractional change in the objective
|
||
<code>trace(Z.H @ A @ Z @ inv(Z Z.H))</code> is smaller than the
|
||
tolerance
|
||
</dd>
|
||
<dt><strong><code>max_iters</code></strong></dt>
|
||
<dd>
|
||
TODO
|
||
</dd>
|
||
<dt><strong><code>reset_iters</code></strong></dt>
|
||
<dd>
|
||
TODO
|
||
</dd>
|
||
<dt><strong><code>callback</code></strong></dt>
|
||
<dd>
|
||
TODO
|
||
</dd>
|
||
<dt><strong><code>y0</code></strong></dt>
|
||
<dd>
|
||
TODO, initial guess
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>(eigenvalues, eigenvectors)</code> where
|
||
<code>eigenvalues[i]</code> corresponds to the vector
|
||
<code>eigenvectors[i, :]</code></p>
|
||
<h3 id="meanas.fdfd.bloch.fftn">Function <code>fftn</code></h3>
|
||
<blockquote>
|
||
<p><code>def fftn(*args: Any, **kwargs: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.bloch.find_k">Function <code>find_k</code></h3>
|
||
<blockquote>
|
||
<p><code>def find_k(frequency: float, tolerance: float, direction: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, band: int = 0, k_bounds: tuple[float, float] = (0, 0.5), k_guess: float | None = None, solve_callback: collections.abc.Callable[..., None] | None = None, iter_callback: collections.abc.Callable[..., None] | None = None, v0: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]] | None = None) -> tuple[float, float, numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Search for a bloch vector that has a given frequency.</p>
|
||
<p>Args —–= <strong><code>frequency</code></strong> : Target
|
||
frequency.</p>
|
||
<dl>
|
||
<dt><strong><code>tolerance</code></strong></dt>
|
||
<dd>
|
||
Target frequency tolerance.
|
||
</dd>
|
||
<dt><strong><code>direction</code></strong></dt>
|
||
<dd>
|
||
k-vector direction to search along.
|
||
</dd>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. All fields are
|
||
sampled at cell centers (i.e., NOT Yee-gridded)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permability distribution for the simulation. Default None (1
|
||
everywhere).
|
||
</dd>
|
||
<dt><strong><code>band</code></strong></dt>
|
||
<dd>
|
||
Which band to search in. Default 0 (lowest frequency).
|
||
</dd>
|
||
<dt><strong><code>k_bounds</code></strong></dt>
|
||
<dd>
|
||
Minimum and maximum values for k. Default (0, 0.5).
|
||
</dd>
|
||
<dt><strong><code>k_guess</code></strong></dt>
|
||
<dd>
|
||
Initial value for k.
|
||
</dd>
|
||
<dt><strong><code>solve_callback</code></strong></dt>
|
||
<dd>
|
||
TODO
|
||
</dd>
|
||
<dt><strong><code>iter_callback</code></strong></dt>
|
||
<dd>
|
||
TODO
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>(k, actual_frequency, eigenvalues,
|
||
eigenvectors)</code> The found k-vector and its frequency, along with
|
||
all eigenvalues and eigenvectors.</p>
|
||
<h3 id="meanas.fdfd.bloch.generate_kmn">Function
|
||
<code>generate_kmn</code></h3>
|
||
<blockquote>
|
||
<p><code>def generate_kmn(k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], shape: collections.abc.Sequence[int]) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]</code></p>
|
||
</blockquote>
|
||
<p>Generate a (k, m, n) orthogonal basis for each k-vector in the
|
||
simulation grid.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : [k0x, k0y, k0z], Bloch
|
||
wavevector, in G basis.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
[nx, ny, nz] shape of the simulation grid.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>(|k|, m, n)</code> where <code>|k|</code> has shape
|
||
<code>tuple(shape) + (1,)</code> and <code>m</code>, <code>n</code> have
|
||
shape <code>tuple(shape) + (3,)</code>. All are given in the xyz basis
|
||
(e.g. <code>|k|[0,0,0] = norm(G_matrix @ k0)</code>).</p>
|
||
<h3 id="meanas.fdfd.bloch.hmn_2_exyz">Function
|
||
<code>hmn_2_exyz</code></h3>
|
||
<blockquote>
|
||
<p><code>def hmn_2_exyz(k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Generate an operator which converts a vectorized
|
||
spatial-frequency-space <code>h_mn</code> into an E-field distribution,
|
||
i.e.</p>
|
||
<pre><code>ifft(conv(1/eps_k, ik x h_mn))</code></pre>
|
||
<p>The operator is a function that acts on a vector <code>h_mn</code> of
|
||
size <code>2 * epsilon[0].size</code>.</p>
|
||
<p>See the <code><a
|
||
href="#meanas.fdfd.bloch">meanas.fdfd.bloch</a></code> docstring for
|
||
more information.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : Bloch wavevector,
|
||
<code>[k0x, k0y, k0z]</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. All fields are
|
||
sampled at cell centers (i.e., NOT Yee-gridded)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function for converting <code>h_mn</code> into
|
||
<code>E_xyz</code></p>
|
||
<h3 id="meanas.fdfd.bloch.hmn_2_hxyz">Function
|
||
<code>hmn_2_hxyz</code></h3>
|
||
<blockquote>
|
||
<p><code>def hmn_2_hxyz(k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Generate an operator which converts a vectorized
|
||
spatial-frequency-space <code>h_mn</code> into an H-field distribution,
|
||
i.e.</p>
|
||
<pre><code>ifft(h_mn)</code></pre>
|
||
<p>The operator is a function that acts on a vector <code>h_mn</code> of
|
||
size <code>2 * epsilon[0].size</code>.</p>
|
||
<p>See the <code><a
|
||
href="#meanas.fdfd.bloch">meanas.fdfd.bloch</a></code> docstring for
|
||
more information.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : Bloch wavevector,
|
||
<code>[k0x, k0y, k0z]</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. Only
|
||
<code>epsilon[0].shape</code> is used.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function for converting <code>h_mn</code> into
|
||
<code>H_xyz</code></p>
|
||
<h3 id="meanas.fdfd.bloch.ifftn">Function <code>ifftn</code></h3>
|
||
<blockquote>
|
||
<p><code>def ifftn(*args: Any, **kwargs: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.bloch.inner_product">Function
|
||
<code>inner_product</code></h3>
|
||
<blockquote>
|
||
<p><code>def inner_product(eL, hL, eR, hR) -> complex</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.bloch.inverse_maxwell_operator_approx">Function
|
||
<code>inverse_maxwell_operator_approx</code></h3>
|
||
<blockquote>
|
||
<p><code>def inverse_maxwell_operator_approx(k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Generate an approximate inverse of the Maxwell operator,</p>
|
||
<pre><code>ik x conv(eps_k, ik x conv(mu_k, ___))</code></pre>
|
||
<p>which can be used to improve the speed of ARPACK in shift-invert
|
||
mode.</p>
|
||
<p>See the <code><a
|
||
href="#meanas.fdfd.bloch">meanas.fdfd.bloch</a></code> docstring for
|
||
more information.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : Bloch wavevector,
|
||
<code>[k0x, k0y, k0z]</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. All fields are
|
||
sampled at cell centers (i.e., NOT Yee-gridded)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permability distribution for the simulation. Default None (1
|
||
everywhere).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function which applies the approximate inverse of the
|
||
maxwell operator to <code>h_mn</code>.</p>
|
||
<h3 id="meanas.fdfd.bloch.maxwell_operator">Function
|
||
<code>maxwell_operator</code></h3>
|
||
<blockquote>
|
||
<p><code>def maxwell_operator(k0: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], G_matrix: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Generate the Maxwell operator</p>
|
||
<pre><code>conv(1/mu_k, ik x conv(1/eps_k, ik x ___))</code></pre>
|
||
<p>which is the spatial-frequency-space representation of</p>
|
||
<pre><code>1/mu * curl(1/eps * curl(___))</code></pre>
|
||
<p>The operator is a function that acts on a vector h_mn of size
|
||
<code>2 * epsilon[0].size</code></p>
|
||
<p>See the <code><a
|
||
href="#meanas.fdfd.bloch">meanas.fdfd.bloch</a></code> docstring for
|
||
more information.</p>
|
||
<p>Args —–= <strong><code>k0</code></strong> : Bloch wavevector,
|
||
<code>[k0x, k0y, k0z]</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>G_matrix</code></strong></dt>
|
||
<dd>
|
||
3x3 matrix, with reciprocal lattice vectors as columns.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution for the simulation. All fields are
|
||
sampled at cell centers (i.e., NOT Yee-gridded)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permability distribution for the simulation. Default None (1
|
||
everywhere).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function which applies the maxwell operator to h_mn.</p>
|
||
<h3 id="meanas.fdfd.bloch.trq">Function <code>trq</code></h3>
|
||
<blockquote>
|
||
<p><code>def trq(eI, hI, eO, hO) -> tuple[complex, complex]</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.farfield">Module
|
||
<code>meanas.fdfd.farfield</code></h1>
|
||
<p>Functions for performing near-to-farfield transformation (and the
|
||
reverse).</p>
|
||
<h2 id="functions-2">Functions</h2>
|
||
<h3 id="meanas.fdfd.farfield.far_to_nearfield">Function
|
||
<code>far_to_nearfield</code></h3>
|
||
<blockquote>
|
||
<p><code>def far_to_nearfield(E_far: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], H_far: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], dkx: float, dky: float, padded_size: list[int] | int | None = None) -> dict[str, typing.Any]</code></p>
|
||
</blockquote>
|
||
<p>Compute the farfield, i.e. the distribution of the fields after
|
||
propagation through several wavelengths of uniform medium.</p>
|
||
<p>The input fields should be complex phasors.</p>
|
||
<p>Args —–= <strong><code>E_far</code></strong> : List 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))</p>
|
||
<dl>
|
||
<dt><strong><code>H_far</code></strong></dt>
|
||
<dd>
|
||
List 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))
|
||
</dd>
|
||
<dt><strong><code>dkx</code></strong></dt>
|
||
<dd>
|
||
kx discretization, in units of wavelength.
|
||
</dd>
|
||
<dt><strong><code>dky</code></strong></dt>
|
||
<dd>
|
||
ky discretization, in units of wavelength.
|
||
</dd>
|
||
<dt><strong><code>padded_size</code></strong></dt>
|
||
<dd>
|
||
Shape of the output. A single integer <code>n</code> will be expanded to
|
||
<code>(n, n)</code>. Powers of 2 are most efficient for FFT computation.
|
||
Default is the smallest power of 2 larger than the input, for each axis.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Dict with keys</p>
|
||
<ul>
|
||
<li><code>E</code>: E-field nearfield</li>
|
||
<li><code>H</code>: H-field nearfield</li>
|
||
<li><code>dx</code>, <code>dy</code>: spatial discretization, normalized
|
||
to wavelength (dimensionless)</li>
|
||
</ul>
|
||
<h3 id="meanas.fdfd.farfield.near_to_farfield">Function
|
||
<code>near_to_farfield</code></h3>
|
||
<blockquote>
|
||
<p><code>def near_to_farfield(E_near: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], H_near: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], dx: float, dy: float, padded_size: list[int] | int | None = None) -> dict[str, typing.Any]</code></p>
|
||
</blockquote>
|
||
<p>Compute the farfield, i.e. the distribution of the fields after
|
||
propagation through several wavelengths of uniform medium.</p>
|
||
<p>The input fields should be complex phasors.</p>
|
||
<p>Args —–= <strong><code>E_near</code></strong> : List 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).</p>
|
||
<dl>
|
||
<dt><strong><code>H_near</code></strong></dt>
|
||
<dd>
|
||
List 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).
|
||
</dd>
|
||
<dt><strong><code>dx</code></strong></dt>
|
||
<dd>
|
||
Cell size along x-dimension, in units of wavelength.
|
||
</dd>
|
||
<dt><strong><code>dy</code></strong></dt>
|
||
<dd>
|
||
Cell size along y-dimension, in units of wavelength.
|
||
</dd>
|
||
<dt><strong><code>padded_size</code></strong></dt>
|
||
<dd>
|
||
Shape of the output. A single integer <code>n</code> will be expanded to
|
||
<code>(n, n)</code>. Powers of 2 are most efficient for FFT computation.
|
||
Default is the smallest power of 2 larger than the input, for each axis.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Dict with keys</p>
|
||
<ul>
|
||
<li><code>E_far</code>: Normalized E-field farfield; multiply by (i k
|
||
exp(-i k r) / (4 pi r)) to get the actual field value.</li>
|
||
<li><code>H_far</code>: Normalized H-field farfield; multiply by (i k
|
||
exp(-i k r) / (4 pi r)) to get the actual field value.</li>
|
||
<li><code>kx</code>, <code>ky</code>: Wavevector values corresponding to
|
||
the x- and y- axes in E_far and H_far, normalized to wavelength
|
||
(dimensionless).</li>
|
||
<li><code>dkx</code>, <code>dky</code>: step size for kx and ky,
|
||
normalized to wavelength.</li>
|
||
<li><code>theta</code>: arctan2(ky, kx) corresponding to each (kx, ky).
|
||
This is the angle in the x-y plane, counterclockwise from above,
|
||
starting from +x.</li>
|
||
<li><code>phi</code>: arccos(kz / k) corresponding to each (kx, ky).
|
||
This is the angle away from +z.</li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.functional">Module
|
||
<code>meanas.fdfd.functional</code></h1>
|
||
<p>Functional versions of many FDFD operators. These can be useful for
|
||
performing FDFD calculations without needing to construct large matrices
|
||
in memory.</p>
|
||
<p>The functions generated here expect <code>cfdfield_t</code> inputs
|
||
with shape (3, X, Y, Z), e.g. E = [E_x, E_y, E_z] where each (complex)
|
||
component has shape (X, Y, Z)</p>
|
||
<h2 id="functions-3">Functions</h2>
|
||
<h3 id="meanas.fdfd.functional.e2h">Function <code>e2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def e2h(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Utility operator for converting the <code>E</code> field into the
|
||
<code>H</code> field. For use with <code><a
|
||
href="#meanas.fdfd.functional.e_full">e_full()</a></code> – assumes that
|
||
there is no magnetic current <code>M</code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function <code>f</code> for converting <code>E</code> to
|
||
<code>H</code>, <code>f(E)</code> -> <code>H</code></p>
|
||
<h3 id="meanas.fdfd.functional.e_full">Function <code>e_full</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_full(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Wave operator for use with E-field. See <code>operators.e_full</code>
|
||
for details.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function <code>f</code> implementing the wave operator
|
||
<code>f(E)</code> -> <code>-i * omega * J</code></p>
|
||
<h3 id="meanas.fdfd.functional.e_tfsf_source">Function
|
||
<code>e_tfsf_source</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_tfsf_source(TF_region: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Operator that turns an E-field distribution into a
|
||
total-field/scattered-field (TFSF) source.</p>
|
||
<p>Args —–= <strong><code>TF_region</code></strong> : mask 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. <code>epsilon[0].shape</code>.</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function <code>f</code> which takes an E field and
|
||
returns a current distribution, <code>f(E)</code> ->
|
||
<code>J</code></p>
|
||
<h3 id="meanas.fdfd.functional.eh_full">Function
|
||
<code>eh_full</code></h3>
|
||
<blockquote>
|
||
<p><code>def eh_full(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]], tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]]</code></p>
|
||
</blockquote>
|
||
<p>Wave operator for full (both E and H) field representation. See
|
||
<code>operators.eh_full</code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function <code>f</code> implementing the wave operator
|
||
<code>f(E, H)</code> -> <code>(J, -M)</code></p>
|
||
<h3 id="meanas.fdfd.functional.m2j">Function <code>m2j</code></h3>
|
||
<blockquote>
|
||
<p><code>def m2j(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Utility operator for converting magnetic current <code>M</code>
|
||
distribution into equivalent electric current distribution
|
||
<code>J</code>. For use with e.g. <code><a
|
||
href="#meanas.fdfd.functional.e_full">e_full()</a></code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function <code>f</code> for converting <code>M</code> to
|
||
<code>J</code>, <code>f(M)</code> -> <code>J</code></p>
|
||
<h3 id="meanas.fdfd.functional.poynting_e_cross_h">Function
|
||
<code>poynting_e_cross_h</code></h3>
|
||
<blockquote>
|
||
<p><code>def poynting_e_cross_h(dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Generates a function that takes the single-frequency <code>E</code>
|
||
and <code>H</code> fields and calculates the cross product
|
||
<code>E</code> x <code>H</code> = <eq env="math">E \times H</eq> as
|
||
required for the Poynting vector, <eq env="math">S = E \times H</eq></p>
|
||
<p>Note —–= This function also shifts the input <code>E</code> field by
|
||
one cell as required for computing the Poynting cross product (see
|
||
<code><a href="#meanas.fdfd">meanas.fdfd</a></code> module docs).</p>
|
||
<p>Note —–= If <code>E</code> and <code>H</code> are peak amplitudes as
|
||
assumed elsewhere in this code, the time-average of the poynting vector
|
||
is <code><S> = Re(S)/2 = Re(E x H*) / 2</code>. The factor of
|
||
<code>1/2</code> can be omitted if root-mean-square quantities are used
|
||
instead.</p>
|
||
<p>Args —–= <strong><code>dxes</code></strong> : Grid parameters
|
||
<code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code></p>
|
||
<p>Returns —–= Function <code>f</code> that returns E x H as required
|
||
for the poynting vector.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.operators">Module
|
||
<code>meanas.fdfd.operators</code></h1>
|
||
<p>Sparse matrix operators for use with electromagnetic wave
|
||
equations.</p>
|
||
<p>These functions return sparse-matrix
|
||
(<code>scipy.sparse.spmatrix</code>) representations of a variety of
|
||
operators, intended for use with E and H fields vectorized using the
|
||
<code><a href="#meanas.fdmath.vectorization.vec">vec()</a></code> and
|
||
<code><a href="#meanas.fdmath.vectorization.unvec">unvec()</a></code>
|
||
functions.</p>
|
||
<p>E- and H-field values are defined on a Yee cell; <code>epsilon</code>
|
||
values should be calculated for cells centered at each E component
|
||
(<code>mu</code> at each H component).</p>
|
||
<p>Many of these functions require a <code>dxes</code> parameter, of
|
||
type <code>dx_lists_t</code>; see the <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> submodule for
|
||
details.</p>
|
||
<p>The following operators are included:</p>
|
||
<ul>
|
||
<li>E-only wave operator</li>
|
||
<li>H-only wave operator</li>
|
||
<li>EH wave operator</li>
|
||
<li>Curl for use with E, H fields</li>
|
||
<li>E to H conversion</li>
|
||
<li>M to J conversion</li>
|
||
<li>Poynting cross products</li>
|
||
<li>Circular shifts</li>
|
||
<li>Discrete derivatives</li>
|
||
<li>Averaging operators</li>
|
||
<li>Cross product matrices</li>
|
||
</ul>
|
||
<h2 id="functions-4">Functions</h2>
|
||
<h3 id="meanas.fdfd.operators.e2h">Function <code>e2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def e2h(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Utility operator for converting the E field into the H field. For use
|
||
with <code><a href="#meanas.fdfd.operators.e_full">e_full()</a></code> –
|
||
assumes that there is no magnetic current M.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>pmc</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PMC cells. Any cells where
|
||
<code>pmc != 0</code> are interpreted as containing a perfect magnetic
|
||
conductor (PMC). The PMC is applied per-field-component
|
||
(i.e. <code>pmc.size == epsilon.size</code>)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for converting E to H.</p>
|
||
<h3 id="meanas.fdfd.operators.e_boundary_source">Function
|
||
<code>e_boundary_source</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_boundary_source(mask: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, periodic_mask_edges: bool = False) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator 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 <code><a
|
||
href="#meanas.fdfd.operators.e_tfsf_source">e_tfsf_source()</a></code>
|
||
with an additional masking step.</p>
|
||
<p>Args —–= <strong><code>mask</code></strong> : The 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.</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix that turns an E-field into a current (J)
|
||
distribution.</p>
|
||
<h3 id="meanas.fdfd.operators.e_full">Function <code>e_full</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_full(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Wave operator <eq env="displaymath"> \nabla \times (\frac{1}{\mu}
|
||
\nabla \times) - \Omega^2 \epsilon </eq></p>
|
||
<pre><code>del x (1/mu * del x) - omega**2 * epsilon</code></pre>
|
||
<p>for use with the E-field, with wave equation <eq env="displaymath">
|
||
(\nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon) E =
|
||
-\imath \omega J </eq></p>
|
||
<pre><code>(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * J</code></pre>
|
||
<p>To make this matrix symmetric, use the preconditioners from <code><a
|
||
href="#meanas.fdfd.operators.e_full_preconditioners">e_full_preconditioners()</a></code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere).
|
||
</dd>
|
||
<dt><strong><code>pec</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PEC cells. Any cells where
|
||
<code>pec != 0</code> are interpreted as containing a perfect electrical
|
||
conductor (PEC). The PEC is applied per-field-component
|
||
(i.e. <code>pec.size == epsilon.size</code>)
|
||
</dd>
|
||
<dt><strong><code>pmc</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PMC cells. Any cells where
|
||
<code>pmc != 0</code> are interpreted as containing a perfect magnetic
|
||
conductor (PMC). The PMC is applied per-field-component
|
||
(i.e. <code>pmc.size == epsilon.size</code>)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix containing the wave operator.</p>
|
||
<h3 id="meanas.fdfd.operators.e_full_preconditioners">Function
|
||
<code>e_full_preconditioners</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_full_preconditioners(dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> tuple[scipy.sparse._matrix.spmatrix, scipy.sparse._matrix.spmatrix]</code></p>
|
||
</blockquote>
|
||
<p>Left and right preconditioners <code>(Pl, Pr)</code> for symmetrizing
|
||
the <code><a href="#meanas.fdfd.operators.e_full">e_full()</a></code>
|
||
wave operator.</p>
|
||
<p>The preconditioned matrix <code>A_symm = (Pl @ A @ Pr)</code> is
|
||
complex-symmetric (non-Hermitian unless there is no loss or PMLs).</p>
|
||
<p>The preconditioner matrices are diagonal and complex, with
|
||
<code>Pr = 1 / Pl</code></p>
|
||
<p>Args —–= <strong><code>dxes</code></strong> : Grid parameters
|
||
<code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code></p>
|
||
<p>Returns —–= Preconditioner matrices <code>(Pl, Pr)</code>.</p>
|
||
<h3 id="meanas.fdfd.operators.e_tfsf_source">Function
|
||
<code>e_tfsf_source</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_tfsf_source(TF_region: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator that turns a desired E-field distribution into a
|
||
total-field/scattered-field (TFSF) source.</p>
|
||
<p>TODO: Reference Rumpf paper</p>
|
||
<p>Args —–= <strong><code>TF_region</code></strong> : Mask, which is set
|
||
to 1 inside the total-field region and 0 in the scattered-field
|
||
region</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix that turns an E-field into a current (J)
|
||
distribution.</p>
|
||
<h3 id="meanas.fdfd.operators.eh_full">Function
|
||
<code>eh_full</code></h3>
|
||
<blockquote>
|
||
<p><code>def eh_full(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Wave operator for <code>[E, H]</code> field representation. This
|
||
operator implements Maxwell’s equations without cancelling out either E
|
||
or H. The operator is <eq env="displaymath"> \begin{bmatrix}
|
||
-\imath \omega \epsilon & \nabla \times \\
|
||
\nabla \times & \imath \omega \mu
|
||
\end{bmatrix} </eq></p>
|
||
<pre><code>[[-i * omega * epsilon, del x ],
|
||
[del x, i * omega * mu]]</code></pre>
|
||
<p>for use with a field vector of the form <code>cat(vec(E),
|
||
vec(H))</code>: <eq env="displaymath"> \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} </eq></p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>pec</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PEC cells. Any cells where
|
||
<code>pec != 0</code> are interpreted as containing a perfect electrical
|
||
conductor (PEC). The PEC is applied per-field-component
|
||
(i.e. <code>pec.size == epsilon.size</code>)
|
||
</dd>
|
||
<dt><strong><code>pmc</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PMC cells. Any cells where
|
||
<code>pmc != 0</code> are interpreted as containing a perfect magnetic
|
||
conductor (PMC). The PMC is applied per-field-component
|
||
(i.e. <code>pmc.size == epsilon.size</code>)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix containing the wave operator.</p>
|
||
<h3 id="meanas.fdfd.operators.h_full">Function <code>h_full</code></h3>
|
||
<blockquote>
|
||
<p><code>def h_full(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Wave operator <eq env="displaymath"> \nabla \times
|
||
(\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu </eq></p>
|
||
<pre><code>del x (1/epsilon * del x) - omega**2 * mu</code></pre>
|
||
<p>for use with the H-field, with wave equation <eq env="displaymath">
|
||
(\nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu) E =
|
||
\imath \omega M </eq></p>
|
||
<pre><code>(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * M</code></pre>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>pec</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PEC cells. Any cells where
|
||
<code>pec != 0</code> are interpreted as containing a perfect electrical
|
||
conductor (PEC). The PEC is applied per-field-component
|
||
(i.e. <code>pec.size == epsilon.size</code>)
|
||
</dd>
|
||
<dt><strong><code>pmc</code></strong></dt>
|
||
<dd>
|
||
Vectorized mask specifying PMC cells. Any cells where
|
||
<code>pmc != 0</code> are interpreted as containing a perfect magnetic
|
||
conductor (PMC). The PMC is applied per-field-component
|
||
(i.e. <code>pmc.size == epsilon.size</code>)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix containing the wave operator.</p>
|
||
<h3 id="meanas.fdfd.operators.m2j">Function <code>m2j</code></h3>
|
||
<blockquote>
|
||
<p><code>def m2j(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator for converting a magnetic current M into an electric current
|
||
J. For use with eg. <code><a
|
||
href="#meanas.fdfd.operators.e_full">e_full()</a></code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Angular frequency of
|
||
the simulation</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for converting M to J.</p>
|
||
<h3 id="meanas.fdfd.operators.poynting_e_cross">Function
|
||
<code>poynting_e_cross</code></h3>
|
||
<blockquote>
|
||
<p><code>def poynting_e_cross(e: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator for computing the Poynting vector, containing the (E x)
|
||
portion of the Poynting vector.</p>
|
||
<p>Args —–= <strong><code>e</code></strong> : Vectorized E-field for the
|
||
ExH cross product</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix containing (E x) portion of Poynting cross
|
||
product.</p>
|
||
<h3 id="meanas.fdfd.operators.poynting_h_cross">Function
|
||
<code>poynting_h_cross</code></h3>
|
||
<blockquote>
|
||
<p><code>def poynting_h_cross(h: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator for computing the Poynting vector, containing the (H x)
|
||
portion of the Poynting vector.</p>
|
||
<p>Args —–= <strong><code>h</code></strong> : Vectorized H-field for the
|
||
HxE cross product</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix containing (H x) portion of Poynting cross
|
||
product.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.scpml">Module <code>meanas.fdfd.scpml</code></h1>
|
||
<p>Functions for creating stretched coordinate perfectly matched layer
|
||
(PML) absorbers.</p>
|
||
<h2 id="variables">Variables</h2>
|
||
<h3 id="meanas.fdfd.scpml.s_function_t">Variable
|
||
<code>s_function_t</code></h3>
|
||
<p>Typedef for s-functions, see <code><a
|
||
href="#meanas.fdfd.scpml.prepare_s_function">prepare_s_function()</a></code></p>
|
||
<h2 id="functions-5">Functions</h2>
|
||
<h3 id="meanas.fdfd.scpml.prepare_s_function">Function
|
||
<code>prepare_s_function</code></h3>
|
||
<blockquote>
|
||
<p><code>def prepare_s_function(ln_R: float = -16, m: float = 4) -> collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]</code></p>
|
||
</blockquote>
|
||
<p>Create an s_function to pass to the SCPML functions. This is used
|
||
when you would like to customize the PML parameters.</p>
|
||
<p>Args —–= <strong><code>ln_R</code></strong> : Natural logarithm of
|
||
the desired reflectance</p>
|
||
<dl>
|
||
<dt><strong><code>m</code></strong></dt>
|
||
<dd>
|
||
Polynomial order for the PML (imaginary part increases as distance ** m)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= An s_function, which takes an ndarray (distances) and
|
||
returns an ndarray (complex part of the cell width; needs to be divided
|
||
by <code>sqrt(epilon_effective) * real(omega))</code> before use.</p>
|
||
<h3 id="meanas.fdfd.scpml.stretch_with_scpml">Function
|
||
<code>stretch_with_scpml</code></h3>
|
||
<blockquote>
|
||
<p><code>def stretch_with_scpml(dxes: list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]], axis: int, polarity: int, omega: float, epsilon_effective: float = 1.0, thickness: int = 10, s_function: collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] | None = None) -> list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]]</code></p>
|
||
</blockquote>
|
||
<p>Stretch dxes to contain a stretched-coordinate PML (SCPML) in one
|
||
direction along one axis.</p>
|
||
<p>Args —–= <strong><code>dxes</code></strong> : Grid parameters
|
||
<code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code></p>
|
||
<dl>
|
||
<dt><strong><code>axis</code></strong></dt>
|
||
<dd>
|
||
axis to stretch (0=x, 1=y, 2=z)
|
||
</dd>
|
||
<dt><strong><code>polarity</code></strong></dt>
|
||
<dd>
|
||
direction to stretch (-1 for -ve, +1 for +ve)
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency for the simulation
|
||
</dd>
|
||
<dt><strong><code>epsilon_effective</code></strong></dt>
|
||
<dd>
|
||
Effective epsilon of the PML. Match this to the material at the edge of
|
||
your grid. Default 1.
|
||
</dd>
|
||
<dt><strong><code>thickness</code></strong></dt>
|
||
<dd>
|
||
number of cells to use for pml (default 10)
|
||
</dd>
|
||
<dt><strong><code>s_function</code></strong></dt>
|
||
<dd>
|
||
Created by <code><a
|
||
href="#meanas.fdfd.scpml.prepare_s_function">prepare_s_function()</a>(…)</code>,
|
||
allowing customization of pml parameters. Default uses <code><a
|
||
href="#meanas.fdfd.scpml.prepare_s_function">prepare_s_function()</a></code>
|
||
with no parameters.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Complex cell widths (dx_lists_mut) as discussed in
|
||
<code><a href="#meanas.fdmath.types">meanas.fdmath.types</a></code>.
|
||
Multiple calls to this function may be necessary if multiple absorpbing
|
||
boundaries are needed.</p>
|
||
<h3 id="meanas.fdfd.scpml.uniform_grid_scpml">Function
|
||
<code>uniform_grid_scpml</code></h3>
|
||
<blockquote>
|
||
<p><code>def uniform_grid_scpml(shape: collections.abc.Sequence[int], thicknesses: collections.abc.Sequence[int], omega: float, epsilon_effective: float = 1.0, s_function: collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] | None = None) -> list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]]</code></p>
|
||
</blockquote>
|
||
<p>Create dx arrays for a uniform grid with a cell width of 1 and a
|
||
pml.</p>
|
||
<p>If you want something more fine-grained, check out <code><a
|
||
href="#meanas.fdfd.scpml.stretch_with_scpml">stretch_with_scpml()</a>(…)</code>.</p>
|
||
<p>Args —–= <strong><code>shape</code></strong> : Shape of the grid,
|
||
including the PMLs (which are 2*thicknesses thick)</p>
|
||
<dl>
|
||
<dt><strong><code>thicknesses</code></strong></dt>
|
||
<dd>
|
||
<code>[th_x, th_y, th_z]</code> 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. <code>th_*</code> may be
|
||
zero, in which case no pml is added.
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency for the simulation
|
||
</dd>
|
||
<dt><strong><code>epsilon_effective</code></strong></dt>
|
||
<dd>
|
||
Effective epsilon of the PML. Match this to the material at the edge of
|
||
your grid. Default 1.
|
||
</dd>
|
||
<dt><strong><code>s_function</code></strong></dt>
|
||
<dd>
|
||
created by <code><a
|
||
href="#meanas.fdfd.scpml.prepare_s_function">prepare_s_function()</a>(…)</code>,
|
||
allowing customization of pml parameters. Default uses <code><a
|
||
href="#meanas.fdfd.scpml.prepare_s_function">prepare_s_function()</a></code>
|
||
with no parameters.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Complex cell widths (dx_lists_mut) as discussed in
|
||
<code><a href="#meanas.fdmath.types">meanas.fdmath.types</a></code>.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.solvers">Module
|
||
<code>meanas.fdfd.solvers</code></h1>
|
||
<p>Solvers and solver interface for FDFD problems.</p>
|
||
<h2 id="functions-6">Functions</h2>
|
||
<h3 id="meanas.fdfd.solvers.generic">Function <code>generic</code></h3>
|
||
<blockquote>
|
||
<p><code>def generic(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], J: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, adjoint: bool = False, matrix_solver: collections.abc.Callable[..., typing.Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[typing.Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[typing.Union[bool, int, float, complex, str, bytes]]]] = <function _scipy_qmr>, matrix_solver_opts: dict[str, typing.Any] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]</code></p>
|
||
</blockquote>
|
||
<p>Conjugate gradient FDFD solver using CSR sparse matrices.</p>
|
||
<p>All ndarray arguments should be 1D arrays, as returned by <code><a
|
||
href="#meanas.fdmath.vectorization.vec">vec()</a></code>.</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : Complex frequency to
|
||
solve at.</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
<code>[[dx_e, dy_e, dz_e], [dx_h, dy_h, dz_h]]</code> (complex cell
|
||
sizes) as discussed in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>J</code></strong></dt>
|
||
<dd>
|
||
Electric current distribution (at E-field locations)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution (at E-field locations)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability distribution (at H-field locations)
|
||
</dd>
|
||
<dt><strong><code>pec</code></strong></dt>
|
||
<dd>
|
||
Perfect electric conductor distribution (at E-field locations; non-zero
|
||
value indicates PEC is present)
|
||
</dd>
|
||
<dt><strong><code>pmc</code></strong></dt>
|
||
<dd>
|
||
Perfect magnetic conductor distribution (at H-field locations; non-zero
|
||
value indicates PMC is present)
|
||
</dd>
|
||
<dt><strong><code>adjoint</code></strong></dt>
|
||
<dd>
|
||
If true, solves the adjoint problem.
|
||
</dd>
|
||
<dt><strong><code>matrix_solver</code></strong></dt>
|
||
<dd>
|
||
Called as
|
||
<code>matrix_solver(A, b, **matrix_solver_opts) -> x</code>, where
|
||
<code>A</code>: <code>scipy.sparse.csr_matrix</code>; <code>b</code>:
|
||
<code>ArrayLike</code>; <code>x</code>: <code>ArrayLike</code>; Default
|
||
is a wrapped version of <code>scipy.sparse.linalg.qmr()</code> which
|
||
doesn’t return convergence info and logs the residual every 100
|
||
iterations.
|
||
</dd>
|
||
<dt><strong><code>matrix_solver_opts</code></strong></dt>
|
||
<dd>
|
||
Passed as kwargs to <code>matrix_solver(…)</code>
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= E-field which solves the system.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.waveguide_2d">Module
|
||
<code>meanas.fdfd.waveguide_2d</code></h1>
|
||
<p>Operators and helper functions for waveguides with unchanging
|
||
cross-section.</p>
|
||
<p>The propagation direction is chosen to be along the z axis, and all
|
||
fields are given an implicit z-dependence of the form
|
||
<code>exp(-1 * wavenumber * z)</code>.</p>
|
||
<p>As the z-dependence is known, all the functions in this file assume a
|
||
2D grid
|
||
(i.e. <code>dxes = [[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]], [[dx_h[0], ...], [dy_h[0], ...]]]</code>).</p>
|
||
<p>===============</p>
|
||
<p>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 <eq
|
||
env="math">\epsilon</eq> tensor, we have</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Expanding the first two equations into vector components, we get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Substituting in our expressions for <eq env="math">\vec{E}</eq>, <eq
|
||
env="math">\vec{H}</eq> and discretizing:</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Rewrite the last three equations as</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Now apply <eq env="math">\imath \beta \tilde{\partial}_x</eq> to the
|
||
last equation, then substitute in for <eq env="math">\imath \beta
|
||
H_x</eq> and <eq env="math">\imath \beta H_y</eq>:</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>With a similar approach (but using <eq env="math">\imath \beta
|
||
\tilde{\partial}_y</eq> instead), we can get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>We can combine this equation for <eq env="math">\imath \beta
|
||
\tilde{\partial}_y E_z</eq> with the unused <eq env="math">\imath \omega
|
||
\mu_{xx} H_x</eq> and <eq env="math">\imath \omega \mu_{yy} H_y</eq>
|
||
equations to get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>and</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>However, based on our rewritten equation for <eq env="math">\imath
|
||
\beta H_x</eq> and the so-far unused equation for <eq env="math">\imath
|
||
\omega \mu_{zz} H_z</eq> we can also write</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>and, similarly,</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>By combining both pairs of expressions, we get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Using these, we can construct the eigenvalue problem</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>In the literature, <eq env="math">\beta</eq> is usually used to
|
||
denote the lossless/real part of the propagation constant, but in
|
||
<code><a href="#meanas">meanas</a></code> it is allowed to be
|
||
complex.</p>
|
||
<p>An equivalent eigenvalue problem can be formed using the <eq
|
||
env="math">H_x</eq> and <eq env="math">H_y</eq> fields, if those are
|
||
more convenient.</p>
|
||
<p>Note that <eq env="math">E_z</eq> was never discretized, so <eq
|
||
env="math">\beta</eq> will need adjustment to account for numerical
|
||
dispersion if the result is introduced into a space with a discretized
|
||
z-axis.</p>
|
||
<h2 id="functions-7">Functions</h2>
|
||
<h3 id="meanas.fdfd.waveguide_2d.curl_e">Function
|
||
<code>curl_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_e(wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Discretized curl operator for use with the waveguide E field.</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code></p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.curl_h">Function
|
||
<code>curl_h</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_h(wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Discretized curl operator for use with the waveguide H field.</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code></p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.e2h">Function <code>e2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def e2h(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Returns an operator which, when applied to a vectorized E eigenfield,
|
||
produces the vectorized H eigenfield.</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.e_err">Function <code>e_err</code></h3>
|
||
<blockquote>
|
||
<p><code>def e_err(e: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> float</code></p>
|
||
</blockquote>
|
||
<p>Calculates the relative error in the E field</p>
|
||
<p>Args —–= <strong><code>e</code></strong> : Vectorized E field</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Relative error <code>norm(A_e @ e) / norm(e)</code>.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.exy2e">Function <code>exy2e</code></h3>
|
||
<blockquote>
|
||
<p><code>def exy2e(wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>e_xy</code> containing the
|
||
vectorized E_x and E_y fields, into a vectorized E containing all three
|
||
E components</p>
|
||
<p>From the operator derivation (see module docs), we have</p>
|
||
<p><eq env="displaymath">
|
||
\imath \omega \epsilon_{zz} E_z = \hat{\partial}_x H_y -
|
||
\hat{\partial}_y H_x \\
|
||
</eq></p>
|
||
<p>as well as the intermediate equations</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Combining these, we get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code> It should satisfy
|
||
<code>operator_e() @ e_xy == wavenumber**2 * e_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.exy2h">Function <code>exy2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def exy2h(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>e_xy</code> containing the
|
||
vectorized E_x and E_y fields, into a vectorized H containing all three
|
||
H components</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_e() @ e_xy == wavenumber**2 * e_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.get_abcd">Function
|
||
<code>get_abcd</code></h3>
|
||
<blockquote>
|
||
<p><code>def get_abcd(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, **kwargs)</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.waveguide_2d.get_s">Function <code>get_s</code></h3>
|
||
<blockquote>
|
||
<p><code>def get_s(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, force_nogain: bool = False, force_reciprocal: bool = False, **kwargs)</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.waveguide_2d.get_tr">Function
|
||
<code>get_tr</code></h3>
|
||
<blockquote>
|
||
<p><code>def get_tr(ehL, wavenumbers_L, ehR, wavenumbers_R, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]])</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.waveguide_2d.h2e">Function <code>h2e</code></h3>
|
||
<blockquote>
|
||
<p><code>def h2e(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Returns an operator which, when applied to a vectorized H eigenfield,
|
||
produces the vectorized E eigenfield.</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.h_err">Function <code>h_err</code></h3>
|
||
<blockquote>
|
||
<p><code>def h_err(h: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> float</code></p>
|
||
</blockquote>
|
||
<p>Calculates the relative error in the H field</p>
|
||
<p>Args —–= <strong><code>h</code></strong> : Vectorized H field</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Relative error <code>norm(A_h @ h) / norm(h)</code>.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.hxy2e">Function <code>hxy2e</code></h3>
|
||
<blockquote>
|
||
<p><code>def hxy2e(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>h_xy</code> containing the
|
||
vectorized H_x and H_y fields, into a vectorized E containing all three
|
||
E components</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_h() @ h_xy == wavenumber**2 * h_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.hxy2h">Function <code>hxy2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def hxy2h(wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>h_xy</code> containing the
|
||
vectorized H_x and H_y fields, into a vectorized H containing all three
|
||
H components</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_h() @ h_xy == wavenumber**2 * h_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.inner_product">Function
|
||
<code>inner_product</code></h3>
|
||
<blockquote>
|
||
<p><code>def inner_product(e1: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], h2: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], prop_phase: float = 0, conj_h: bool = False, trapezoid: bool = False) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.waveguide_2d.normalized_fields_e">Function
|
||
<code>normalized_fields_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def normalized_fields_e(e_xy: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, prop_phase: float = 0) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Given a vector <code>e_xy</code> containing the vectorized E_x and
|
||
E_y fields, returns normalized, vectorized E and H fields for the
|
||
system.</p>
|
||
<p>Args —–= <strong><code>e_xy</code></strong> : Vector containing E_x
|
||
and E_y fields</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_e() @ e_xy == wavenumber**2 * e_xy</code>
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>prop_phase</code></strong></dt>
|
||
<dd>
|
||
Phase shift <code>(dz * corrected_wavenumber)</code> over 1 cell in
|
||
propagation direction. Default 0 (continuous propagation direction,
|
||
i.e. dz->0).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>(e, h)</code>, where each field is vectorized,
|
||
normalized, and contains all three vector components.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.normalized_fields_h">Function
|
||
<code>normalized_fields_h</code></h3>
|
||
<blockquote>
|
||
<p><code>def normalized_fields_h(h_xy: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, prop_phase: float = 0) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Given a vector <code>h_xy</code> containing the vectorized H_x and
|
||
H_y fields, returns normalized, vectorized E and H fields for the
|
||
system.</p>
|
||
<p>Args —–= <strong><code>h_xy</code></strong> : Vector containing H_x
|
||
and H_y fields</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_h() @ h_xy == wavenumber**2 * h_xy</code>
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>prop_phase</code></strong></dt>
|
||
<dd>
|
||
Phase shift <code>(dz * corrected_wavenumber)</code> over 1 cell in
|
||
propagation direction. Default 0 (continuous propagation direction,
|
||
i.e. dz->0).
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>(e, h)</code>, where each field is vectorized,
|
||
normalized, and contains all three vector components.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.operator_e">Function
|
||
<code>operator_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def operator_e(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Waveguide operator of the form</p>
|
||
<pre><code>omega**2 * mu * epsilon +
|
||
mu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +
|
||
[[Dx], [Dy]] / epsilon * [Dx, Dy] * epsilon</code></pre>
|
||
<p>for use with a field vector of the form <code>cat([E_x,
|
||
E_y])</code>.</p>
|
||
<p>More precisely, the operator is</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p><eq env="math">\tilde{\partial}_x</eq> and <eq
|
||
env="math">\hat{\partial}_x</eq> are the forward and backward
|
||
derivatives along x, and each <eq env="math">\epsilon_{xx}</eq>, <eq
|
||
env="math">\mu_{yy}</eq>, etc. is a diagonal matrix containing the
|
||
vectorized material property distribution.</p>
|
||
<p>This operator can be used to form an eigenvalue problem of the form
|
||
<code>operator_e(...) @ [E_x, E_y] = wavenumber**2 * [E_x, E_y]</code></p>
|
||
<p>which can then be solved for the eigenmodes of the system (an
|
||
<code>exp(-i * wavenumber * z)</code> z-dependence is assumed for the
|
||
fields).</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : The angular frequency
|
||
of the system.</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.operator_h">Function
|
||
<code>operator_h</code></h3>
|
||
<blockquote>
|
||
<p><code>def operator_h(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Waveguide operator of the form</p>
|
||
<pre><code>omega**2 * epsilon * mu +
|
||
epsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +
|
||
[[Dx], [Dy]] / mu * [Dx, Dy] * mu</code></pre>
|
||
<p>for use with a field vector of the form <code>cat([H_x,
|
||
H_y])</code>.</p>
|
||
<p>More precisely, the operator is</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p><eq env="math">\tilde{\partial}_x</eq> and <eq
|
||
env="math">\hat{\partial}_x</eq> are the forward and backward
|
||
derivatives along x, and each <eq env="math">\epsilon_{xx}</eq>, <eq
|
||
env="math">\mu_{yy}</eq>, etc. is a diagonal matrix containing the
|
||
vectorized material property distribution.</p>
|
||
<p>This operator can be used to form an eigenvalue problem of the form
|
||
<code>operator_h(...) @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]</code></p>
|
||
<p>which can then be solved for the eigenmodes of the system (an
|
||
<code>exp(-i * wavenumber * z)</code> z-dependence is assumed for the
|
||
fields).</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : The angular frequency
|
||
of the system.</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.sensitivity">Function
|
||
<code>sensitivity</code></h3>
|
||
<blockquote>
|
||
<p><code>def sensitivity(e_norm: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], h_norm: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]</code></p>
|
||
</blockquote>
|
||
<p>Given a waveguide structure (<code>dxes</code>, <code>epsilon</code>,
|
||
<code>mu</code>) and mode fields (<code>e_norm</code>,
|
||
<code>h_norm</code>, <code>wavenumber</code>, <code>omega</code>),
|
||
calculates the sensitivity of the wavenumber <eq env="math">\beta</eq>
|
||
to changes in the dielectric structure <eq env="math">\epsilon</eq>.</p>
|
||
<p>The output is a vector of the same size as <code>vec(epsilon)</code>,
|
||
with each element specifying the sensitivity of <code>wavenumber</code>
|
||
to changes in the corresponding element in <code>vec(epsilon)</code>,
|
||
i.e.</p>
|
||
<p><eq env="displaymath">sens_{i} =
|
||
\frac{\partial\beta}{\partial\epsilon_i}</eq></p>
|
||
<p>An adjoint approach is used to calculate the sensitivity; the
|
||
derivation is provided here:</p>
|
||
<p>Starting with the eigenvalue equation</p>
|
||
<p><eq env="displaymath">\beta^2 E_{xy} = A_E E_{xy}</eq></p>
|
||
<p>where <eq env="math">A_E</eq> is the waveguide operator from <code><a
|
||
href="#meanas.fdfd.waveguide_2d.operator_e">operator_e()</a></code>, and
|
||
<eq env="math">E_{xy} = \begin{bmatrix} E_x \\
|
||
E_y
|
||
\end{bmatrix}</eq>, we can differentiate with respect to one of the <eq
|
||
env="math">\epsilon</eq> elements (i.e. at one Yee grid point), <eq
|
||
env="math">\epsilon_i</eq>:</p>
|
||
<p><eq env="displaymath">
|
||
(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}
|
||
</eq></p>
|
||
<p>We then multiply by <eq env="math">H_{yx}^\star =
|
||
\begin{bmatrix}H_y^\star \\ -H_x^\star \end{bmatrix}</eq> from the
|
||
left:</p>
|
||
<p><eq env="displaymath">
|
||
(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}
|
||
</eq></p>
|
||
<p>However, <eq env="math">H_{yx}^\star</eq> is actually a
|
||
left-eigenvector of <eq env="math">A_E</eq>. This can be verified by
|
||
inspecting the form of <code><a
|
||
href="#meanas.fdfd.waveguide_2d.operator_h">operator_h()</a></code> (<eq
|
||
env="math">A_H</eq>) and comparing its conjugate transpose to <code><a
|
||
href="#meanas.fdfd.waveguide_2d.operator_e">operator_e()</a></code> (<eq
|
||
env="math">A_E</eq>). Also, note <eq env="math">H_{yx}^\star \cdot
|
||
E_{xy} = H^\star \times E</eq> recalls the mode orthogonality relation.
|
||
See doi:10.5194/ars-9-85-201 for a similar approach. Therefore,</p>
|
||
<p><eq env="displaymath">
|
||
H_{yx}^\star A_E \partial_{\epsilon_i} E_{xy} = \beta^2 H_{yx}^\star
|
||
\partial_{\epsilon_i} E_{xy}
|
||
</eq></p>
|
||
<p>and we can simplify to</p>
|
||
<p><eq env="displaymath">
|
||
\partial_{\epsilon_i}(\beta)
|
||
= \frac{1}{2 \beta} \frac{H_{yx}^\star \partial_{\epsilon_i}(A_E)
|
||
E_{xy} }{H_{yx}^\star E_{xy}}
|
||
</eq></p>
|
||
<p>This expression can be quickly calculated for all <eq
|
||
env="math">i</eq> by writing out the various terms of <eq
|
||
env="math">\partial_{\epsilon_i} A_E</eq> and recognizing that the
|
||
vector-matrix-vector products (i.e. scalars) <eq env="math">sens_i =
|
||
\vec{v}_{left} \partial_{\epsilon_i} (\epsilon_{xyz})
|
||
\vec{v}_{right}</eq>, indexed by <eq env="math">i</eq>, can be expressed
|
||
as elementwise multiplications <eq env="math">\vec{sens} =
|
||
\vec{v}_{left} \star \vec{v}_{right}</eq></p>
|
||
<p>Args —–= <strong><code>e_norm</code></strong> : Normalized,
|
||
vectorized E_xyz field for the mode. E.g. as returned by <code><a
|
||
href="#meanas.fdfd.waveguide_2d.normalized_fields_e">normalized_fields_e()</a></code>.</p>
|
||
<dl>
|
||
<dt><strong><code>h_norm</code></strong></dt>
|
||
<dd>
|
||
Normalized, vectorized H_xyz field for the mode. E.g. as returned by
|
||
<code><a
|
||
href="#meanas.fdfd.waveguide_2d.normalized_fields_e">normalized_fields_e()</a></code>.
|
||
</dd>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Propagation constant for the mode. The z-axis is assumed to be
|
||
continuous (i.e. without numerical dispersion).
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system.
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.solve_mode">Function
|
||
<code>solve_mode</code></h3>
|
||
<blockquote>
|
||
<p><code>def solve_mode(mode_number: int, *args: Any, **kwargs: Any) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], complex]</code></p>
|
||
</blockquote>
|
||
<p>Wrapper around <code><a
|
||
href="#meanas.fdfd.waveguide_2d.solve_modes">solve_modes()</a></code>
|
||
that solves for a single mode.</p>
|
||
<p>Args —–= <strong><code>mode_number</code></strong> : 0-indexed mode
|
||
number to solve for</p>
|
||
<dl>
|
||
<dt><strong><code>*args</code></strong></dt>
|
||
<dd>
|
||
passed to <code><a
|
||
href="#meanas.fdfd.waveguide_2d.solve_modes">solve_modes()</a></code>
|
||
</dd>
|
||
<dt><strong><code>**kwargs</code></strong></dt>
|
||
<dd>
|
||
passed to <code><a
|
||
href="#meanas.fdfd.waveguide_2d.solve_modes">solve_modes()</a></code>
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= (e_xy, wavenumber)</p>
|
||
<h3 id="meanas.fdfd.waveguide_2d.solve_modes">Function
|
||
<code>solve_modes</code></h3>
|
||
<blockquote>
|
||
<p><code>def solve_modes(mode_numbers: collections.abc.Sequence[int], omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, mode_margin: int = 2) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>Given a 2D region, attempts to solve for the eigenmode with the
|
||
specified mode numbers.</p>
|
||
<p>Args —–= <strong><code>mode_numbers</code></strong> : List of
|
||
0-indexed mode numbers to solve for</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
<dt><strong><code>mode_margin</code></strong></dt>
|
||
<dd>
|
||
The eigensolver will actually solve for
|
||
<code>(max(mode_number) + mode_margin)</code> modes, but only return the
|
||
target mode. Increasing this value can improve the solver’s ability to
|
||
find the correct mode. Default 2.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>e_xys</code> : NDArray of vfdfield_t specifying
|
||
fields. First dimension is mode number.</p>
|
||
<dl>
|
||
<dt><code>wavenumbers</code></dt>
|
||
<dd>
|
||
list of wavenumbers
|
||
</dd>
|
||
</dl>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.waveguide_3d">Module
|
||
<code>meanas.fdfd.waveguide_3d</code></h1>
|
||
<p>Tools for working with waveguide modes in 3D domains.</p>
|
||
<p>This module relies heavily on <code>waveguide_2d</code> and mostly
|
||
just transforms its parameters into 2D equivalents and expands the
|
||
results back into 3D.</p>
|
||
<h2 id="functions-8">Functions</h2>
|
||
<h3 id="meanas.fdfd.waveguide_3d.compute_overlap_e">Function
|
||
<code>compute_overlap_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def compute_overlap_e(E: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], axis: int, polarity: int, slices: collections.abc.Sequence[slice]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]</code></p>
|
||
</blockquote>
|
||
<p>Given an eigenmode obtained by <code><a
|
||
href="#meanas.fdfd.waveguide_3d.solve_mode">solve_mode()</a></code>,
|
||
calculates an overlap_e for the mode orthogonality relation
|
||
Integrate(((E x H_mode) + (E_mode x H)) dot dn) [assumes reflection
|
||
symmetry].</p>
|
||
<p>TODO: add reference</p>
|
||
<p>Args —–= <strong><code>E</code></strong> : E-field of the mode</p>
|
||
<dl>
|
||
<dt><strong><code>H</code></strong></dt>
|
||
<dd>
|
||
H-field of the mode (advanced by half of a Yee cell from E)
|
||
</dd>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber of the mode
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>axis</code></strong></dt>
|
||
<dd>
|
||
Propagation axis (0=x, 1=y, 2=z)
|
||
</dd>
|
||
<dt><strong><code>polarity</code></strong></dt>
|
||
<dd>
|
||
Propagation direction (+1 for +ve, -1 for -ve)
|
||
</dd>
|
||
<dt><strong><code>slices</code></strong></dt>
|
||
<dd>
|
||
<code>epsilon[tuple(slices)]</code> is used to select the portion of the
|
||
grid to use as the waveguide cross-section. slices[axis] should select
|
||
only one item.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= overlap_e such that
|
||
<code>numpy.sum(overlap_e * other_e.conj())</code> computes the overlap
|
||
integral</p>
|
||
<h3 id="meanas.fdfd.waveguide_3d.compute_source">Function
|
||
<code>compute_source</code></h3>
|
||
<blockquote>
|
||
<p><code>def compute_source(E: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], axis: int, polarity: int, slices: collections.abc.Sequence[slice], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]</code></p>
|
||
</blockquote>
|
||
<p>Given an eigenmode obtained by <code><a
|
||
href="#meanas.fdfd.waveguide_3d.solve_mode">solve_mode()</a></code>,
|
||
returns the current source distribution necessary to position a
|
||
unidirectional source at the slice location.</p>
|
||
<p>Args —–= <strong><code>E</code></strong> : E-field of the mode</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber of the mode
|
||
</dd>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>axis</code></strong></dt>
|
||
<dd>
|
||
Propagation axis (0=x, 1=y, 2=z)
|
||
</dd>
|
||
<dt><strong><code>polarity</code></strong></dt>
|
||
<dd>
|
||
Propagation direction (+1 for +ve, -1 for -ve)
|
||
</dd>
|
||
<dt><strong><code>slices</code></strong></dt>
|
||
<dd>
|
||
<code>epsilon[tuple(slices)]</code> is used to select the portion of the
|
||
grid to use as the waveguide cross-section. <code>slices[axis]</code>
|
||
should select only one item.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= J distribution for the unidirectional source</p>
|
||
<h3 id="meanas.fdfd.waveguide_3d.expand_e">Function
|
||
<code>expand_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def expand_e(E: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], axis: int, polarity: int, slices: collections.abc.Sequence[slice]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]</code></p>
|
||
</blockquote>
|
||
<p>Given an eigenmode obtained by <code><a
|
||
href="#meanas.fdfd.waveguide_3d.solve_mode">solve_mode()</a></code>,
|
||
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.</p>
|
||
<p>Args —–= <strong><code>E</code></strong> : E-field of the mode</p>
|
||
<dl>
|
||
<dt><strong><code>wavenumber</code></strong></dt>
|
||
<dd>
|
||
Wavenumber of the mode
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>axis</code></strong></dt>
|
||
<dd>
|
||
Propagation axis (0=x, 1=y, 2=z)
|
||
</dd>
|
||
<dt><strong><code>polarity</code></strong></dt>
|
||
<dd>
|
||
Propagation direction (+1 for +ve, -1 for -ve)
|
||
</dd>
|
||
<dt><strong><code>slices</code></strong></dt>
|
||
<dd>
|
||
<code>epsilon[tuple(slices)]</code> is used to select the portion of the
|
||
grid to use as the waveguide cross-section. slices[axis] should select
|
||
only one item.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>E</code>, with the original field expanded along
|
||
the specified <code>axis</code>.</p>
|
||
<h3 id="meanas.fdfd.waveguide_3d.solve_mode">Function
|
||
<code>solve_mode</code></h3>
|
||
<blockquote>
|
||
<p><code>def solve_mode(mode_number: int, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], axis: int, polarity: int, slices: collections.abc.Sequence[slice], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> dict[str, complex | numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]]]</code></p>
|
||
</blockquote>
|
||
<p>Given a 3D grid, selects a slice from the grid and attempts to solve
|
||
for an eigenmode propagating through that slice.</p>
|
||
<p>Args —–= <strong><code>mode_number</code></strong> : Number of the
|
||
mode, 0-indexed</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code>
|
||
</dd>
|
||
<dt><strong><code>axis</code></strong></dt>
|
||
<dd>
|
||
Propagation axis (0=x, 1=y, 2=z)
|
||
</dd>
|
||
<dt><strong><code>polarity</code></strong></dt>
|
||
<dd>
|
||
Propagation direction (+1 for +ve, -1 for -ve)
|
||
</dd>
|
||
<dt><strong><code>slices</code></strong></dt>
|
||
<dd>
|
||
<code>epsilon[tuple(slices)]</code> is used to select the portion of the
|
||
grid to use as the waveguide cross-section. <code>slices[axis]</code>
|
||
should select only one item.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–=</p>
|
||
<pre><code>{
|
||
'E': NDArray[complexfloating],
|
||
'H': NDArray[complexfloating],
|
||
'wavenumber': complex,
|
||
}</code></pre>
|
||
<hr />
|
||
<h1 id="meanas.fdfd.waveguide_cyl">Module
|
||
<code>meanas.fdfd.waveguide_cyl</code></h1>
|
||
<p>Operators and helper functions for cylindrical waveguides with
|
||
unchanging cross-section.</p>
|
||
<p>WORK IN PROGRESS, CURRENTLY BROKEN</p>
|
||
<p>As the z-dependence is known, all the functions in this file assume a
|
||
2D grid
|
||
(i.e. <code>dxes = [[[dr_e_0, dx_e_1, ...], [dy_e_0, ...]], [[dr_h_0, ...], [dy_h_0, ...]]]</code>).</p>
|
||
<h2 id="functions-9">Functions</h2>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.cylindrical_operator">Function
|
||
<code>cylindrical_operator</code></h3>
|
||
<blockquote>
|
||
<p><code>def cylindrical_operator(omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], rmin: float) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Cylindrical coordinate waveguide operator of the form</p>
|
||
<p>(NOTE: See 10.1364/OL.33.001848) TODO: consider
|
||
10.1364/OE.20.021583</p>
|
||
<p>TODO</p>
|
||
<p>for use with a field vector of the form <code>[E_r, E_y]</code>.</p>
|
||
<p>This operator can be used to form an eigenvalue problem of the form A
|
||
@ [E_r, E_y] = wavenumber**2 * [E_r, E_y]</p>
|
||
<p>which can then be solved for the eigenmodes of the system (an
|
||
<code>exp(-i * wavenumber * theta)</code> theta-dependence is assumed
|
||
for the fields).</p>
|
||
<p>Args —–= <strong><code>omega</code></strong> : The angular frequency
|
||
of the system</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>rmin</code></strong></dt>
|
||
<dd>
|
||
Radius at the left edge of the simulation domain (minimum ‘x’)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.dxes2T">Function
|
||
<code>dxes2T</code></h3>
|
||
<blockquote>
|
||
<p><code>def dxes2T(dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], rmin=builtins.float) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.e2h">Function <code>e2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def e2h(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Returns an operator which, when applied to a vectorized E eigenfield,
|
||
produces the vectorized H eigenfield.</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representation of the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.exy2e">Function
|
||
<code>exy2e</code></h3>
|
||
<blockquote>
|
||
<p><code>def exy2e(wavenumber: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>e_xy</code> containing the
|
||
vectorized E_x and E_y fields, into a vectorized E containing all three
|
||
E components</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code> It should satisfy
|
||
<code>operator_e() @ e_xy == wavenumber**2 * e_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.exy2h">Function
|
||
<code>exy2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def exy2h(wavenumber: complex, omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Operator which transforms the vector <code>e_xy</code> containing the
|
||
vectorized E_x and E_y fields, into a vectorized H containing all three
|
||
H components</p>
|
||
<p>Args —–= <strong><code>wavenumber</code></strong> : Wavenumber
|
||
assuming fields have z-dependence of
|
||
<code>exp(-i * wavenumber * z)</code>. It should satisfy
|
||
<code>operator_e() @ e_xy == wavenumber**2 * e_xy</code></p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
The angular frequency of the system
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Vectorized magnetic permeability grid (default 1 everywhere)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix representing the operator.</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.linear_wavenumbers">Function
|
||
<code>linear_wavenumbers</code></h3>
|
||
<blockquote>
|
||
<p><code>def linear_wavenumbers(e_xys: numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], angular_wavenumbers: Union[collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], rmin: float) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate linear wavenumbers (1/distance) based on angular
|
||
wavenumbers (1/rad) and the mode’s energy distribution.</p>
|
||
<p>Args —–= <strong><code>e_xys</code></strong> : Vectorized mode fields
|
||
with shape [num_modes, 2 * x *y)</p>
|
||
<dl>
|
||
<dt><strong><code>angular_wavenumbers</code></strong></dt>
|
||
<dd>
|
||
Angular wavenumbers corresponding to the fields in <code>e_xys</code>
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Vectorized dielectric constant grid with shape (3, x, y)
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters <code>[dx_e, dx_h]</code> as described in <code><a
|
||
href="#meanas.fdmath.types">meanas.fdmath.types</a></code> (2D)
|
||
</dd>
|
||
<dt><strong><code>rmin</code></strong></dt>
|
||
<dd>
|
||
Radius at the left edge of the simulation domain (minimum ‘x’)
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= NDArray containing the calculated linear (1/distance)
|
||
wavenumbers</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.solve_mode">Function
|
||
<code>solve_mode</code></h3>
|
||
<blockquote>
|
||
<p><code>def solve_mode(mode_number: int, *args: Any, **kwargs: Any) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], complex]</code></p>
|
||
</blockquote>
|
||
<p>Wrapper around <code><a
|
||
href="#meanas.fdfd.waveguide_cyl.solve_modes">solve_modes()</a></code>
|
||
that solves for a single mode.</p>
|
||
<p>Args —–= <strong><code>mode_number</code></strong> : 0-indexed mode
|
||
number to solve for</p>
|
||
<dl>
|
||
<dt><strong><code>*args</code></strong></dt>
|
||
<dd>
|
||
passed to <code><a
|
||
href="#meanas.fdfd.waveguide_cyl.solve_modes">solve_modes()</a></code>
|
||
</dd>
|
||
<dt><strong><code>**kwargs</code></strong></dt>
|
||
<dd>
|
||
passed to <code><a
|
||
href="#meanas.fdfd.waveguide_cyl.solve_modes">solve_modes()</a></code>
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= (e_xy, angular_wavenumber)</p>
|
||
<h3 id="meanas.fdfd.waveguide_cyl.solve_modes">Function
|
||
<code>solve_modes</code></h3>
|
||
<blockquote>
|
||
<p><code>def solve_modes(mode_numbers: collections.abc.Sequence[int], omega: complex, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], rmin: float, mode_margin: int = 2) -> tuple[numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]]</code></p>
|
||
</blockquote>
|
||
<p>TODO: fixup Given a 2d (r, y) slice of epsilon, attempts to solve for
|
||
the eigenmode of the bent waveguide with the specified mode number.</p>
|
||
<p>Args —–= <strong><code>mode_number</code></strong> : Number of the
|
||
mode, 0-indexed</p>
|
||
<dl>
|
||
<dt><strong><code>omega</code></strong></dt>
|
||
<dd>
|
||
Angular frequency of the simulation
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types. The
|
||
first coordinate is assumed to be r, the second is y.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant
|
||
</dd>
|
||
<dt><strong><code>rmin</code></strong></dt>
|
||
<dd>
|
||
Radius of curvature for the simulation. This should be the minimum value
|
||
of r within the simulation domain.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>e_xys</code> : NDArray of vfdfield_t specifying
|
||
fields. First dimension is mode number.</p>
|
||
<dl>
|
||
<dt><code>angular_wavenumbers</code></dt>
|
||
<dd>
|
||
list of wavenumbers in 1/rad units.
|
||
</dd>
|
||
</dl>
|
||
<hr />
|
||
<h1 id="meanas.fdmath">Module <code>meanas.fdmath</code></h1>
|
||
<p>Basic discrete calculus for finite difference (fd) simulations.</p>
|
||
<h1 id="fields-functions-and-operators">Fields, Functions, and
|
||
Operators</h1>
|
||
<p>Discrete fields are stored in one of two forms:</p>
|
||
<ul>
|
||
<li>The <code>fdfield_t</code> form is a multidimensional
|
||
<code>numpy.NDArray</code>
|
||
<ul>
|
||
<li>For a scalar field, this is just <code>U[m, n, p]</code>, where
|
||
<code>m</code>, <code>n</code>, and <code>p</code> are discrete indices
|
||
referring to positions on the x, y, and z axes respectively.</li>
|
||
<li>For a vector field, the first index specifies which vector component
|
||
is accessed:
|
||
<code>E[:, m, n, p] = [Ex[m, n, p], Ey[m, n, p], Ez[m, n, p]]</code>.</li>
|
||
</ul></li>
|
||
<li>The <code>vfdfield_t</code> form is simply a vectorzied (i.e. 1D)
|
||
version of the <code>fdfield_t</code>, as obtained by <code><a
|
||
href="#meanas.fdmath.vectorization.vec">vec()</a></code> (effectively
|
||
just <code>numpy.ravel</code>)</li>
|
||
</ul>
|
||
<p>Operators which act on fields also come in two forms: + Python
|
||
functions, created by the functions in <code><a
|
||
href="#meanas.fdmath.functional">meanas.fdmath.functional</a></code>.
|
||
The generated functions act on fields in the <code>fdfield_t</code>
|
||
form. + Linear operators, usually 2D sparse matrices using
|
||
<code>scipy.sparse</code>, created by <code><a
|
||
href="#meanas.fdmath.operators">meanas.fdmath.operators</a></code>.
|
||
These operators act on vectorized fields in the <code>vfdfield_t</code>
|
||
form.</p>
|
||
<p>The operations performed should be equivalent:
|
||
<code>functional.op(*args)(E)</code> should be equivalent to
|
||
<code>unvec(operators.op(*args) @ vec(E), E.shape[1:])</code>.</p>
|
||
<p>Generally speaking the <code>field_t</code> form 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).</p>
|
||
<h1 id="discrete-calculus">Discrete calculus</h1>
|
||
<p>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.</p>
|
||
<h2 id="scalar-derivatives-and-cell-shifts">Scalar Derivatives And Cell
|
||
Shifts</h2>
|
||
<p>Define the discrete forward derivative as <eq env="displaymath">
|
||
[\tilde{\partial}_x f]_{m + \frac{1}{2}} = \frac{1}{\Delta_{x, m}} (f_{m
|
||
+ 1} - f_m) </eq> where <eq env="math">f</eq> is a function defined at
|
||
discrete locations on the x-axis (labeled using <eq env="math">m</eq>).
|
||
The value at <eq env="math">m</eq> occupies a length <eq
|
||
env="math">\Delta_{x, m}</eq> along the x-axis. Note that <eq
|
||
env="math">m</eq> is an index along the x-axis, <em>not</em> necessarily
|
||
an x-coordinate, since each length <eq env="math">\Delta_{x, m},
|
||
\Delta_{x, m+1}, ...</eq> is independently chosen.</p>
|
||
<p>If we treat <code>f</code> as a 1D array of values, with the
|
||
<code>i</code>-th value <code>f[i]</code> taking up a length
|
||
<code>dx[i]</code> along the x-axis, the forward derivative is</p>
|
||
<pre><code>deriv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]</code></pre>
|
||
<p>Likewise, discrete reverse derivative is <eq env="displaymath">
|
||
[\hat{\partial}_x f ]_{m - \frac{1}{2}} = \frac{1}{\Delta_{x, m}} (f_{m}
|
||
- f_{m - 1}) </eq> or</p>
|
||
<pre><code>deriv_back(f)[i] = (f[i] - f[i - 1]) / dx[i]</code></pre>
|
||
<p>The derivatives’ values are shifted by a half-cell relative to the
|
||
original function, and will have different cell widths if all the
|
||
<code>dx[i]</code> ( <eq env="math">\Delta_{x, m}</eq> ) are not
|
||
identical:</p>
|
||
<pre><code>[figure: derivatives and cell sizes]
|
||
dx0 dx1 dx2 dx3 cell sizes for function
|
||
----- ----- ----------- -----
|
||
______________________________
|
||
| | | |
|
||
f0 | f1 | f2 | f3 | function
|
||
_____|_____|___________|_____|
|
||
| | | |
|
||
| Df0 | Df1 | Df2 | Df3 forward derivative (periodic boundary)
|
||
__|_____|________|________|___
|
||
|
||
dx'3] dx'0 dx'1 dx'2 [dx'3 cell sizes for forward derivative
|
||
-- ----- -------- -------- ---
|
||
dx'0] dx'1 dx'2 dx'3 [dx'0 cell sizes for reverse derivative
|
||
______________________________
|
||
| | | |
|
||
| df1 | df2 | df3 | df0 reverse derivative (periodic boundary)
|
||
__|_____|________|________|___
|
||
|
||
Periodic boundaries are used here and elsewhere unless otherwise noted.</code></pre>
|
||
<p>In the above figure, <code>f0 =</code> <eq env="math">f_0</eq>,
|
||
<code>f1 =</code> <eq env="math">f_1</eq> <code>Df0 =</code> <eq
|
||
env="math">[\tilde{\partial}f]_{0 + \frac{1}{2}}</eq> <code>Df1 =</code>
|
||
<eq env="math">[\tilde{\partial}f]_{1 + \frac{1}{2}}</eq>
|
||
<code>df0 =</code> <eq env="math">[\hat{\partial}f]_{0 -
|
||
\frac{1}{2}}</eq> etc.</p>
|
||
<p>The fractional subscript <eq env="math">m + \frac{1}{2}</eq> is used
|
||
to indicate values defined at shifted locations relative to the original
|
||
<eq env="math">m</eq>, with corresponding lengths <eq env="displaymath">
|
||
\Delta_{x, m + \frac{1}{2}} = \frac{1}{2} * (\Delta_{x, m} + \Delta_{x,
|
||
m + 1}) </eq></p>
|
||
<p>Just as <eq env="math">m</eq> is not itself an x-coordinate, neither
|
||
is <eq env="math">m + \frac{1}{2}</eq>; carefully note the positions of
|
||
the various cells in the above figure vs their labels. If the positions
|
||
labeled with <eq env="math">m</eq> are considered the “base” or
|
||
“original” grid, the positions labeled with <eq env="math">m +
|
||
\frac{1}{2}</eq> are said to lie on a “dual” or “derived” grid.</p>
|
||
<p>For the remainder of the <code>Discrete calculus</code> section, all
|
||
figures will show constant-length cells in order to focus on the vector
|
||
derivatives themselves. See the <code>Grid description</code> section
|
||
below for additional information on this topic and generalization to
|
||
three dimensions.</p>
|
||
<h2 id="gradients-and-fore-vectors">Gradients and fore-vectors</h2>
|
||
<p>Expanding to three dimensions, we can define two gradients <eq
|
||
env="displaymath"> [\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}} </eq> <eq env="displaymath"> [\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}} </eq></p>
|
||
<p>or</p>
|
||
<pre><code>[code: gradients]
|
||
grad_forward(f)[i,j,k] = [Dx_forward(f)[i, j, k],
|
||
Dy_forward(f)[i, j, k],
|
||
Dz_forward(f)[i, j, k]]
|
||
= [(f[i + 1, j, k] - f[i, j, k]) / dx[i],
|
||
(f[i, j + 1, k] - f[i, j, k]) / dy[i],
|
||
(f[i, j, k + 1] - f[i, j, k]) / dz[i]]
|
||
|
||
grad_back(f)[i,j,k] = [Dx_back(f)[i, j, k],
|
||
Dy_back(f)[i, j, k],
|
||
Dz_back(f)[i, j, k]]
|
||
= [(f[i, j, k] - f[i - 1, j, k]) / dx[i],
|
||
(f[i, j, k] - f[i, j - 1, k]) / dy[i],
|
||
(f[i, j, k] - f[i, j, k - 1]) / dz[i]]</code></pre>
|
||
<p>The three derivatives in the gradient cause shifts in different
|
||
directions, so the x/y/z components of the resulting “vector” are
|
||
defined at different points: the x-component is shifted in the
|
||
x-direction, y in y, and z in z.</p>
|
||
<p>We call the resulting object a “fore-vector” or “back-vector”,
|
||
depending on the direction of the shift. We write it as <eq
|
||
env="displaymath"> \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}} </eq> <eq
|
||
env="displaymath"> \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}} </eq></p>
|
||
<pre><code>[figure: gradient / fore-vector]
|
||
(m, n+1, p+1) ______________ (m+1, n+1, p+1)
|
||
/: /|
|
||
/ : / |
|
||
/ : / |
|
||
(m, n, p+1)/_____________/ | The forward derivatives are defined
|
||
| : | | at the Dx, Dy, Dz points,
|
||
| :.........|...| but the forward-gradient fore-vector
|
||
z y Dz / | / is the set of all three
|
||
|/_x | Dy | / and is said to be "located" at (m,n,p)
|
||
|/ |/
|
||
(m, n, p)|_____Dx______| (m+1, n, p)</code></pre>
|
||
<h2 id="divergences">Divergences</h2>
|
||
<p>There are also two divergences,</p>
|
||
<p><eq env="displaymath"> 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} </eq></p>
|
||
<p><eq env="displaymath"> 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} </eq></p>
|
||
<p>or</p>
|
||
<pre><code>[code: divergences]
|
||
div_forward(g)[i,j,k] = Dx_forward(gx)[i, j, k] +
|
||
Dy_forward(gy)[i, j, k] +
|
||
Dz_forward(gz)[i, j, k]
|
||
= (gx[i + 1, j, k] - gx[i, j, k]) / dx[i] +
|
||
(gy[i, j + 1, k] - gy[i, j, k]) / dy[i] +
|
||
(gz[i, j, k + 1] - gz[i, j, k]) / dz[i]
|
||
|
||
div_back(g)[i,j,k] = Dx_back(gx)[i, j, k] +
|
||
Dy_back(gy)[i, j, k] +
|
||
Dz_back(gz)[i, j, k]
|
||
= (gx[i, j, k] - gx[i - 1, j, k]) / dx[i] +
|
||
(gy[i, j, k] - gy[i, j - 1, k]) / dy[i] +
|
||
(gz[i, j, k] - gz[i, j, k - 1]) / dz[i]</code></pre>
|
||
<p>where <code>g = [gx, gy, gz]</code> is a fore- or back-vector
|
||
field.</p>
|
||
<p>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 <eq env="math">(m,n,p)</eq> and not at the
|
||
locations of its components <eq env="math">(m \pm \frac{1}{2},n,p)</eq>
|
||
etc.</p>
|
||
<pre><code>[figure: divergence]
|
||
^^
|
||
(m-1/2, n+1/2, p+1/2) _____||_______ (m+1/2, n+1/2, p+1/2)
|
||
/: || ,, /|
|
||
/ : || // / | The divergence at (m, n, p) (the center
|
||
/ : // / | of this cube) of a fore-vector field
|
||
(m-1/2, n-1/2, p+1/2)/_____________/ | is the sum of the outward-pointing
|
||
| : | | fore-vector components, which are
|
||
z y <==|== :.........|.====> located at the face centers.
|
||
|/_x | / | /
|
||
| / // | / Note that in a nonuniform grid, each
|
||
|/ // || |/ dimension is normalized by the cell width.
|
||
(m-1/2, n-1/2, p-1/2)|____//_______| (m+1/2, n-1/2, p-1/2)
|
||
'' ||
|
||
VV</code></pre>
|
||
<h2 id="curls">Curls</h2>
|
||
<p>The two curls are then</p>
|
||
<p><eq env="displaymath"> \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} </eq></p>
|
||
<p>and</p>
|
||
<p><eq env="displaymath"> \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}} </eq></p>
|
||
<p>where <eq env="math">\hat{g}</eq> and <eq env="math">\tilde{g}</eq>
|
||
are located at <eq env="math">(m,n,p)</eq> with components at <eq
|
||
env="math">(m \pm \frac{1}{2},n,p)</eq> etc., while <eq
|
||
env="math">\hat{h}</eq> and <eq env="math">\tilde{h}</eq> are located at
|
||
<eq env="math">(m \pm \frac{1}{2}, n \pm \frac{1}{2}, p \pm
|
||
\frac{1}{2})</eq> with components at <eq env="math">(m, n \pm
|
||
\frac{1}{2}, p \pm \frac{1}{2})</eq> etc.</p>
|
||
<pre><code>[code: curls]
|
||
curl_forward(g)[i,j,k] = [Dy_forward(gz)[i, j, k] - Dz_forward(gy)[i, j, k],
|
||
Dz_forward(gx)[i, j, k] - Dx_forward(gz)[i, j, k],
|
||
Dx_forward(gy)[i, j, k] - Dy_forward(gx)[i, j, k]]
|
||
|
||
curl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],
|
||
Dz_back(gx)[i, j, k] - Dx_back(gz)[i, j, k],
|
||
Dx_back(gy)[i, j, k] - Dy_back(gx)[i, j, k]]</code></pre>
|
||
<p>For example, consider the forward curl, at (m, n, p), of a
|
||
back-vector field <code>g</code>, defined on a grid containing (m + 1/2,
|
||
n + 1/2, p + 1/2). The curl will be a fore-vector, so its z-component
|
||
will be defined at (m, n, p + 1/2). Take the nearest x- and y-components
|
||
of <code>g</code> in the xy plane where the curl’s z-component is
|
||
located; these are</p>
|
||
<pre><code>[curl components]
|
||
(m, n + 1/2, p + 1/2) : x-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)
|
||
(m + 1, n + 1/2, p + 1/2) : x-component of back-vector at (m + 3/2, n + 1/2, p + 1/2)
|
||
(m + 1/2, n , p + 1/2) : y-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)
|
||
(m + 1/2, n + 1 , p + 1/2) : y-component of back-vector at (m + 1/2, n + 3/2, p + 1/2)</code></pre>
|
||
<p>These four xy-components can be used to form a loop around the curl’s
|
||
z-component; its magnitude and sign is set by their loop-oriented sum
|
||
(i.e. two have their signs flipped to complete the loop).</p>
|
||
<pre><code>[figure: z-component of curl]
|
||
: |
|
||
z y : ^^ |
|
||
|/_x :....||.<.....| (m+1, n+1, p+1/2)
|
||
/ || /
|
||
| v || | ^
|
||
|/ |/
|
||
(m, n, p+1/2) |_____>______| (m+1, n, p+1/2)</code></pre>
|
||
<h1 id="maxwells-equations">Maxwell’s Equations</h1>
|
||
<p>If we discretize both space (m,n,p) and time (l), Maxwell’s equations
|
||
become</p>
|
||
<p><eq env="displaymath"> \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} </eq></p>
|
||
<p>with</p>
|
||
<p><eq env="displaymath"> \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} </eq></p>
|
||
<p>where the spatial subscripts are abbreviated as <eq
|
||
env="math">\vec{r} = (m, n, p)</eq> and <eq env="math">\vec{r} +
|
||
\frac{1}{2} = (m + \frac{1}{2}, n + \frac{1}{2}, p + \frac{1}{2})</eq>,
|
||
<eq env="math">\tilde{E}</eq> and <eq env="math">\hat{H}</eq> are the
|
||
electric and magnetic fields, <eq env="math">\tilde{J}</eq> and <eq
|
||
env="math">\hat{M}</eq> are the electric and magnetic current
|
||
distributions, and <eq env="math">\epsilon</eq> and <eq
|
||
env="math">\mu</eq> are the dielectric permittivity and magnetic
|
||
permeability.</p>
|
||
<p>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:</p>
|
||
<pre><code>[code: Maxwell's equations updates]
|
||
H[i, j, k] -= dt * (curl_forward(E)[i, j, k] + M[t, i, j, k]) / mu[i, j, k]
|
||
E[i, j, k] += dt * (curl_back( H)[i, j, k] + J[t, i, j, k]) / epsilon[i, j, k]</code></pre>
|
||
<p>Note that the E-field fore-vector and H-field back-vector are offset
|
||
by a half-cell, resulting in distinct locations for all six E- and
|
||
H-field components:</p>
|
||
<pre><code>[figure: Field components]
|
||
|
||
(m - 1/2,=> ____________Hx__________[H] <= r + 1/2 = (m + 1/2,
|
||
n + 1/2, /: /: /| n + 1/2,
|
||
z y p + 1/2) / : / : / | p + 1/2)
|
||
|/_x / : / : / |
|
||
/ : Ez__________Hy | Locations of the E- and
|
||
/ : : : /| | H-field components for the
|
||
(m - 1/2, / : : Ey...../.|..Hz [E] fore-vector at r = (m,n,p)
|
||
n - 1/2, =>/________________________/ | /| (the large cube's center)
|
||
p + 1/2) | : : / | | / | and [H] back-vector at r + 1/2
|
||
| : :/ | |/ | (the top right corner)
|
||
| : [E].......|.Ex |
|
||
| :.................|......| <= (m + 1/2, n + 1/2, p + 1/2)
|
||
| / | /
|
||
| / | /
|
||
| / | / This is the Yee discretization
|
||
| / | / scheme ("Yee cell").
|
||
r - 1/2 = | / | /
|
||
(m - 1/2, |/ |/
|
||
n - 1/2,=> |________________________| <= (m + 1/2, n - 1/2, p - 1/2)
|
||
p - 1/2)</code></pre>
|
||
<p>Each component forms its own grid, offset from the others:</p>
|
||
<pre><code>[figure: E-fields for adjacent cells]
|
||
|
||
H1__________Hx0_________H0
|
||
z y /: /|
|
||
|/_x / : / | This figure shows H back-vector locations
|
||
/ : / | H0, H1, etc. and their associated components
|
||
Hy1 : Hy0 | H0 = (Hx0, Hy0, Hz0) etc.
|
||
/ : / |
|
||
/ Hz1 / Hz0
|
||
H2___________Hx3_________H3 | The equivalent drawing for E would have
|
||
| : | | fore-vectors located at the cube's
|
||
| : | | center (and the centers of adjacent cubes),
|
||
| : | | with components on the cube's faces.
|
||
| H5..........Hx4...|......H4
|
||
| / | /
|
||
Hz2 / Hz2 /
|
||
| / | /
|
||
| Hy6 | Hy4
|
||
| / | /
|
||
|/ |/
|
||
H6__________Hx7__________H7</code></pre>
|
||
<p>The divergence equations can be derived by taking the divergence of
|
||
the curl equations and combining them with charge continuity, <eq
|
||
env="displaymath"> \hat{\nabla} \cdot \tilde{J} + \hat{\partial}_t \rho
|
||
= 0 </eq> implying that the discrete Maxwell’s equations do not produce
|
||
spurious charges.</p>
|
||
<h2 id="wave-equation">Wave Equation</h2>
|
||
<p>Taking the backward curl of the <eq env="math">\tilde{\nabla} \times
|
||
\tilde{E}</eq> equation and replacing the resulting <eq
|
||
env="math">\hat{\nabla} \times \hat{H}</eq> term using its respective
|
||
equation, and setting <eq env="math">\hat{M}</eq> to zero, we can form
|
||
the discrete wave equation:</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<h2 id="frequency-domain">Frequency Domain</h2>
|
||
<p>We can substitute in a time-harmonic fields</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>resulting in</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>This gives the frequency-domain wave equation,</p>
|
||
<p><eq env="displaymath">
|
||
\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} \\
|
||
</eq></p>
|
||
<h2 id="plane-waves-and-dispersion-relation">Plane Waves And Dispersion
|
||
Relation</h2>
|
||
<p>With uniform material distribution and no sources</p>
|
||
<p><eq env="displaymath">
|
||
\begin{aligned}
|
||
\mu_{\vec{r} + \frac{1}{2}} &= \mu \\
|
||
\epsilon_{\vec{r}} &= \epsilon \\
|
||
\tilde{J}_{\vec{r}} &= 0 \\
|
||
\end{aligned}
|
||
</eq></p>
|
||
<p>the frequency domain wave equation simplifies to</p>
|
||
<p><eq env="displaymath"> \hat{\nabla} \times \tilde{\nabla} \times
|
||
\tilde{E}_{\vec{r}} - \Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0
|
||
</eq></p>
|
||
<p>Since <eq env="math">\hat{\nabla} \cdot \tilde{E}_{\vec{r}} = 0</eq>,
|
||
we can simplify</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>and we get</p>
|
||
<p><eq env="displaymath"> \tilde{\nabla}^2 \tilde{E}_{\vec{r}} +
|
||
\Omega^2 \epsilon \mu \tilde{E}_{\vec{r}} = 0 </eq></p>
|
||
<p>We can convert this to three scalar-wave equations of the form</p>
|
||
<p><eq env="displaymath"> (\tilde{\nabla}^2 + K^2) \phi_{\vec{r}} = 0
|
||
</eq></p>
|
||
<p>with <eq env="math">K^2 = \Omega^2 \mu \epsilon</eq>. Now we let</p>
|
||
<p><eq env="displaymath"> \phi_{\vec{r}} = A e^{\imath (k_x m \Delta_x
|
||
+ k_y n \Delta_y + k_z p \Delta_z)} </eq></p>
|
||
<p>resulting in</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>with similar expressions for the y and z dimnsions (and <eq
|
||
env="math">K_y, K_z</eq>).</p>
|
||
<p>This implies</p>
|
||
<p><eq env="displaymath">
|
||
\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
|
||
</eq></p>
|
||
<p>where <eq env="math">c = \sqrt{\mu \epsilon}</eq>.</p>
|
||
<p>Assuming real <eq env="math">(k_x, k_y, k_z), \omega</eq> will be
|
||
real only if</p>
|
||
<p><eq env="displaymath"> 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}) </eq></p>
|
||
<p>If <eq env="math">\Delta_x = \Delta_y = \Delta_z</eq>, this
|
||
simplifies to <eq env="math">c \Delta_t < \Delta_x / \sqrt{3}</eq>.
|
||
This last form can be interpreted as enforcing causality; the distance
|
||
that light travels in one timestep (i.e., <eq env="math">c
|
||
\Delta_t</eq>) must be less than the diagonal of the smallest cell ( <eq
|
||
env="math">\Delta_x / \sqrt{3}</eq> when on a uniform cubic grid).</p>
|
||
<h1 id="grid-description">Grid description</h1>
|
||
<p>As described in the section on scalar discrete derivatives above,
|
||
cell widths (<code>dx[i]</code>, <code>dy[j]</code>, <code>dz[k]</code>)
|
||
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.</p>
|
||
<p>To get a better sense of how this works, let’s start by drawing a
|
||
grid with uniform <code>dy</code> and <code>dz</code> and nonuniform
|
||
<code>dx</code>. 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 nonuniform <code>dx</code> affects the
|
||
various components.</p>
|
||
<p>Place the E fore-vectors at integer indices <eq env="math">r = (m, n,
|
||
p)</eq> and the H back-vectors at fractional indices <eq env="math">r +
|
||
\frac{1}{2} = (m + \frac{1}{2}, n + \frac{1}{2},
|
||
p + \frac{1}{2})</eq>. Remember that these are indices and not
|
||
coordinates; they can correspond to arbitrary (monotonically increasing)
|
||
coordinates depending on the cell widths.</p>
|
||
<p>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.</p>
|
||
<p>The result looks something like this:</p>
|
||
<pre><code>[figure: Component centers]
|
||
p=
|
||
[H]__________Hx___________[H]_____Hx______[H] __ +1/2
|
||
z y /: /: /: /: /| | |
|
||
|/_x / : / : / : / : / | | |
|
||
/ : / : / : / : / | | |
|
||
Hy : Ez...........Hy : Ez......Hy | | |
|
||
/: : : : /: : : : /| | | |
|
||
/ : Hz : Ey....../.:..Hz : Ey./.|..Hz __ 0 | dz[0]
|
||
/ : /: : / / : /: : / / | /| | |
|
||
/_________________________/_______________/ | / | | |
|
||
| :/ : :/ | :/ : :/ | |/ | | |
|
||
| Ex : [E].......|..Ex : [E]..|..Ex | | |
|
||
| : | : | | | |
|
||
| [H]..........Hx....|......[H].....H|x.....[H] __ --------- (n=+1/2, p=-1/2)
|
||
| / | / | / / /
|
||
Hz / Hz / Hz / / /
|
||
| / | / | / / /
|
||
| Hy | Hy | Hy __ 0 / dy[0]
|
||
| / | / | / / /
|
||
| / | / | / / /
|
||
|/ |/ |/ / /
|
||
[H]__________Hx___________[H]_____Hx______[H] __ -1/2 /
|
||
=n
|
||
|------------|------------|-------|-------|
|
||
-1/2 0 +1/2 +1 +3/2 = m
|
||
|
||
------------------------- ----------------
|
||
dx[0] dx[1]
|
||
|
||
Part of a nonuniform "base grid", with labels specifying
|
||
positions of the various field components. [E] fore-vectors
|
||
are at the cell centers, and [H] back-vectors are at the
|
||
vertices. H components along the near (-y) top (+z) edge
|
||
have been omitted to make the insides of the cubes easier
|
||
to visualize.</code></pre>
|
||
<p>The 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 <code>m = +1/2</code>: it is shifted in the
|
||
x-direction by a half-cell from the E fore-vector at <code>m = 0</code>
|
||
(labeled <code>[E]</code> in the figure). It corresponds to a volume
|
||
between <code>m = 0</code> and <code>m = +1</code> (the other dimensions
|
||
are not shifted, i.e. they are still bounded by
|
||
<code>n, p = +-1/2</code>). (See figure below). Since <code>m</code> is
|
||
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 quantity <code>dx'[0] = (dx[0] + dx[1]) / 2</code> rather
|
||
than the base <code>dx</code>. (See also <code>Scalar derivatives and
|
||
cell shifts</code>).</p>
|
||
<pre><code>[figure: Ex volumes]
|
||
p=
|
||
<_________________________________________> __ +1/2
|
||
z y << /: / /: >> | |
|
||
|/_x < < / : / / : > > | |
|
||
< < / : / / : > > | |
|
||
< < / : / / : > > | |
|
||
<: < / : : / : >: > | |
|
||
< : < / : : / : > : > __ 0 | dz[0]
|
||
< : < / : : / :> : > | |
|
||
<____________/____________________/_______> : > | |
|
||
< : < | : : | > : > | |
|
||
< Ex < | : Ex | > Ex > | |
|
||
< : < | : : | > : > | |
|
||
< : <....|.......:........:...|.......>...:...> __ --------- (n=+1/2, p=-1/2)
|
||
< : < | / : /| /> : > / /
|
||
< : < | / : / | / > : > / /
|
||
< :< | / :/ | / > :> / /
|
||
< < | / : | / > > _ 0 / dy[0]
|
||
< < | / | / > > / /
|
||
< < | / | / > > / /
|
||
<< |/ |/ >> / /
|
||
<____________|____________________|_______> __ -1/2 /
|
||
=n
|
||
|------------|------------|-------|-------|
|
||
-1/2 0 +1/2 +1 +3/2 = m
|
||
|
||
~------------ -------------------- -------~
|
||
dx'[-1] dx'[0] dx'[1]
|
||
|
||
The Ex values are positioned on the x-faces of the base
|
||
grid. They represent the Ex field in volumes shifted by
|
||
a half-cell in the x-dimension, as shown here. Only the
|
||
center cell (with width dx'[0]) is fully shown; the
|
||
other two are truncated (shown using >< markers).
|
||
|
||
Note that the Ex positions are the in the same positions
|
||
as the previous figure; only the cell boundaries have moved.
|
||
Also note that the points at which Ex is defined are not
|
||
necessarily centered in the volumes they represent; non-
|
||
uniform cell sizes result in off-center volumes like the
|
||
center cell here.</code></pre>
|
||
<p>The next figure shows the volumes corresponding to the Hy components,
|
||
which are shifted in two dimensions (x and z) compared to the base
|
||
grid.</p>
|
||
<pre><code>[figure: Hy volumes]
|
||
p=
|
||
z y mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm __ +1/2 s
|
||
|/_x << m: m: >> | |
|
||
< < m : m : > > | | dz'[1]
|
||
< < m : m : > > | |
|
||
Hy........... m........Hy...........m......Hy > | |
|
||
< < m : m : > > | |
|
||
< ______ m_____:_______________m_____:_>______ __ 0
|
||
< < m /: m / > > | |
|
||
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm > | |
|
||
< < | / : | / > > | | dz'[0]
|
||
< < | / : | / > > | |
|
||
< < | / : | / > > | |
|
||
< wwwww|w/wwwwwwwwwwwwwwwwwww|w/wwwww>wwwwwwww __ s
|
||
< < |/ w |/ w> > / /
|
||
_____________|_____________________|________ > / /
|
||
< < | w | w > > / /
|
||
< Hy........|...w........Hy.......|...w...>..Hy _ 0 / dy[0]
|
||
< < | w | w > > / /
|
||
<< | w | w > > / /
|
||
< |w |w >> / /
|
||
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww __ -1/2 /
|
||
|
||
|------------|------------|--------|-------|
|
||
-1/2 0 +1/2 +1 +3/2 = m
|
||
|
||
~------------ --------------------- -------~
|
||
dx'[-1] dx'[0] dx'[1]
|
||
|
||
The Hy values are positioned on the y-edges of the base
|
||
grid. Again here, the 'Hy' labels represent the same points
|
||
as in the basic grid figure above; the edges have shifted
|
||
by a half-cell along the x- and z-axes.
|
||
|
||
The grid lines _|:/ are edges of the area represented by
|
||
each Hy value, and the lines drawn using <m>.w represent
|
||
edges where a cell's faces extend beyond the drawn area
|
||
(i.e. where the drawing is truncated in the x- or z-
|
||
directions).</code></pre>
|
||
<h2 id="datastructure-dx_lists_t">Datastructure: dx_lists_t</h2>
|
||
<p>In 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.</p>
|
||
<p>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:</p>
|
||
<pre><code>[dx, dy, dz] = [[dx[0], dx[1], ...], [dy[0], ...], [dz[0], ...]]</code></pre>
|
||
<p>Note 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.</p>
|
||
<p>Knowing the base grid’s cell widths and the boundary conditions
|
||
(periodic unless otherwise noted) is enough information to calculate the
|
||
cell widths <code>dx'</code>, <code>dy'</code>, and <code>dz'</code> for
|
||
the derived grids.</p>
|
||
<p>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.</p>
|
||
<p>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:</p>
|
||
<pre><code> [[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],
|
||
[[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]</code></pre>
|
||
<p>where <code>dx_e[0]</code> is the x-width of the <code>m=0</code>
|
||
cells, as used when calculating dE/dx, and <code>dy_h[0]</code> is the
|
||
y-width of the <code>n=0</code> cells, as used when calculating dH/dy,
|
||
etc.</p>
|
||
<h1 id="permittivity-and-permeability">Permittivity and
|
||
Permeability</h1>
|
||
<p>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 <code>epsilon</code> and <code>mu</code> can also be
|
||
different for all three field components, even when representing a
|
||
simple planar interface between two isotropic materials.</p>
|
||
<p>As a result, <code>epsilon</code> and <code>mu</code> are taken to
|
||
have the same dimensions as the field, and composed of the three
|
||
diagonal tensor components:</p>
|
||
<pre><code>[equations: epsilon_and_mu]
|
||
epsilon = [epsilon_xx, epsilon_yy, epsilon_zz]
|
||
mu = [mu_xx, mu_yy, mu_zz]</code></pre>
|
||
<p>or</p>
|
||
<p><eq env="displaymath">
|
||
\epsilon = \begin{bmatrix} \epsilon_{xx} & 0 & 0 \\
|
||
0 & \epsilon_{yy} & 0 \\
|
||
0 & 0 & \epsilon_{zz} \end{bmatrix}
|
||
</eq> <eq env="displaymath">
|
||
\mu = \begin{bmatrix} \mu_{xx} & 0 & 0 \\
|
||
0 & \mu_{yy} & 0 \\
|
||
0 & 0 & \mu_{zz} \end{bmatrix}
|
||
</eq></p>
|
||
<p>where the off-diagonal terms (e.g. <code>epsilon_xy</code>) are
|
||
assumed to be zero.</p>
|
||
<p>High-accuracy volumetric integration of shapes on multiple grids can
|
||
be performed by the <a
|
||
href="https://mpxd.net/code/jan/gridlock">gridlock</a> module.</p>
|
||
<p>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.</p>
|
||
<h2 id="sub-modules-2">Sub-modules</h2>
|
||
<ul>
|
||
<li><a
|
||
href="#meanas.fdmath.functional">meanas.fdmath.functional</a></li>
|
||
<li><a href="#meanas.fdmath.operators">meanas.fdmath.operators</a></li>
|
||
<li><a href="#meanas.fdmath.types">meanas.fdmath.types</a></li>
|
||
<li><a
|
||
href="#meanas.fdmath.vectorization">meanas.fdmath.vectorization</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.fdmath.functional">Module
|
||
<code>meanas.fdmath.functional</code></h1>
|
||
<p>Math functions for finite difference simulations</p>
|
||
<p>Basic discrete calculus etc.</p>
|
||
<h2 id="functions-10">Functions</h2>
|
||
<h3 id="meanas.fdmath.functional.curl_back">Function
|
||
<code>curl_back</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_back(dx_h: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> collections.abc.Callable[[~TT], ~TT]</code></p>
|
||
</blockquote>
|
||
<p>Create a function which takes the backward curl of a field.</p>
|
||
<p>Args —–= <strong><code>dx_h</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= Function <code>f</code> for taking the discrete backward
|
||
curl of a field, <code>f(H)</code> -> curlH <eq env="math">= \nabla_b
|
||
\times H</eq></p>
|
||
<h3 id="meanas.fdmath.functional.curl_back_parts">Function
|
||
<code>curl_back_parts</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_back_parts(dx_h: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> collections.abc.Callable</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdmath.functional.curl_forward">Function
|
||
<code>curl_forward</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_forward(dx_e: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> collections.abc.Callable[[~TT], ~TT]</code></p>
|
||
</blockquote>
|
||
<p>Curl operator for use with the E field.</p>
|
||
<p>Args —–= <strong><code>dx_e</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= Function <code>f</code> for taking the discrete forward
|
||
curl of a field, <code>f(E)</code> -> curlE <eq env="math">= \nabla_f
|
||
\times E</eq></p>
|
||
<h3 id="meanas.fdmath.functional.curl_forward_parts">Function
|
||
<code>curl_forward_parts</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_forward_parts(dx_e: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> collections.abc.Callable</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdmath.functional.deriv_back">Function
|
||
<code>deriv_back</code></h3>
|
||
<blockquote>
|
||
<p><code>def deriv_back(dx_h: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> tuple[collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]]</code></p>
|
||
</blockquote>
|
||
<p>Utility operators for taking discretized derivatives (forward
|
||
variant).</p>
|
||
<p>Args —–= <strong><code>dx_h</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= List of functions for taking forward derivatives along
|
||
each axis.</p>
|
||
<h3 id="meanas.fdmath.functional.deriv_forward">Function
|
||
<code>deriv_forward</code></h3>
|
||
<blockquote>
|
||
<p><code>def deriv_forward(dx_e: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]] | None = None) -> tuple[collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]]</code></p>
|
||
</blockquote>
|
||
<p>Utility operators for taking discretized derivatives (backward
|
||
variant).</p>
|
||
<p>Args —–= <strong><code>dx_e</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= List of functions for taking forward derivatives along
|
||
each axis.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdmath.operators">Module
|
||
<code>meanas.fdmath.operators</code></h1>
|
||
<p>Matrix operators for finite difference simulations</p>
|
||
<p>Basic discrete calculus etc.</p>
|
||
<h2 id="functions-11">Functions</h2>
|
||
<h3 id="meanas.fdmath.operators.avg_back">Function
|
||
<code>avg_back</code></h3>
|
||
<blockquote>
|
||
<p><code>def avg_back(axis: int, shape: collections.abc.Sequence[int]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Backward average operator <code>(x4 = (x4 + x3) / 2)</code></p>
|
||
<p>Args —–= <strong><code>axis</code></strong> : Axis to average along
|
||
(x=0, y=1, z=2)</p>
|
||
<dl>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
Shape of the grid to average
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for backward average operation.</p>
|
||
<h3 id="meanas.fdmath.operators.avg_forward">Function
|
||
<code>avg_forward</code></h3>
|
||
<blockquote>
|
||
<p><code>def avg_forward(axis: int, shape: collections.abc.Sequence[int]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Forward average operator <code>(x4 = (x4 + x5) / 2)</code></p>
|
||
<p>Args —–= <strong><code>axis</code></strong> : Axis to average along
|
||
(x=0, y=1, z=2)</p>
|
||
<dl>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
Shape of the grid to average
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for forward average operation.</p>
|
||
<h3 id="meanas.fdmath.operators.cross">Function <code>cross</code></h3>
|
||
<blockquote>
|
||
<p><code>def cross(B: collections.abc.Sequence[scipy.sparse._matrix.spmatrix]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Cross product operator</p>
|
||
<p>Args —–= <strong><code>B</code></strong> : List <code>[Bx, By,
|
||
Bz]</code> of sparse matrices corresponding to the x, y, z portions of
|
||
the operator on the left side of the cross product.</p>
|
||
<p>Returns —–= Sparse matrix corresponding to (B x), where x is the
|
||
cross product.</p>
|
||
<h3 id="meanas.fdmath.operators.curl_back">Function
|
||
<code>curl_back</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_back(dx_h: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Curl operator for use with the H field.</p>
|
||
<p>Args —–= <strong><code>dx_h</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= Sparse matrix for taking the discretized curl of the
|
||
H-field</p>
|
||
<h3 id="meanas.fdmath.operators.curl_forward">Function
|
||
<code>curl_forward</code></h3>
|
||
<blockquote>
|
||
<p><code>def curl_forward(dx_e: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Curl operator for use with the E field.</p>
|
||
<p>Args —–= <strong><code>dx_e</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= Sparse matrix for taking the discretized curl of the
|
||
E-field</p>
|
||
<h3 id="meanas.fdmath.operators.deriv_back">Function
|
||
<code>deriv_back</code></h3>
|
||
<blockquote>
|
||
<p><code>def deriv_back(dx_h: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]) -> list[scipy.sparse._matrix.spmatrix]</code></p>
|
||
</blockquote>
|
||
<p>Utility operators for taking discretized derivatives (backward
|
||
variant).</p>
|
||
<p>Args —–= <strong><code>dx_h</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= List of operators for taking forward derivatives along
|
||
each axis.</p>
|
||
<h3 id="meanas.fdmath.operators.deriv_forward">Function
|
||
<code>deriv_forward</code></h3>
|
||
<blockquote>
|
||
<p><code>def deriv_forward(dx_e: collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]) -> list[scipy.sparse._matrix.spmatrix]</code></p>
|
||
</blockquote>
|
||
<p>Utility operators for taking discretized derivatives (forward
|
||
variant).</p>
|
||
<p>Args —–= <strong><code>dx_e</code></strong> : Lists of cell sizes for
|
||
all axes <code>[[dx_0, dx_1, …], [dy_0, dy_1, …], …]</code>.</p>
|
||
<p>Returns —–= List of operators for taking forward derivatives along
|
||
each axis.</p>
|
||
<h3 id="meanas.fdmath.operators.shift_circ">Function
|
||
<code>shift_circ</code></h3>
|
||
<blockquote>
|
||
<p><code>def shift_circ(axis: int, shape: collections.abc.Sequence[int], shift_distance: int = 1) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Utility operator for performing a circular shift along a specified
|
||
axis by a specified number of elements.</p>
|
||
<p>Args —–= <strong><code>axis</code></strong> : Axis to shift along.
|
||
x=0, y=1, z=2</p>
|
||
<dl>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
Shape of the grid being shifted
|
||
</dd>
|
||
<dt><strong><code>shift_distance</code></strong></dt>
|
||
<dd>
|
||
Number of cells to shift by. May be negative. Default 1.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for performing the circular shift.</p>
|
||
<h3 id="meanas.fdmath.operators.shift_with_mirror">Function
|
||
<code>shift_with_mirror</code></h3>
|
||
<blockquote>
|
||
<p><code>def shift_with_mirror(axis: int, shape: collections.abc.Sequence[int], shift_distance: int = 1) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Utility operator for performing an n-element shift along a specified
|
||
axis, with mirror boundary conditions applied to the cells beyond the
|
||
receding edge.</p>
|
||
<p>Args —–= <strong><code>axis</code></strong> : Axis to shift along.
|
||
x=0, y=1, z=2</p>
|
||
<dl>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
Shape of the grid being shifted
|
||
</dd>
|
||
<dt><strong><code>shift_distance</code></strong></dt>
|
||
<dd>
|
||
Number of cells to shift by. May be negative. Default 1.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Sparse matrix for performing the shift-with-mirror.</p>
|
||
<h3 id="meanas.fdmath.operators.vec_cross">Function
|
||
<code>vec_cross</code></h3>
|
||
<blockquote>
|
||
<p><code>def vec_cross(b: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]) -> scipy.sparse._matrix.spmatrix</code></p>
|
||
</blockquote>
|
||
<p>Vector cross product operator</p>
|
||
<p>Args —–= <strong><code>b</code></strong> : Vector on the left side of
|
||
the cross product.</p>
|
||
<p>Returns —–= Sparse matrix corresponding to (b x), where x is the
|
||
cross product.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdmath.types">Module
|
||
<code>meanas.fdmath.types</code></h1>
|
||
<p>Types shared across multiple submodules</p>
|
||
<h2 id="variables-1">Variables</h2>
|
||
<h3 id="meanas.fdmath.types.cfdfield_t">Variable
|
||
<code>cfdfield_t</code></h3>
|
||
<p>Complex vector field with shape (3, X, Y, Z) (e.g. <code>[E_x, E_y,
|
||
E_z]</code>)</p>
|
||
<h3 id="meanas.fdmath.types.cfdfield_updater_t">Variable
|
||
<code>cfdfield_updater_t</code></h3>
|
||
<p>Convenience type for functions which take and return an
|
||
cfdfield_t</p>
|
||
<h3 id="meanas.fdmath.types.dx_lists_mut">Variable
|
||
<code>dx_lists_mut</code></h3>
|
||
<p>Mutable version of <code><a
|
||
href="#meanas.fdmath.types.dx_lists_t">dx_lists_t</a></code></p>
|
||
<h3 id="meanas.fdmath.types.dx_lists_t">Variable
|
||
<code>dx_lists_t</code></h3>
|
||
<p>‘dxes’ datastructure which contains grid cell width information in
|
||
the following format:</p>
|
||
<pre><code>[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],
|
||
[[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]</code></pre>
|
||
<p>where <code>dx_e[0]</code> is the x-width of the <code>x=0</code>
|
||
cells, as used when calculating dE/dx, and <code>dy_h[0]</code> is the
|
||
y-width of the <code>y=0</code> cells, as used when calculating dH/dy,
|
||
etc.</p>
|
||
<h3 id="meanas.fdmath.types.fdfield_t">Variable
|
||
<code>fdfield_t</code></h3>
|
||
<p>Vector field with shape (3, X, Y, Z) (e.g. <code>[E_x, E_y,
|
||
E_z]</code>)</p>
|
||
<h3 id="meanas.fdmath.types.fdfield_updater_t">Variable
|
||
<code>fdfield_updater_t</code></h3>
|
||
<p>Convenience type for functions which take and return an fdfield_t</p>
|
||
<h3 id="meanas.fdmath.types.vcfdfield_t">Variable
|
||
<code>vcfdfield_t</code></h3>
|
||
<p>Linearized complex vector field (single vector of length
|
||
3<em>X</em>Y*Z)</p>
|
||
<h3 id="meanas.fdmath.types.vfdfield_t">Variable
|
||
<code>vfdfield_t</code></h3>
|
||
<p>Linearized vector field (single vector of length 3<em>X</em>Y*Z)</p>
|
||
<hr />
|
||
<h1 id="meanas.fdmath.vectorization">Module
|
||
<code>meanas.fdmath.vectorization</code></h1>
|
||
<p>Functions for moving between a vector field (list of 3 ndarrays,
|
||
<code>[f_x, f_y, f_z]</code>) and a 1D array representation of that
|
||
field <code>[f_x0, f_x1, f_x2,… f_y0,… f_z0,…]</code>. Vectorized
|
||
versions of the field use row-major (ie., C-style) ordering.</p>
|
||
<h2 id="functions-12">Functions</h2>
|
||
<h3 id="meanas.fdmath.vectorization.unvec">Function
|
||
<code>unvec</code></h3>
|
||
<blockquote>
|
||
<p><code>def unvec(v: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]] | None, shape: collections.abc.Sequence[int], nvdim: int = 3) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]] | None</code></p>
|
||
</blockquote>
|
||
<p>Perform the inverse of vec(): take a 1D ndarray and output an
|
||
<code>nvdim</code>-component field of form e.g. <code>[f_x, f_y,
|
||
f_z]</code> (<code>nvdim=3</code>) where each of <code>f_*</code> is a
|
||
len(shape)-dimensional ndarray.</p>
|
||
<p>Returns <code>None</code> if called with <code>v=None</code>.</p>
|
||
<p>Args —–= <strong><code>v</code></strong> : 1D ndarray representing a
|
||
vector field of shape shape (or None)</p>
|
||
<dl>
|
||
<dt><strong><code>shape</code></strong></dt>
|
||
<dd>
|
||
shape of the vector field
|
||
</dd>
|
||
<dt><strong><code>nvdim</code></strong></dt>
|
||
<dd>
|
||
Number of components in each vector
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>[f_x, f_y, f_z]</code> where each <code>f_</code>
|
||
is a <code>len(shape)</code> dimensional ndarray (or
|
||
<code>None</code>)</p>
|
||
<h3 id="meanas.fdmath.vectorization.vec">Function <code>vec</code></h3>
|
||
<blockquote>
|
||
<p><code>def vec(f: Union[numpy.ndarray[Any, numpy.dtype[numpy.floating]], numpy.ndarray[Any, numpy.dtype[numpy.complexfloating]], collections.abc.Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]], ForwardRef(None)]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | numpy.ndarray[typing.Any, numpy.dtype[numpy.complexfloating]] | None</code></p>
|
||
</blockquote>
|
||
<p>Create a 1D ndarray from a vector field which spans a 1-3D
|
||
region.</p>
|
||
<p>Returns <code>None</code> if called with <code>f=None</code>.</p>
|
||
<p>Args —–= <strong><code>f</code></strong> : A vector field,
|
||
e.g. <code>[f_x, f_y, f_z]</code> where each <code>f_</code> component
|
||
is a 1- to 3-D ndarray (<code>f_*</code> should all be the same size).
|
||
Doesn’t fail with <code>f=None</code>.</p>
|
||
<p>Returns —–= 1D ndarray containing the linearized field (or
|
||
<code>None</code>)</p>
|
||
<hr />
|
||
<h1 id="meanas.fdtd">Module <code>meanas.fdtd</code></h1>
|
||
<p>Utilities for running finite-difference time-domain (FDTD)
|
||
simulations</p>
|
||
<p>See the discussion of <code>Maxwell's Equations</code> in <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code> for basic mathematical
|
||
background.</p>
|
||
<h1 id="timestep">Timestep</h1>
|
||
<p>From the discussion of “Plane waves and the Dispersion relation” in
|
||
<code><a href="#meanas.fdmath">meanas.fdmath</a></code>, we have</p>
|
||
<p><eq env="displaymath"> 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}) </eq></p>
|
||
<p>or, if <eq env="math">\Delta_x = \Delta_y = \Delta_z</eq>, then <eq
|
||
env="math">c \Delta_t < \frac{\Delta_x}{\sqrt{3}}</eq>.</p>
|
||
<p>Based on this, we can set</p>
|
||
<pre><code>dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2)</code></pre>
|
||
<p>The <code>dx_min</code>, <code>dy_min</code>, <code>dz_min</code>
|
||
should be the minimum value across both the base and derived grids.</p>
|
||
<h1 id="poynting-vector-and-energy-conservation">Poynting Vector and
|
||
Energy Conservation</h1>
|
||
<p>Let</p>
|
||
<p><eq env="displaymath"> \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}
|
||
</eq></p>
|
||
<p>where <eq env="math">\vec{r} = (m, n, p)</eq> and <eq
|
||
env="math">\otimes</eq> is a modified cross product in which the <eq
|
||
env="math">\tilde{E}</eq> terms are shifted as indicated.</p>
|
||
<p>By taking the divergence and rearranging terms, we can show that</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>where in the last line the spatial subscripts have been dropped to
|
||
emphasize the time subscripts <eq env="math">l, l'</eq>, i.e.</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>etc. For <eq env="math">l' = l + \frac{1}{2}</eq> we get</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>and for <eq env="math">l' = l - \frac{1}{2}</eq>,</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>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:</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>Rewriting the Poynting theorem in terms of the energy
|
||
expressions,</p>
|
||
<p><eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>This result is exact and should practically hold to within numerical
|
||
precision. No time- or spatial-averaging is necessary.</p>
|
||
<p>Note that each value of <eq env="math">J</eq> contributes to the
|
||
energy twice (i.e. once per field update) despite only causing the value
|
||
of <eq env="math">E</eq> to change once (same for <eq env="math">M</eq>
|
||
and <eq env="math">H</eq>).</p>
|
||
<h1 id="sources">Sources</h1>
|
||
<p>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.</p>
|
||
<p>The Ricker wavelet (normalized second derivative of a Gaussian) is
|
||
commonly used for the pulse shape. It can be written</p>
|
||
<p><eq env="displaymath"> f_r(t) = (1 - \frac{1}{2} (\omega (t -
|
||
\tau))^2) e^{-(\frac{\omega (t - \tau)}{2})^2} </eq></p>
|
||
<p>with <eq env="math">\tau > \frac{2 * \pi}{\omega}</eq> as a
|
||
minimum delay to avoid a discontinuity at t=0 (assuming the source is
|
||
off for t<0 this gives <eq env="math">\sim 10^{-3}</eq> error at
|
||
t=0).</p>
|
||
<h1 id="boundary-conditions">Boundary conditions</h1>
|
||
<h1 id="todo-notes-about-boundaries-pmls">TODO notes about boundaries /
|
||
PMLs</h1>
|
||
<h2 id="sub-modules-3">Sub-modules</h2>
|
||
<ul>
|
||
<li><a href="#meanas.fdtd.base">meanas.fdtd.base</a></li>
|
||
<li><a href="#meanas.fdtd.boundaries">meanas.fdtd.boundaries</a></li>
|
||
<li><a href="#meanas.fdtd.energy">meanas.fdtd.energy</a></li>
|
||
<li><a href="#meanas.fdtd.pml">meanas.fdtd.pml</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.fdtd.base">Module <code>meanas.fdtd.base</code></h1>
|
||
<p>Basic FDTD field updates</p>
|
||
<h2 id="functions-13">Functions</h2>
|
||
<h3 id="meanas.fdtd.base.maxwell_e">Function <code>maxwell_e</code></h3>
|
||
<blockquote>
|
||
<p><code>def maxwell_e(dt: float, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]</code></p>
|
||
</blockquote>
|
||
<p>Build a function which performs a portion the time-domain E-field
|
||
update,</p>
|
||
<pre><code>E += curl_back(H[t]) / epsilon</code></pre>
|
||
<p>The full update should be</p>
|
||
<pre><code>E += (curl_back(H[t]) + J) / epsilon</code></pre>
|
||
<p>which requires an additional step of <code>E += J / epsilon</code>
|
||
which is not performed by the generated function.</p>
|
||
<p>See <code><a href="#meanas.fdmath">meanas.fdmath</a></code> for
|
||
descriptions of</p>
|
||
<ul>
|
||
<li>This update step: “Maxwell’s equations” section</li>
|
||
<li><code>dxes</code>: “Datastructure: dx_lists_t” section</li>
|
||
<li><code>epsilon</code>: “Permittivity and Permeability” section</li>
|
||
</ul>
|
||
<p>Also see the “Timestep” section of <code><a
|
||
href="#meanas.fdtd">meanas.fdtd</a></code> for a discussion of the
|
||
<code>dt</code> parameter.</p>
|
||
<p>Args —–= <strong><code>dt</code></strong> : Timestep. See <code><a
|
||
href="#meanas.fdtd">meanas.fdtd</a></code> for details.</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function
|
||
<code>f(E_old, H_old, epsilon) -> E_new</code>.</p>
|
||
<h3 id="meanas.fdtd.base.maxwell_h">Function <code>maxwell_h</code></h3>
|
||
<blockquote>
|
||
<p><code>def maxwell_h(dt: float, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]</code></p>
|
||
</blockquote>
|
||
<p>Build a function which performs part of the time-domain H-field
|
||
update,</p>
|
||
<pre><code>H -= curl_forward(E[t]) / mu</code></pre>
|
||
<p>The full update should be</p>
|
||
<pre><code>H -= (curl_forward(E[t]) + M) / mu</code></pre>
|
||
<p>which requires an additional step of <code>H -= M / mu</code> which
|
||
is not performed by the generated function; this step can be omitted if
|
||
there is no magnetic current <code>M</code>.</p>
|
||
<p>See <code><a href="#meanas.fdmath">meanas.fdmath</a></code> for
|
||
descriptions of</p>
|
||
<ul>
|
||
<li>This update step: “Maxwell’s equations” section</li>
|
||
<li><code>dxes</code>: “Datastructure: dx_lists_t” section</li>
|
||
<li><code>mu</code>: “Permittivity and Permeability” section</li>
|
||
</ul>
|
||
<p>Also see the “Timestep” section of <code><a
|
||
href="#meanas.fdtd">meanas.fdtd</a></code> for a discussion of the
|
||
<code>dt</code> parameter.</p>
|
||
<p>Args —–= <strong><code>dt</code></strong> : Timestep. See <code><a
|
||
href="#meanas.fdtd">meanas.fdtd</a></code> for details.</p>
|
||
<dl>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Function
|
||
<code>f(E_old, H_old, epsilon) -> E_new</code>.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdtd.boundaries">Module
|
||
<code>meanas.fdtd.boundaries</code></h1>
|
||
<p>Boundary conditions</p>
|
||
<p>#TODO conducting boundary documentation</p>
|
||
<h2 id="functions-14">Functions</h2>
|
||
<h3 id="meanas.fdtd.boundaries.conducting_boundary">Function
|
||
<code>conducting_boundary</code></h3>
|
||
<blockquote>
|
||
<p><code>def conducting_boundary(direction: int, polarity: int) -> tuple[collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], collections.abc.Callable[..., numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]]]</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<h1 id="meanas.fdtd.energy">Module <code>meanas.fdtd.energy</code></h1>
|
||
<h2 id="functions-15">Functions</h2>
|
||
<h3 id="meanas.fdtd.energy.delta_energy_e2h">Function
|
||
<code>delta_energy_e2h</code></h3>
|
||
<blockquote>
|
||
<p><code>def delta_energy_e2h(dt: float, h0: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e1: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h2: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e3: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Change in energy during the half-step from <code>e1</code> to
|
||
<code>h2</code>.</p>
|
||
<p>This is just from (h2 * h2 + e3 * e1) - (e1 * e1 + h0 * h2)</p>
|
||
<p>Args —–= <strong><code>h0</code></strong> : E-field one half-timestep
|
||
before the start of the energy delta.</p>
|
||
<dl>
|
||
<dt><strong><code>e1</code></strong></dt>
|
||
<dd>
|
||
H-field at the start of the energy delta.
|
||
</dd>
|
||
<dt><strong><code>h2</code></strong></dt>
|
||
<dd>
|
||
E-field at the end of the energy delta (one half-timestep after
|
||
<code>e1</code>).
|
||
</dd>
|
||
<dt><strong><code>e3</code></strong></dt>
|
||
<dd>
|
||
H-field one half-timestep after the end of the energy delta.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability distribution.
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Change in energy from the time of <code>e1</code> to the
|
||
time of <code>h2</code>.</p>
|
||
<h3 id="meanas.fdtd.energy.delta_energy_h2e">Function
|
||
<code>delta_energy_h2e</code></h3>
|
||
<blockquote>
|
||
<p><code>def delta_energy_h2e(dt: float, e0: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h1: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e2: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h3: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Change in energy during the half-step from <code>h1</code> to
|
||
<code>e2</code>.</p>
|
||
<p>This is just from (e2 * e2 + h3 * h1) - (h1 * h1 + e0 * e2)</p>
|
||
<p>Args —–= <strong><code>e0</code></strong> : E-field one half-timestep
|
||
before the start of the energy delta.</p>
|
||
<dl>
|
||
<dt><strong><code>h1</code></strong></dt>
|
||
<dd>
|
||
H-field at the start of the energy delta.
|
||
</dd>
|
||
<dt><strong><code>e2</code></strong></dt>
|
||
<dd>
|
||
E-field at the end of the energy delta (one half-timestep after
|
||
<code>h1</code>).
|
||
</dd>
|
||
<dt><strong><code>h3</code></strong></dt>
|
||
<dd>
|
||
H-field one half-timestep after the end of the energy delta.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability distribution.
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Change in energy from the time of <code>h1</code> to the
|
||
time of <code>e2</code>.</p>
|
||
<h3 id="meanas.fdtd.energy.delta_energy_j">Function
|
||
<code>delta_energy_j</code></h3>
|
||
<blockquote>
|
||
<p><code>def delta_energy_j(j0: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e1: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate</p>
|
||
<p>Note that each value of <eq env="math">J</eq> contributes to the
|
||
energy twice (i.e. once per field update) despite only causing the value
|
||
of <eq env="math">E</eq> to change once (same for <eq env="math">M</eq>
|
||
and <eq env="math">H</eq>).</p>
|
||
<h3 id="meanas.fdtd.energy.dxmul">Function <code>dxmul</code></h3>
|
||
<blockquote>
|
||
<p><code>def dxmul(ee: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], hh: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | float | None = None, mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | float | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdtd.energy.energy_estep">Function
|
||
<code>energy_estep</code></h3>
|
||
<blockquote>
|
||
<p><code>def energy_estep(h0: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e1: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h2: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate energy <code>U</code> at the time of the provided E-field
|
||
<code>e1</code>.</p>
|
||
<p>TODO: Figure out what this means spatially.</p>
|
||
<p>Args —–= <strong><code>h0</code></strong> : H-field one half-timestep
|
||
before the energy.</p>
|
||
<dl>
|
||
<dt><strong><code>e1</code></strong></dt>
|
||
<dd>
|
||
E-field (at the same timestep as the energy).
|
||
</dd>
|
||
<dt><strong><code>h2</code></strong></dt>
|
||
<dd>
|
||
H-field one half-timestep after the energy.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability distribution.
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Energy, at the time of the E-field <code>e1</code>.</p>
|
||
<h3 id="meanas.fdtd.energy.energy_hstep">Function
|
||
<code>energy_hstep</code></h3>
|
||
<blockquote>
|
||
<p><code>def energy_hstep(e0: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h1: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], e2: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, mu: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate energy <code>U</code> at the time of the provided H-field
|
||
<code>h1</code>.</p>
|
||
<p>TODO: Figure out what this means spatially.</p>
|
||
<p>Args —–= <strong><code>e0</code></strong> : E-field one half-timestep
|
||
before the energy.</p>
|
||
<dl>
|
||
<dt><strong><code>h1</code></strong></dt>
|
||
<dd>
|
||
H-field (at the same timestep as the energy).
|
||
</dd>
|
||
<dt><strong><code>e2</code></strong></dt>
|
||
<dd>
|
||
E-field one half-timestep after the energy.
|
||
</dd>
|
||
<dt><strong><code>epsilon</code></strong></dt>
|
||
<dd>
|
||
Dielectric constant distribution.
|
||
</dd>
|
||
<dt><strong><code>mu</code></strong></dt>
|
||
<dd>
|
||
Magnetic permeability distribution.
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= Energy, at the time of the H-field <code>h1</code>.</p>
|
||
<h3 id="meanas.fdtd.energy.poynting">Function <code>poynting</code></h3>
|
||
<blockquote>
|
||
<p><code>def poynting(e: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], h: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate the poynting vector <code>S</code> (<eq
|
||
env="math">S</eq>).</p>
|
||
<p>This is the energy transfer rate (amount of energy <code>U</code> per
|
||
<code>dt</code> transferred between adjacent cells) in each direction
|
||
that happens during the half-step bounded by the two provided
|
||
fields.</p>
|
||
<p>The returned vector field <code>S</code> is the energy flow across
|
||
+x, +y, and +z boundaries of the corresponding <code>U</code> cell. For
|
||
example,</p>
|
||
<pre><code> mx = numpy.roll(mask, -1, axis=0)
|
||
my = numpy.roll(mask, -1, axis=1)
|
||
mz = numpy.roll(mask, -1, axis=2)
|
||
|
||
u_hstep = fdtd.energy_hstep(e0=es[ii - 1], h1=hs[ii], e2=es[ii], **args)
|
||
u_estep = fdtd.energy_estep(h0=hs[ii], e1=es[ii], h2=hs[ii + 1], **args)
|
||
delta_j_B = fdtd.delta_energy_j(j0=js[ii], e1=es[ii], dxes=dxes)
|
||
du_half_h2e = u_estep - u_hstep - delta_j_B
|
||
|
||
s_h2e = -fdtd.poynting(e=es[ii], h=hs[ii], dxes=dxes) * dt
|
||
planes = [s_h2e[0, mask].sum(), -s_h2e[0, mx].sum(),
|
||
s_h2e[1, mask].sum(), -s_h2e[1, my].sum(),
|
||
s_h2e[2, mask].sum(), -s_h2e[2, mz].sum()]
|
||
|
||
assert_close(sum(planes), du_half_h2e[mask])</code></pre>
|
||
<p>(see <code>meanas.tests.test_fdtd.test_poynting_planes</code>)</p>
|
||
<p>The full relationship is <eq env="displaymath">
|
||
\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}
|
||
</eq></p>
|
||
<p>These equalities are exact and should practically hold to within
|
||
numerical precision. No time- or spatial-averaging is necessary. (See
|
||
<code><a href="#meanas.fdtd">meanas.fdtd</a></code> docs for
|
||
derivation.)</p>
|
||
<p>Args —–= <strong><code>e</code></strong> : E-field</p>
|
||
<dl>
|
||
<dt><strong><code>h</code></strong></dt>
|
||
<dd>
|
||
H-field (one half-timestep before or after <code>e</code>)
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>s</code> : Vector 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.</p>
|
||
<h3 id="meanas.fdtd.energy.poynting_divergence">Function
|
||
<code>poynting_divergence</code></h3>
|
||
<blockquote>
|
||
<p><code>def poynting_divergence(s: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, *, e: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, h: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | None = None, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]] | None = None) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]</code></p>
|
||
</blockquote>
|
||
<p>Calculate the divergence of the poynting vector.</p>
|
||
<p>This is the net energy flow for each cell, i.e. the change in energy
|
||
<code>U</code> per <code>dt</code> caused by transfer of energy to
|
||
nearby cells (rather than absorption/emission by currents <code>J</code>
|
||
or <code>M</code>).</p>
|
||
<p>See <code><a
|
||
href="#meanas.fdtd.energy.poynting">poynting()</a></code> and <code><a
|
||
href="#meanas.fdtd">meanas.fdtd</a></code> for more details.</p>
|
||
<p>Args —–= <strong><code>s</code></strong> : Poynting vector, as
|
||
calculated with <code><a
|
||
href="#meanas.fdtd.energy.poynting">poynting()</a></code>. Optional;
|
||
caller can provide <code>e</code> and <code>h</code> instead.</p>
|
||
<dl>
|
||
<dt><strong><code>e</code></strong></dt>
|
||
<dd>
|
||
E-field (optional; need either <code>s</code> or both <code>e</code> and
|
||
<code>h</code>)
|
||
</dd>
|
||
<dt><strong><code>h</code></strong></dt>
|
||
<dd>
|
||
H-field (optional; need either <code>s</code> or both <code>e</code> and
|
||
<code>h</code>)
|
||
</dd>
|
||
<dt><strong><code>dxes</code></strong></dt>
|
||
<dd>
|
||
Grid description; see <code><a
|
||
href="#meanas.fdmath">meanas.fdmath</a></code>.
|
||
</dd>
|
||
</dl>
|
||
<p>Returns —–= <code>ds</code> : Divergence of the poynting vector.
|
||
Entries indicate the net energy flow out of the corresponding energy
|
||
cell.</p>
|
||
<hr />
|
||
<h1 id="meanas.fdtd.pml">Module <code>meanas.fdtd.pml</code></h1>
|
||
<p>PML implementations</p>
|
||
<p>#TODO discussion of PMLs #TODO cpml documentation</p>
|
||
<h2 id="functions-16">Functions</h2>
|
||
<h3 id="meanas.fdtd.pml.cpml_params">Function
|
||
<code>cpml_params</code></h3>
|
||
<blockquote>
|
||
<p><code>def cpml_params(axis: int, polarity: int, dt: float, thickness: int = 8, ln_R_per_layer: float = -1.6, epsilon_eff: float = 1, mu_eff: float = 1, m: float = 3.5, ma: float = 1, cfs_alpha: float = 0) -> dict[str, typing.Any]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.fdtd.pml.updates_with_cpml">Function
|
||
<code>updates_with_cpml</code></h3>
|
||
<blockquote>
|
||
<p><code>def updates_with_cpml(cpml_params: collections.abc.Sequence[collections.abc.Sequence[dict[str, typing.Any] | None]], dt: float, dxes: collections.abc.Sequence[collections.abc.Sequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], *, dtype: Union[numpy.dtype[Any], ForwardRef(None), type[Any], numpy._typing._dtype_like._SupportsDType[numpy.dtype[Any]], str, tuple[Any, int], tuple[Any, Union[SupportsIndex, collections.abc.Sequence[SupportsIndex]]], list[Any], numpy._typing._dtype_like._DTypeDict, tuple[Any, Any]] = numpy.float32) -> tuple[collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], None], collections.abc.Callable[[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]], numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]]], None]]</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<h1 id="meanas.test">Module <code>meanas.test</code></h1>
|
||
<p>Tests (run with
|
||
<code>python3 -m pytest -rxPXs | tee results.txt</code>)</p>
|
||
<h2 id="sub-modules-4">Sub-modules</h2>
|
||
<ul>
|
||
<li><a href="#meanas.test.conftest">meanas.test.conftest</a></li>
|
||
<li><a href="#meanas.test.test_fdfd">meanas.test.test_fdfd</a></li>
|
||
<li><a
|
||
href="#meanas.test.test_fdfd_pml">meanas.test.test_fdfd_pml</a></li>
|
||
<li><a href="#meanas.test.test_fdtd">meanas.test.test_fdtd</a></li>
|
||
<li><a href="#meanas.test.utils">meanas.test.utils</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h1 id="meanas.test.conftest">Module
|
||
<code>meanas.test.conftest</code></h1>
|
||
<p>Test fixtures</p>
|
||
<h2 id="functions-17">Functions</h2>
|
||
<h3 id="meanas.test.conftest.dx">Function <code>dx</code></h3>
|
||
<blockquote>
|
||
<p><code>def dx(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.dxes">Function <code>dxes</code></h3>
|
||
<blockquote>
|
||
<p><code>def dxes(request: Any, shape: tuple[int, ...], dx: float) -> list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.epsilon">Function <code>epsilon</code></h3>
|
||
<blockquote>
|
||
<p><code>def epsilon(request: Any, shape: tuple[int, ...], epsilon_bg: float, epsilon_fg: float) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.epsilon_bg">Function
|
||
<code>epsilon_bg</code></h3>
|
||
<blockquote>
|
||
<p><code>def epsilon_bg(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.epsilon_fg">Function
|
||
<code>epsilon_fg</code></h3>
|
||
<blockquote>
|
||
<p><code>def epsilon_fg(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.j_mag">Function <code>j_mag</code></h3>
|
||
<blockquote>
|
||
<p><code>def j_mag(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.conftest.shape">Function <code>shape</code></h3>
|
||
<blockquote>
|
||
<p><code>def shape(request: Any) -> tuple[int, ...]</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<h1 id="meanas.test.test_fdfd">Module
|
||
<code>meanas.test.test_fdfd</code></h1>
|
||
<h2 id="functions-18">Functions</h2>
|
||
<h3 id="meanas.test.test_fdfd.j_distribution">Function
|
||
<code>j_distribution</code></h3>
|
||
<blockquote>
|
||
<p><code>def j_distribution(request: Any, shape: tuple[int, ...], j_mag: float) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd.omega">Function <code>omega</code></h3>
|
||
<blockquote>
|
||
<p><code>def omega(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd.pec">Function <code>pec</code></h3>
|
||
<blockquote>
|
||
<p><code>def pec(request: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd.pmc">Function <code>pmc</code></h3>
|
||
<blockquote>
|
||
<p><code>def pmc(request: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd.sim">Function <code>sim</code></h3>
|
||
<blockquote>
|
||
<p><code>def sim(request: Any, shape: tuple[int, ...], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], dxes: list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]], j_distribution: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], omega: float, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None) -> meanas.test.test_fdfd.FDResult</code></p>
|
||
</blockquote>
|
||
<p>Build simulation from parts</p>
|
||
<h3 id="meanas.test.test_fdfd.test_poynting_planes">Function
|
||
<code>test_poynting_planes</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_poynting_planes(sim: FDResult) -> None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd.test_residual">Function
|
||
<code>test_residual</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_residual(sim: FDResult) -> None</code></p>
|
||
</blockquote>
|
||
<h2 id="classes">Classes</h2>
|
||
<h3 id="meanas.test.test_fdfd.FDResult">Class <code>FDResult</code></h3>
|
||
<p><a
|
||
href="https://mpxd.net/code/jan/meanas/src/commit/651e255704ecd14e72a49f0a5662cc304accfd9f/meanas/test/test_fdfd.py#L102-L111">[view
|
||
code]</a></p>
|
||
<blockquote>
|
||
<p><code>class FDResult(shape: tuple[int, ...], dxes: list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], omega: complex, j: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], e: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None)</code></p>
|
||
</blockquote>
|
||
<p>FDResult(shape: tuple[int, …], dxes:
|
||
list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]],
|
||
epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], omega:
|
||
complex, j: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], e:
|
||
numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], pmc:
|
||
numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None, pec:
|
||
numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None)</p>
|
||
<h4 id="class-variables">Class variables</h4>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.dxes">Variable
|
||
<code>dxes</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.e">Variable <code>e</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.epsilon">Variable
|
||
<code>epsilon</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.j">Variable <code>j</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.omega">Variable
|
||
<code>omega</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.pec">Variable
|
||
<code>pec</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.pmc">Variable
|
||
<code>pmc</code></h5>
|
||
<h5 id="meanas.test.test_fdfd.FDResult.shape">Variable
|
||
<code>shape</code></h5>
|
||
<hr />
|
||
<h1 id="meanas.test.test_fdfd_pml">Module
|
||
<code>meanas.test.test_fdfd_pml</code></h1>
|
||
<h2 id="functions-19">Functions</h2>
|
||
<h3 id="meanas.test.test_fdfd_pml.dxes">Function <code>dxes</code></h3>
|
||
<blockquote>
|
||
<p><code>def dxes(request: Any, shape: tuple[int, ...], dx: float, omega: float, epsilon_fg: float) -> list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.epsilon">Function
|
||
<code>epsilon</code></h3>
|
||
<blockquote>
|
||
<p><code>def epsilon(request: Any, shape: tuple[int, ...], epsilon_bg: float, epsilon_fg: float) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.j_distribution">Function
|
||
<code>j_distribution</code></h3>
|
||
<blockquote>
|
||
<p><code>def j_distribution(request: Any, shape: tuple[int, ...], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], dxes: collections.abc.MutableSequence[collections.abc.MutableSequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], omega: float, src_polarity: int) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.omega">Function
|
||
<code>omega</code></h3>
|
||
<blockquote>
|
||
<p><code>def omega(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.pec">Function <code>pec</code></h3>
|
||
<blockquote>
|
||
<p><code>def pec(request: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.pmc">Function <code>pmc</code></h3>
|
||
<blockquote>
|
||
<p><code>def pmc(request: Any) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.shape">Function
|
||
<code>shape</code></h3>
|
||
<blockquote>
|
||
<p><code>def shape(request: Any) -> tuple[int, int, int]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.sim">Function <code>sim</code></h3>
|
||
<blockquote>
|
||
<p><code>def sim(request: Any, shape: tuple[int, ...], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], dxes: collections.abc.MutableSequence[collections.abc.MutableSequence[numpy.ndarray[typing.Any, numpy.dtype[numpy.floating | numpy.complexfloating]]]], j_distribution: numpy.ndarray[typing.Any, numpy.dtype[numpy.complex128]], omega: float, pec: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None, pmc: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] | None) -> meanas.test.test_fdfd.FDResult</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.src_polarity">Function
|
||
<code>src_polarity</code></h3>
|
||
<blockquote>
|
||
<p><code>def src_polarity(request: Any) -> int</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdfd_pml.test_pml">Function
|
||
<code>test_pml</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_pml(sim: meanas.test.test_fdfd.FDResult, src_polarity: int) -> None</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<h1 id="meanas.test.test_fdtd">Module
|
||
<code>meanas.test.test_fdtd</code></h1>
|
||
<h2 id="functions-20">Functions</h2>
|
||
<h3 id="meanas.test.test_fdtd.dt">Function <code>dt</code></h3>
|
||
<blockquote>
|
||
<p><code>def dt(request: Any) -> float</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.j_distribution">Function
|
||
<code>j_distribution</code></h3>
|
||
<blockquote>
|
||
<p><code>def j_distribution(request: Any, shape: tuple[int, ...], j_mag: float) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.j_steps">Function
|
||
<code>j_steps</code></h3>
|
||
<blockquote>
|
||
<p><code>def j_steps(request: Any) -> tuple[int, ...]</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.sim">Function <code>sim</code></h3>
|
||
<blockquote>
|
||
<p><code>def sim(request: Any, shape: tuple[int, ...], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], dxes: list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]], dt: float, j_distribution: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], j_steps: tuple[int, ...]) -> meanas.test.test_fdtd.TDResult</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.test_energy_conservation">Function
|
||
<code>test_energy_conservation</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_energy_conservation(sim: TDResult) -> None</code></p>
|
||
</blockquote>
|
||
<p>Assumes fields start at 0 before J0 is added</p>
|
||
<h3 id="meanas.test.test_fdtd.test_initial_energy">Function
|
||
<code>test_initial_energy</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_initial_energy(sim: TDResult) -> None</code></p>
|
||
</blockquote>
|
||
<p>Assumes fields start at 0 before J0 is added</p>
|
||
<h3 id="meanas.test.test_fdtd.test_initial_fields">Function
|
||
<code>test_initial_fields</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_initial_fields(sim: TDResult) -> None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.test_poynting_divergence">Function
|
||
<code>test_poynting_divergence</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_poynting_divergence(sim: TDResult) -> None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.test_fdtd.test_poynting_planes">Function
|
||
<code>test_poynting_planes</code></h3>
|
||
<blockquote>
|
||
<p><code>def test_poynting_planes(sim: TDResult) -> None</code></p>
|
||
</blockquote>
|
||
<h2 id="classes-1">Classes</h2>
|
||
<h3 id="meanas.test.test_fdtd.TDResult">Class <code>TDResult</code></h3>
|
||
<p><a
|
||
href="https://mpxd.net/code/jan/meanas/src/commit/651e255704ecd14e72a49f0a5662cc304accfd9f/meanas/test/test_fdtd.py#L158-L168">[view
|
||
code]</a></p>
|
||
<blockquote>
|
||
<p><code>class TDResult(shape: tuple[int, ...], dt: float, dxes: list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]], epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], j_distribution: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], j_steps: tuple[int, ...], es: list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] = <factory>, hs: list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] = <factory>, js: list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] = <factory>)</code></p>
|
||
</blockquote>
|
||
<p>TDResult(shape: tuple[int, …], dt: float, dxes:
|
||
list[list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]],
|
||
epsilon: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]],
|
||
j_distribution: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]],
|
||
j_steps: tuple[int, …], es: list[numpy.ndarray[typing.Any,
|
||
numpy.dtype[numpy.float64]]] = <factory>, hs:
|
||
list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] = <factory>,
|
||
js: list[numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]] =
|
||
<factory>)</p>
|
||
<h4 id="class-variables-1">Class variables</h4>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.dt">Variable <code>dt</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.dxes">Variable
|
||
<code>dxes</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.epsilon">Variable
|
||
<code>epsilon</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.es">Variable <code>es</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.hs">Variable <code>hs</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.j_distribution">Variable
|
||
<code>j_distribution</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.j_steps">Variable
|
||
<code>j_steps</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.js">Variable <code>js</code></h5>
|
||
<h5 id="meanas.test.test_fdtd.TDResult.shape">Variable
|
||
<code>shape</code></h5>
|
||
<hr />
|
||
<h1 id="meanas.test.utils">Module <code>meanas.test.utils</code></h1>
|
||
<h2 id="functions-21">Functions</h2>
|
||
<h3 id="meanas.test.utils.assert_close">Function
|
||
<code>assert_close</code></h3>
|
||
<blockquote>
|
||
<p><code>def assert_close(x: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]], y: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]], *args, **kwargs) -> None</code></p>
|
||
</blockquote>
|
||
<h3 id="meanas.test.utils.assert_fields_close">Function
|
||
<code>assert_fields_close</code></h3>
|
||
<blockquote>
|
||
<p><code>def assert_fields_close(x: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]], y: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]], *args, **kwargs) -> None</code></p>
|
||
</blockquote>
|
||
<hr />
|
||
<p>Generated by <em>pdoc</em> 0.11.1 (<a href="https://pdoc3.github.io"
|
||
class="uri">https://pdoc3.github.io</a>).</p>
|
||
</body>
|
||
</html>
|