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>
 |