{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"meanas","text":"
meanas is a Python package for finite-difference electromagnetic simulation. It combines:
meanas.fdfd for frequency-domain operators, sources, waveguide modes, and SCPMLmeanas.fdtd for Yee-grid timestepping, CPML, energy/flux accounting, and phasor extractionmeanas.fdmath for the shared discrete operators and derivations underneath both solversThis documentation is built directly from the package docstrings. The API pages are the source of truth for the mathematical derivations and calling conventions.
"},{"location":"#recommended-starting-points","title":"Recommended starting points","text":"The docs build generates two HTML views from the same source:
site/print_page/If htmlark is installed, ./make_docs.sh also writes a fully inlined site/standalone.html.
The package is documented directly from its docstrings. The most useful entry points are:
The waveguide and FDTD pages are the best places to start if you want the mathematical derivations rather than just the callable reference.
"},{"location":"api/eigensolvers/","title":"eigensolvers","text":""},{"location":"api/eigensolvers/#meanas.eigensolvers","title":"meanas.eigensolvers","text":"Solvers for eigenvalue / eigenvector problems
"},{"location":"api/eigensolvers/#meanas.eigensolvers.power_iteration","title":"power_iteration","text":"power_iteration(\n operator: spmatrix,\n guess_vector: NDArray[complex128] | None = None,\n iterations: int = 20,\n) -> tuple[complex, NDArray[numpy.complex128]]\n Use power iteration to estimate the dominant eigenvector of a matrix.
Parameters:
Name Type Description Defaultoperator spmatrix Matrix to analyze.
requiredguess_vector NDArray[complex128] | None Starting point for the eigenvector. Default is a randomly chosen vector.
None iterations int Number of iterations to perform. Default 20.
20 Returns:
Type Descriptiontuple[complex, NDArray[complex128]] (Largest-magnitude eigenvalue, Corresponding eigenvector estimate)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.rayleigh_quotient_iteration","title":"rayleigh_quotient_iteration","text":"rayleigh_quotient_iteration(\n operator: spmatrix | LinearOperator,\n guess_vector: NDArray[complex128],\n iterations: int = 40,\n tolerance: float = 1e-13,\n solver: Callable[..., NDArray[complex128]]\n | None = None,\n) -> tuple[complex, NDArray[numpy.complex128]]\n Use Rayleigh quotient iteration to refine an eigenvector guess.
Parameters:
Name Type Description Defaultoperator spmatrix | LinearOperator Matrix to analyze.
requiredguess_vector NDArray[complex128] Eigenvector to refine.
requirediterations int Maximum number of iterations to perform. Default 40.
40 tolerance float Stop iteration if (A - I*eigenvalue) @ v < num_vectors * tolerance, Default 1e-13.
1e-13 solver Callable[..., NDArray[complex128]] | None Solver function of the form x = solver(A, b). By default, use scipy.sparse.spsolve for sparse matrices and scipy.sparse.bicgstab for general LinearOperator instances.
None Returns:
Type Descriptiontuple[complex, NDArray[complex128]] (eigenvalues, eigenvectors)
"},{"location":"api/eigensolvers/#meanas.eigensolvers.signed_eigensolve","title":"signed_eigensolve","text":"signed_eigensolve(\n operator: spmatrix | LinearOperator,\n how_many: int,\n negative: bool = False,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\n Find the largest-magnitude positive-only (or negative-only) eigenvalues and eigenvectors of the provided matrix.
Parameters:
Name Type Description Defaultoperator spmatrix | LinearOperator Matrix to analyze.
requiredhow_many int How many eigenvalues to find.
requirednegative bool Whether to find negative-only eigenvalues. Default False (positive only).
False Returns:
Type DescriptionNDArray[complex128] (sorted list of eigenvalues, 2D ndarray of corresponding eigenvectors)
NDArray[complex128] eigenvectors[:, k] corresponds to the k-th eigenvalue
Tools for finite difference frequency-domain (FDFD) simulations and calculations.
These mostly involve picking a single frequency, then setting up and solving a matrix equation (Ax=b) or eigenvalue problem.
Submodules:
operators, functional: General FDFD problem setup.solvers: Solver interface and reference implementation.scpml: Stretched-coordinate perfectly matched layer (SCPML) boundary conditions.waveguide_2d: Operators and mode-solver for waveguides with constant cross-section.waveguide_3d: Functions for transforming waveguide_2d results into 3D, including mode-source and overlap-window construction.farfield, bloch, eme: specialized helper modules for near/far transforms, Bloch-periodic problems, and eigenmode expansion.================================================================
From the \"Frequency domain\" section of meanas.fdmath, we have
resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\end{aligned} \\]Maxwell's equations are then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2} \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2} \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\tilde{\\nabla} \\cdot \\hat{B}_{\\vec{r} + \\frac{1}{2}} &= 0 \\\\ \\hat{\\nabla} \\cdot \\tilde{D}_{\\vec{r}} &= \\rho_{\\vec{r}} \\end{aligned} \\]With \\(\\Delta_t \\to 0\\), this simplifies to
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &\\to \\tilde{E}_{\\vec{r}} \\\\ \\tilde{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{H}_{\\vec{r} + \\frac{1}{2}} \\\\ \\tilde{J}_{l, \\vec{r}} &\\to \\tilde{J}_{\\vec{r}} \\\\ \\tilde{M}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} &\\to \\tilde{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\Omega &\\to \\omega \\\\ \\tilde{\\partial}_t &\\to -\\imath \\omega \\\\ \\hat{\\partial}_t &\\to -\\imath \\omega \\\\ \\end{aligned} \\]and then
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\imath \\omega \\hat{B}_{\\vec{r} + \\frac{1}{2}} - \\hat{M}_{\\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times \\hat{H}_{\\vec{r} + \\frac{1}{2}} &= -\\imath \\omega \\tilde{D}_{\\vec{r}} + \\tilde{J}_{\\vec{r}} \\\\ \\end{aligned} \\] \\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\omega \\tilde{J}_{\\vec{r}} \\\\ \\]"},{"location":"api/fdfd/#core-operator-layers","title":"Core operator layers","text":""},{"location":"api/fdfd/#meanas.fdfd.functional","title":"meanas.fdfd.functional","text":"Functional versions of many FDFD operators. These can be useful for performing FDFD calculations without needing to construct large matrices in memory.
The functions generated here expect cfdfield_t 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)
e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\n Wave operator for use with E-field. See operators.e_full for details.
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon fdfield Dielectric constant
requiredmu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptioncfdfield_updater_t Function f implementing the wave operator
cfdfield_updater_t f(E) -> -i * omega * J
eh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> Callable[\n [cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]\n]\n Wave operator for full (both E and H) field representation. See operators.eh_full.
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon fdfield Dielectric constant
requiredmu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type DescriptionCallable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] Function f implementing the wave operator
Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]] f(E, H) -> (J, -M)
e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\n Utility operator for converting the E field into the H field. For use with e_full -- assumes that there is no magnetic current M.
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
mu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptioncfdfield_updater_t Function f for converting E to H,
cfdfield_updater_t f(E) -> H
m2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\n Utility operator for converting magnetic current M distribution into equivalent electric current distribution J. For use with e.g. e_full.
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
mu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptioncfdfield_updater_t Function f for converting M to J,
cfdfield_updater_t f(M) -> J
e_tfsf_source(\n TF_region: fdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_updater_t\n Operator that turns an E-field distribution into a total-field/scattered-field (TFSF) source.
If A is the full wave operator from e_full(...) and Q is the diagonal mask selecting the total-field region, then the TFSF source is the commutator
This vanishes in the interior of the total-field and scattered-field regions and is supported only at their shared boundary, where the mask discontinuity makes A and Q fail to commute. The returned current is therefore the distributed source needed to inject the desired total field without also forcing the scattered-field region.
Parameters:
Name Type Description DefaultTF_region fdfield 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. epsilon[0].shape.
omega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon fdfield Dielectric constant distribution
requiredmu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptioncfdfield_updater_t Function f which takes an E field and returns a current distribution,
cfdfield_updater_t f(E) -> J
poynting_e_cross_h(\n dxes: dx_lists_t,\n) -> Callable[[cfdfield, cfdfield], cfdfield_t]\n Generates a function that takes the single-frequency E and H fields and calculates the cross product E x H = \\(E \\times H\\) as required for the Poynting vector, \\(S = E \\times H\\).
On the Yee grid, the electric and magnetic components are not stored at the same locations. This helper therefore applies the same one-cell electric-field shifts used by the sparse operators.poynting_e_cross(...) construction so that the discrete cross product matches the face-centered energy flux used in meanas.fdtd.energy.poynting(...).
This function also shifts the input E field by one cell as required for computing the Poynting cross product (see meanas.fdfd module docs).
If E and H are peak amplitudes as assumed elsewhere in this code, the time-average of the poynting vector is <S> = Re(S)/2 = Re(E x H*) / 2. The factor of 1/2 can be omitted if root-mean-square quantities are used instead.
Parameters:
Name Type Description Defaultdxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
Returns:
Type DescriptionCallable[[cfdfield, cfdfield], cfdfield_t] Function f that returns the staggered-grid cross product E \\times H.
Callable[[cfdfield, cfdfield], cfdfield_t] For time-average power, call it as f(E, H.conj()) and take Re(...) / 2.
Sparse matrix operators for use with electromagnetic wave equations.
These functions return sparse-matrix (scipy.sparse.sparray) representations of a variety of operators, intended for use with E and H fields vectorized using the meanas.fdmath.vectorization.vec() and meanas.fdmath.vectorization.unvec() functions.
E- and H-field values are defined on a Yee cell; epsilon values should be calculated for cells centered at each E component (mu at each H component).
Many of these functions require a dxes parameter, of type dx_lists_t; see the meanas.fdmath.types submodule for details.
The following operators are included:
e_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield | vcfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\n Wave operator $$ \\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon $$
del x (1/mu * del x) - omega**2 * epsilon\n for use with the E-field, with wave equation $$ (\\nabla \\times (\\frac{1}{\\mu} \\nabla \\times) - \\Omega^2 \\epsilon) E = -\\imath \\omega J $$
(del x (1/mu * del x) - omega**2 * epsilon) E = -i * omega * J\n To make this matrix symmetric, use the preconditioners from e_full_preconditioners().
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdfield | vcfdfield Vectorized dielectric constant
requiredmu vfdfield | None Vectorized magnetic permeability (default 1 everywhere).
None pec vfdfield | None Vectorized mask specifying PEC cells. Any cells where pec != 0 are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e. pec.size == epsilon.size)
None pmc vfdfield | None Vectorized mask specifying PMC cells. Any cells where pmc != 0 are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e. pmc.size == epsilon.size)
None Returns:
Type Descriptionsparray Sparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_full_preconditioners","title":"e_full_preconditioners","text":"e_full_preconditioners(\n dxes: dx_lists_t,\n) -> tuple[sparse.sparray, sparse.sparray]\n Left and right preconditioners (Pl, Pr) for symmetrizing the e_full wave operator.
The preconditioned matrix A_symm = (Pl @ A @ Pr) is complex-symmetric (non-Hermitian unless there is no loss or PMLs).
The preconditioner matrices are diagonal and complex, with Pr = 1 / Pl
Parameters:
Name Type Description Defaultdxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
Returns:
Type Descriptiontuple[sparray, sparray] Preconditioner matrices (Pl, Pr).
h_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\n Wave operator $$ \\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu $$
del x (1/epsilon * del x) - omega**2 * mu\n for use with the H-field, with wave equation $$ (\\nabla \\times (\\frac{1}{\\epsilon} \\nabla \\times) - \\omega^2 \\mu) E = \\imath \\omega M $$
(del x (1/epsilon * del x) - omega**2 * mu) E = i * omega * M\n Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdfield Vectorized dielectric constant
requiredmu vfdfield | None Vectorized magnetic permeability (default 1 everywhere)
None pec vfdfield | None Vectorized mask specifying PEC cells. Any cells where pec != 0 are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e. pec.size == epsilon.size)
None pmc vfdfield | None Vectorized mask specifying PMC cells. Any cells where pmc != 0 are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e. pmc.size == epsilon.size)
None Returns:
Type Descriptionsparray Sparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.eh_full","title":"eh_full","text":"eh_full(\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\n Wave operator for [E, H] field representation. This operator implements Maxwell's equations without cancelling out either E or H. The operator is $$ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} $$
[[-i * omega * epsilon, del x ],\n [del x, i * omega * mu]]\n for use with a field vector of the form cat(vec(E), vec(H)): $$ \\begin{bmatrix} -\\imath \\omega \\epsilon & \\nabla \\times \\ \\nabla \\times & \\imath \\omega \\mu \\end{bmatrix} \\begin{bmatrix} E \\ H \\end{bmatrix} = \\begin{bmatrix} J \\ -M \\end{bmatrix} $$
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdfield Vectorized dielectric constant
requiredmu vfdfield | None Vectorized magnetic permeability (default 1 everywhere)
None pec vfdfield | None Vectorized mask specifying PEC cells. Any cells where pec != 0 are interpreted as containing a perfect electrical conductor (PEC). The PEC is applied per-field-component (i.e. pec.size == epsilon.size)
None pmc vfdfield | None Vectorized mask specifying PMC cells. Any cells where pmc != 0 are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e. pmc.size == epsilon.size)
None Returns:
Type Descriptionsparray Sparse matrix containing the wave operator.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e2h","title":"e2h","text":"e2h(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n pmc: vfdfield | None = None,\n) -> sparse.sparray\n Utility operator for converting the E field into the H field. For use with e_full() -- assumes that there is no magnetic current M.
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
mu vfdfield | None Vectorized magnetic permeability (default 1 everywhere)
None pmc vfdfield | None Vectorized mask specifying PMC cells. Any cells where pmc != 0 are interpreted as containing a perfect magnetic conductor (PMC). The PMC is applied per-field-component (i.e. pmc.size == epsilon.size)
None Returns:
Type Descriptionsparray Sparse matrix for converting E to H.
"},{"location":"api/fdfd/#meanas.fdfd.operators.m2j","title":"m2j","text":"m2j(\n omega: complex,\n dxes: dx_lists_t,\n mu: vfdfield | None = None,\n) -> sparse.sparray\n Operator for converting a magnetic current M into an electric current J. For use with eg. e_full().
Parameters:
Name Type Description Defaultomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
mu vfdfield | None Vectorized magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix for converting M to J.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_e_cross","title":"poynting_e_cross","text":"poynting_e_cross(\n e: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\n Operator for computing the staggered-grid (E \\times) part of the Poynting vector.
On the Yee grid the E and H components live on different edges, so the electric field must be shifted by one cell in the appropriate direction before forming the discrete cross product. This sparse operator encodes that shifted cross product directly and is the matrix equivalent of functional.poynting_e_cross_h(...).
Parameters:
Name Type Description Defaulte vcfdfield Vectorized E-field for the ExH cross product
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
Returns:
Type Descriptionsparray Sparse matrix containing the (E \\times) part of the staggered Poynting
sparray cross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.poynting_h_cross","title":"poynting_h_cross","text":"poynting_h_cross(\n h: vcfdfield, dxes: dx_lists_t\n) -> sparse.sparray\n Operator for computing the staggered-grid (H \\times) part of the Poynting vector.
Together with poynting_e_cross(...), this provides the matrix form of the Yee-grid cross product used in the flux helpers. The two are related by the usual antisymmetry of the cross product,
once the same staggered field placement is used on both sides.
Parameters:
Name Type Description Defaulth vcfdfield Vectorized H-field for the HxE cross product
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
Returns:
Type Descriptionsparray Sparse matrix containing the (H \\times) part of the staggered Poynting
sparray cross product.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_tfsf_source","title":"e_tfsf_source","text":"e_tfsf_source(\n TF_region: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n) -> sparse.sparray\n Operator that turns a desired E-field distribution into a total-field/scattered-field (TFSF) source.
Let A be the full wave operator from e_full(...), and let Q = \\mathrm{diag}(TF_region) be the projector onto the total-field region. Then the TFSF current operator is the commutator
Inside regions where Q is locally constant, A and Q commute and the source vanishes. Only cells at the TF/SF boundary contribute nonzero current, which is exactly the desired distributed source for injecting the chosen field into the total-field region without directly forcing the scattered-field region.
Parameters:
Name Type Description DefaultTF_region vfdfield Mask, which is set to 1 inside the total-field region and 0 in the scattered-field region
requiredomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdfield Vectorized dielectric constant
requiredmu vfdfield | None Vectorized magnetic permeability (default 1 everywhere).
None Returns:
Type Descriptionsparray Sparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.operators.e_boundary_source","title":"e_boundary_source","text":"e_boundary_source(\n mask: vfdfield,\n omega: complex,\n dxes: dx_lists_t,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n periodic_mask_edges: bool = False,\n) -> sparse.sparray\n 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 e_tfsf_source() with an additional masking step.
Equivalently, this helper first constructs the TFSF commutator source for the full mask and then zeroes out all cells except the mask boundary. The boundary is defined as the set of cells whose mask value changes under a one-cell shift in any Cartesian direction. With periodic_mask_edges=False the shifts mirror at the domain boundary; with True they wrap periodically.
Parameters:
Name Type Description Defaultmask vfdfield 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.
requiredomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdfield Vectorized dielectric constant
requiredmu vfdfield | None Vectorized magnetic permeability (default 1 everywhere).
None Returns:
Type Descriptionsparray Sparse matrix that turns an E-field into a current (J) distribution.
"},{"location":"api/fdfd/#meanas.fdfd.solvers","title":"meanas.fdfd.solvers","text":"Solvers and solver interface for FDFD problems.
"},{"location":"api/fdfd/#meanas.fdfd.solvers.generic","title":"generic","text":"generic(\n omega: complex,\n dxes: dx_lists_t,\n J: vcfdfield,\n epsilon: vfdfield,\n mu: vfdfield | None = None,\n *,\n pec: vfdfield | None = None,\n pmc: vfdfield | None = None,\n adjoint: bool = False,\n matrix_solver: Callable[..., ArrayLike] = _scipy_qmr,\n matrix_solver_opts: dict[str, Any] | None = None,\n E_guess: vcfdfield | None = None,\n) -> vcfdfield_t\n Conjugate gradient FDFD solver using CSR sparse matrices.
All ndarray arguments should be 1D arrays, as returned by meanas.fdmath.vectorization.vec().
Parameters:
Name Type Description Defaultomega complex Complex frequency to solve at.
requireddxes dx_lists_t [[dx_e, dy_e, dz_e], [dx_h, dy_h, dz_h]] (complex cell sizes) as discussed in meanas.fdmath.types
J vcfdfield Electric current distribution (at E-field locations)
requiredepsilon vfdfield Dielectric constant distribution (at E-field locations)
requiredmu vfdfield | None Magnetic permeability distribution (at H-field locations)
None pec vfdfield | None Perfect electric conductor distribution (at E-field locations; non-zero value indicates PEC is present)
None pmc vfdfield | None Perfect magnetic conductor distribution (at H-field locations; non-zero value indicates PMC is present)
None adjoint bool If true, solves the adjoint problem.
False matrix_solver Callable[..., ArrayLike] Called as matrix_solver(A, b, **matrix_solver_opts) -> x, where A: scipy.sparse.csr_array; b: ArrayLike; x: ArrayLike; Default is a wrapped version of scipy.sparse.linalg.qmr() which doesn't return convergence info and logs the residual every 100 iterations.
_scipy_qmr matrix_solver_opts dict[str, Any] | None Passed as kwargs to matrix_solver(...)
None E_guess vcfdfield | None Guess at the solution E-field. matrix_solver must accept an x0 argument with the same purpose.
None Returns:
Type Descriptionvcfdfield_t E-field which solves the system.
"},{"location":"api/fdfd/#meanas.fdfd.scpml","title":"meanas.fdfd.scpml","text":"Functions for creating stretched coordinate perfectly matched layer (PML) absorbers.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.s_function_t","title":"s_function_tmodule-attribute","text":"s_function_t = Callable[\n [NDArray[float64]], NDArray[float64]\n]\n Typedef for s-functions, see prepare_s_function()
prepare_s_function(\n ln_R: float = -16, m: float = 4\n) -> s_function_t\n Create an s_function to pass to the SCPML functions. This is used when you would like to customize the PML parameters.
Parameters:
Name Type Description Defaultln_R float Natural logarithm of the desired reflectance
-16 m float Polynomial order for the PML (imaginary part increases as distance ** m)
4 Returns:
Type Descriptions_function_t An s_function, which takes an ndarray (distances) and returns an ndarray (complex part
s_function_t of the cell width; needs to be divided by sqrt(epilon_effective) * real(omega))
s_function_t before use.
"},{"location":"api/fdfd/#meanas.fdfd.scpml.uniform_grid_scpml","title":"uniform_grid_scpml","text":"uniform_grid_scpml(\n shape: Sequence[int],\n thicknesses: Sequence[int],\n omega: float,\n epsilon_effective: float = 1.0,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\n Create dx arrays for a uniform grid with a cell width of 1 and a pml.
If you want something more fine-grained, check out stretch_with_scpml(...).
Parameters:
Name Type Description Defaultshape Sequence[int] Shape of the grid, including the PMLs (which are 2*thicknesses thick)
requiredthicknesses Sequence[int] [th_x, th_y, th_z] Thickness of the PML in each direction. Both polarities are added. Each th_ of pml is applied twice, once on each edge of the grid along the given axis. th_* may be zero, in which case no pml is added.
omega float Angular frequency for the simulation
requiredepsilon_effective float Effective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0 s_function s_function_t | None created by prepare_s_function(...), allowing customization of pml parameters. Default uses prepare_s_function() with no parameters.
None Returns:
Type Descriptionlist[list[NDArray[float64]]] Complex cell widths (dx_lists_mut) as discussed in meanas.fdmath.types.
stretch_with_scpml(\n dxes: list[list[NDArray[float64]]],\n axis: int,\n polarity: int,\n omega: float,\n epsilon_effective: float = 1.0,\n thickness: int = 10,\n s_function: s_function_t | None = None,\n) -> list[list[NDArray[numpy.float64]]]\n Stretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis.
Parameters:
Name Type Description Defaultdxes list[list[NDArray[float64]]] Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
axis int axis to stretch (0=x, 1=y, 2=z)
requiredpolarity int direction to stretch (-1 for -ve, +1 for +ve)
requiredomega float Angular frequency for the simulation
requiredepsilon_effective float Effective epsilon of the PML. Match this to the material at the edge of your grid. Default 1.
1.0 thickness int number of cells to use for pml (default 10)
10 s_function s_function_t | None Created by prepare_s_function(...), allowing customization of pml parameters. Default uses prepare_s_function() with no parameters.
None Returns:
Type Descriptionlist[list[NDArray[float64]]] Complex cell widths (dx_lists_mut) as discussed in meanas.fdmath.types.
list[list[NDArray[float64]]] Multiple calls to this function may be necessary if multiple absorpbing boundaries are needed.
"},{"location":"api/fdfd/#meanas.fdfd.farfield","title":"meanas.fdfd.farfield","text":"Functions for performing near-to-farfield transformation (and the reverse).
"},{"location":"api/fdfd/#meanas.fdfd.farfield.near_to_farfield","title":"near_to_farfield","text":"near_to_farfield(\n E_near: cfdfield_t,\n H_near: cfdfield_t,\n dx: float,\n dy: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\n Compute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_near cfdfield_t 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).
requiredH_near cfdfield_t 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).
requireddx float Cell size along x-dimension, in units of wavelength.
requireddy float Cell size along y-dimension, in units of wavelength.
requiredpadded_size list[int] | int | None Shape of the output. A single integer n will be expanded to (n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.
None Returns:
Type Descriptiondict[str, Any] Dict with keys
dict[str, Any] E_far: Normalized E-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any] H_far: Normalized H-field farfield; multiply by (i k exp(-i k r) / (4 pi r)) to get the actual field value.dict[str, Any] kx, ky: Wavevector values corresponding to the x- and y- axes in E_far and H_far, normalized to wavelength (dimensionless).dict[str, Any] dkx, dky: step size for kx and ky, normalized to wavelength.dict[str, Any] theta: arctan2(ky, kx) corresponding to each (kx, ky). This is the angle in the x-y plane, counterclockwise from above, starting from +x.dict[str, Any] phi: arccos(kz / k) corresponding to each (kx, ky). This is the angle away from +z.far_to_nearfield(\n E_far: cfdfield_t,\n H_far: cfdfield_t,\n dkx: float,\n dky: float,\n padded_size: list[int] | int | None = None,\n) -> dict[str, Any]\n Compute the farfield, i.e. the distribution of the fields after propagation through several wavelengths of uniform medium.
The input fields should be complex phasors.
Parameters:
Name Type Description DefaultE_far cfdfield_t 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))
requiredH_far cfdfield_t 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))
requireddkx float kx discretization, in units of wavelength.
requireddky float ky discretization, in units of wavelength.
requiredpadded_size list[int] | int | None Shape of the output. A single integer n will be expanded to (n, n). Powers of 2 are most efficient for FFT computation. Default is the smallest power of 2 larger than the input, for each axis.
None Returns:
Type Descriptiondict[str, Any] Dict with keys
dict[str, Any] E: E-field nearfielddict[str, Any] H: H-field nearfielddict[str, Any] dx, dy: spatial discretization, normalized to wavelength (dimensionless)Basic discrete calculus for finite difference (fd) simulations.
"},{"location":"api/fdmath/#meanas.fdmath--fields-functions-and-operators","title":"Fields, Functions, and Operators","text":"Discrete fields are stored in one of two forms:
fdfield_t form is a multidimensional numpy.NDArrayU[m, n, p], where m, n, and p are discrete indices referring to positions on the x, y, and z axes respectively.E[:, m, n, p] = [Ex[m, n, p], Ey[m, n, p], Ez[m, n, p]].vfdfield_t form is simply a vectorzied (i.e. 1D) version of the fdfield_t, as obtained by meanas.fdmath.vectorization.vec (effectively just numpy.ravel)meanas.fdmath.functional. The generated functions act on fields in the fdfield_t form.scipy.sparse, created by meanas.fdmath.operators. These operators act on vectorized fields in the vfdfield_t form.The operations performed should be equivalent: functional.op(*args)(E) should be equivalent to unvec(operators.op(*args) @ vec(E), E.shape[1:]).
Generally speaking the field_t 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).
This documentation and approach is roughly based on W.C. Chew's excellent \"Electromagnetic Theory on a Lattice\" (doi:10.1063/1.355770), which covers a superset of this material with similar notation and more detail.
"},{"location":"api/fdmath/#meanas.fdmath--scalar-derivatives-and-cell-shifts","title":"Scalar derivatives and cell shifts","text":"Define the discrete forward derivative as $$ [\\tilde{\\partial}x f] - f_m) $$ where }{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m + 1\\(f\\) is a function defined at discrete locations on the x-axis (labeled using \\(m\\)). The value at \\(m\\) occupies a length \\(\\Delta_{x, m}\\) along the x-axis. Note that \\(m\\) is an index along the x-axis, not necessarily an x-coordinate, since each length \\(\\Delta_{x, m}, \\Delta_{x, m+1}, ...\\) is independently chosen.
If we treat f as a 1D array of values, with the i-th value f[i] taking up a length dx[i] along the x-axis, the forward derivative is
deriv_forward(f)[i] = (f[i + 1] - f[i]) / dx[i]\n Likewise, discrete reverse derivative is $$ [\\hat{\\partial}x f ]) $$ or}{2}} = \\frac{1}{\\Delta_{x, m}} (f_{m} - f_{m - 1
deriv_back(f)[i] = (f[i] - f[i - 1]) / dx[i]\n The derivatives' values are shifted by a half-cell relative to the original function, and will have different cell widths if all the dx[i] ( \\(\\Delta_{x, m}\\) ) are not identical:
[figure: derivatives and cell sizes]\n dx0 dx1 dx2 dx3 cell sizes for function\n ----- ----- ----------- -----\n ______________________________\n | | | |\n f0 | f1 | f2 | f3 | function\n _____|_____|___________|_____|\n | | | |\n | Df0 | Df1 | Df2 | Df3 forward derivative (periodic boundary)\n __|_____|________|________|___\n\n dx'3] dx'0 dx'1 dx'2 [dx'3 cell sizes for forward derivative\n -- ----- -------- -------- ---\n dx'0] dx'1 dx'2 dx'3 [dx'0 cell sizes for reverse derivative\n ______________________________\n | | | |\n | df1 | df2 | df3 | df0 reverse derivative (periodic boundary)\n __|_____|________|________|___\n\nPeriodic boundaries are used here and elsewhere unless otherwise noted.\n In the above figure, f0 = \\(f_0\\), f1 = \\(f_1\\) Df0 = \\([\\tilde{\\partial}f]_{0 + \\frac{1}{2}}\\) Df1 = \\([\\tilde{\\partial}f]_{1 + \\frac{1}{2}}\\) df0 = \\([\\hat{\\partial}f]_{0 - \\frac{1}{2}}\\) etc.
The fractional subscript \\(m + \\frac{1}{2}\\) is used to indicate values defined at shifted locations relative to the original \\(m\\), with corresponding lengths $$ \\Delta_{x, m + \\frac{1}{2}} = \\frac{1}{2} * (\\Delta_{x, m} + \\Delta_{x, m + 1}) $$
Just as \\(m\\) is not itself an x-coordinate, neither is \\(m + \\frac{1}{2}\\); carefully note the positions of the various cells in the above figure vs their labels. If the positions labeled with \\(m\\) are considered the \"base\" or \"original\" grid, the positions labeled with \\(m + \\frac{1}{2}\\) are said to lie on a \"dual\" or \"derived\" grid.
For the remainder of the Discrete calculus section, all figures will show constant-length cells in order to focus on the vector derivatives themselves. See the Grid description section below for additional information on this topic and generalization to three dimensions.
Expanding to three dimensions, we can define two gradients $$ [\\tilde{\\nabla} f]{m,n,p} = \\vec{x} [\\tilde{\\partial}_x f] + \\vec{y} [\\tilde{\\partial}}{2},n,py f] + \\vec{z} [\\tilde{\\partial}}{2},pz f] $$ $$ [\\hat{\\nabla} f]}{2}{m,n,p} = \\vec{x} [\\hat{\\partial}_x f] + \\vec{y} [\\hat{\\partial}}{2},n,py f] + \\vec{z} [\\hat{\\partial}}{2},pz f] $$}{2}
or
[code: gradients]\ngrad_forward(f)[i,j,k] = [Dx_forward(f)[i, j, k],\n Dy_forward(f)[i, j, k],\n Dz_forward(f)[i, j, k]]\n = [(f[i + 1, j, k] - f[i, j, k]) / dx[i],\n (f[i, j + 1, k] - f[i, j, k]) / dy[i],\n (f[i, j, k + 1] - f[i, j, k]) / dz[i]]\n\ngrad_back(f)[i,j,k] = [Dx_back(f)[i, j, k],\n Dy_back(f)[i, j, k],\n Dz_back(f)[i, j, k]]\n = [(f[i, j, k] - f[i - 1, j, k]) / dx[i],\n (f[i, j, k] - f[i, j - 1, k]) / dy[i],\n (f[i, j, k] - f[i, j, k - 1]) / dz[i]]\n The three derivatives in the gradient cause shifts in different directions, so the x/y/z components of the resulting \"vector\" are defined at different points: the x-component is shifted in the x-direction, y in y, and z in z.
We call the resulting object a \"fore-vector\" or \"back-vector\", depending on the direction of the shift. We write it as $$ \\tilde{g}{m,n,p} = \\vec{x} g^x + \\vec{y} g^y_{m,n + \\frac{1}{2},p} + \\vec{z} g^z_{m,n,p + \\frac{1}{2}} $$ $$ \\hat{g}}{2},n,p{m,n,p} = \\vec{x} g^x + \\vec{y} g^y_{m,n - \\frac{1}{2},p} + \\vec{z} g^z_{m,n,p - \\frac{1}{2}} $$}{2},n,p
[figure: gradient / fore-vector]\n (m, n+1, p+1) ______________ (m+1, n+1, p+1)\n /: /|\n / : / |\n / : / |\n (m, n, p+1)/_____________/ | The forward derivatives are defined\n | : | | at the Dx, Dy, Dz points,\n | :.........|...| but the forward-gradient fore-vector\n z y Dz / | / is the set of all three\n |/_x | Dy | / and is said to be \"located\" at (m,n,p)\n |/ |/\n (m, n, p)|_____Dx______| (m+1, n, p)\n"},{"location":"api/fdmath/#meanas.fdmath--divergences","title":"Divergences","text":"There are also two divergences,
$$ d_{n,m,p} = [\\tilde{\\nabla} \\cdot \\hat{g}]{n,m,p} = [\\tilde{\\partial}_x g^x] + [\\tilde{\\partial}y g^y] + [\\tilde{\\partial}z g^z] $$
$$ d_{n,m,p} = [\\hat{\\nabla} \\cdot \\tilde{g}]{n,m,p} = [\\hat{\\partial}_x g^x] + [\\hat{\\partial}y g^y] + [\\hat{\\partial}z g^z] $$
or
[code: divergences]\ndiv_forward(g)[i,j,k] = Dx_forward(gx)[i, j, k] +\n Dy_forward(gy)[i, j, k] +\n Dz_forward(gz)[i, j, k]\n = (gx[i + 1, j, k] - gx[i, j, k]) / dx[i] +\n (gy[i, j + 1, k] - gy[i, j, k]) / dy[i] +\n (gz[i, j, k + 1] - gz[i, j, k]) / dz[i]\n\ndiv_back(g)[i,j,k] = Dx_back(gx)[i, j, k] +\n Dy_back(gy)[i, j, k] +\n Dz_back(gz)[i, j, k]\n = (gx[i, j, k] - gx[i - 1, j, k]) / dx[i] +\n (gy[i, j, k] - gy[i, j - 1, k]) / dy[i] +\n (gz[i, j, k] - gz[i, j, k - 1]) / dz[i]\n where g = [gx, gy, gz] is a fore- or back-vector field.
Since we applied the forward divergence to the back-vector (and vice-versa), the resulting scalar value is defined at the back-vector's (fore-vector's) location \\((m,n,p)\\) and not at the locations of its components \\((m \\pm \\frac{1}{2},n,p)\\) etc.
[figure: divergence]\n ^^\n (m-1/2, n+1/2, p+1/2) _____||_______ (m+1/2, n+1/2, p+1/2)\n /: || ,, /|\n / : || // / | The divergence at (m, n, p) (the center\n / : // / | of this cube) of a fore-vector field\n (m-1/2, n-1/2, p+1/2)/_____________/ | is the sum of the outward-pointing\n | : | | fore-vector components, which are\n z y <==|== :.........|.====> located at the face centers.\n |/_x | / | /\n | / // | / Note that in a nonuniform grid, each\n |/ // || |/ dimension is normalized by the cell width.\n (m-1/2, n-1/2, p-1/2)|____//_______| (m+1/2, n-1/2, p-1/2)\n '' ||\n VV\n"},{"location":"api/fdmath/#meanas.fdmath--curls","title":"Curls","text":"The two curls are then
$$ \\begin{aligned} \\hat{h}{m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}} &= \\ [\\tilde{\\nabla} \\times \\tilde{g}] &= \\vec{x} (\\tilde{\\partial}}{2}, n + \\frac{1}{2}, p + \\frac{1}{2}y g^z}{2}} - \\tilde{\\partialz g^y) \\ &+ \\vec{y} (\\tilde{\\partial}}{2},pz g^x}{2},n,p} - \\tilde{\\partialx g^z) \\ &+ \\vec{z} (\\tilde{\\partial}}{2}x g^y}{2},p} - \\tilde{\\partialy g^z) \\end{aligned} $$}{2},n,p
and
$$ \\tilde{h}{m - \\frac{1}{2}, n - \\frac{1}{2}, p - \\frac{1}{2}} = [\\hat{\\nabla} \\times \\hat{g}] $$}{2}, n - \\frac{1}{2}, p - \\frac{1}{2}
where \\(\\hat{g}\\) and \\(\\tilde{g}\\) are located at \\((m,n,p)\\) with components at \\((m \\pm \\frac{1}{2},n,p)\\) etc., while \\(\\hat{h}\\) and \\(\\tilde{h}\\) are located at \\((m \\pm \\frac{1}{2}, n \\pm \\frac{1}{2}, p \\pm \\frac{1}{2})\\) with components at \\((m, n \\pm \\frac{1}{2}, p \\pm \\frac{1}{2})\\) etc.
[code: curls]\ncurl_forward(g)[i,j,k] = [Dy_forward(gz)[i, j, k] - Dz_forward(gy)[i, j, k],\n Dz_forward(gx)[i, j, k] - Dx_forward(gz)[i, j, k],\n Dx_forward(gy)[i, j, k] - Dy_forward(gx)[i, j, k]]\n\ncurl_back(g)[i,j,k] = [Dy_back(gz)[i, j, k] - Dz_back(gy)[i, j, k],\n Dz_back(gx)[i, j, k] - Dx_back(gz)[i, j, k],\n Dx_back(gy)[i, j, k] - Dy_back(gx)[i, j, k]]\n For example, consider the forward curl, at (m, n, p), of a back-vector field g, defined on a grid containing (m + 1/2, n + 1/2, p + 1/2). The curl will be a fore-vector, so its z-component will be defined at (m, n, p + 1/2). Take the nearest x- and y-components of g in the xy plane where the curl's z-component is located; these are
[curl components]\n(m, n + 1/2, p + 1/2) : x-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1, n + 1/2, p + 1/2) : x-component of back-vector at (m + 3/2, n + 1/2, p + 1/2)\n(m + 1/2, n , p + 1/2) : y-component of back-vector at (m + 1/2, n + 1/2, p + 1/2)\n(m + 1/2, n + 1 , p + 1/2) : y-component of back-vector at (m + 1/2, n + 3/2, p + 1/2)\n These four xy-components can be used to form a loop around the curl's z-component; its magnitude and sign is set by their loop-oriented sum (i.e. two have their signs flipped to complete the loop).
[figure: z-component of curl]\n : |\n z y : ^^ |\n |/_x :....||.<.....| (m+1, n+1, p+1/2)\n / || /\n | v || | ^\n |/ |/\n (m, n, p+1/2) |_____>______| (m+1, n, p+1/2)\n"},{"location":"api/fdmath/#meanas.fdmath--maxwells-equations","title":"Maxwell's Equations","text":"If we discretize both space (m,n,p) and time (l), Maxwell's equations become
$$ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}{l,\\vec{r}} &= -\\tilde{\\partial}_t \\hat{B} - \\hat{M}}{2}, \\vec{r} + \\frac{1}{2}{l, \\vec{r} + \\frac{1}{2}} \\ \\hat{\\nabla} \\times \\hat{H}}{2},\\vec{r} + \\frac{1}{2}} &= \\hat{\\partialt \\tilde{D} + \\tilde{J}}{l-\\frac{1}{2},\\vec{r}} \\ \\tilde{\\nabla} \\cdot \\hat{B} &= 0 \\ \\hat{\\nabla} \\cdot \\tilde{D}}{2}, \\vec{r} + \\frac{1}{2}{l,\\vec{r}} &= \\rho \\end{aligned} $$}
with
$$ \\begin{aligned} \\hat{B}{\\vec{r}} &= \\mu} + \\frac{1}{2}} \\cdot \\hat{H{\\vec{r} + \\frac{1}{2}} \\ \\tilde{D} \\end{aligned} $$}} &= \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}
where the spatial subscripts are abbreviated as \\(\\vec{r} = (m, n, p)\\) and \\(\\vec{r} + \\frac{1}{2} = (m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2})\\), \\(\\tilde{E}\\) and \\(\\hat{H}\\) are the electric and magnetic fields, \\(\\tilde{J}\\) and \\(\\hat{M}\\) are the electric and magnetic current distributions, and \\(\\epsilon\\) and \\(\\mu\\) are the dielectric permittivity and magnetic permeability.
The above is Yee's algorithm, written in a form analogous to Maxwell's equations. The time derivatives can be expanded to form the update equations:
[code: Maxwell's equations updates]\nH[i, j, k] -= dt * (curl_forward(E)[i, j, k] + M[t, i, j, k]) / mu[i, j, k]\nE[i, j, k] += dt * (curl_back( H)[i, j, k] + J[t, i, j, k]) / epsilon[i, j, k]\n Note that the E-field fore-vector and H-field back-vector are offset by a half-cell, resulting in distinct locations for all six E- and H-field components:
[figure: Field components]\n\n (m - 1/2,=> ____________Hx__________[H] <= r + 1/2 = (m + 1/2,\n n + 1/2, /: /: /| n + 1/2,\n z y p + 1/2) / : / : / | p + 1/2)\n |/_x / : / : / |\n / : Ez__________Hy | Locations of the E- and\n / : : : /| | H-field components for the\n (m - 1/2, / : : Ey...../.|..Hz [E] fore-vector at r = (m,n,p)\n n - 1/2, =>/________________________/ | /| (the large cube's center)\n p + 1/2) | : : / | | / | and [H] back-vector at r + 1/2\n | : :/ | |/ | (the top right corner)\n | : [E].......|.Ex |\n | :.................|......| <= (m + 1/2, n + 1/2, p + 1/2)\n | / | /\n | / | /\n | / | / This is the Yee discretization\n | / | / scheme (\"Yee cell\").\nr - 1/2 = | / | /\n (m - 1/2, |/ |/\n n - 1/2,=> |________________________| <= (m + 1/2, n - 1/2, p - 1/2)\n p - 1/2)\n Each component forms its own grid, offset from the others:
[figure: E-fields for adjacent cells]\n\n H1__________Hx0_________H0\n z y /: /|\n |/_x / : / | This figure shows H back-vector locations\n / : / | H0, H1, etc. and their associated components\n Hy1 : Hy0 | H0 = (Hx0, Hy0, Hz0) etc.\n / : / |\n / Hz1 / Hz0\n H2___________Hx3_________H3 | The equivalent drawing for E would have\n | : | | fore-vectors located at the cube's\n | : | | center (and the centers of adjacent cubes),\n | : | | with components on the cube's faces.\n | H5..........Hx4...|......H4\n | / | /\n Hz2 / Hz2 /\n | / | /\n | Hy6 | Hy4\n | / | /\n |/ |/\n H6__________Hx7__________H7\n The divergence equations can be derived by taking the divergence of the curl equations and combining them with charge continuity, $$ \\hat{\\nabla} \\cdot \\tilde{J} + \\hat{\\partial}_t \\rho = 0 $$ implying that the discrete Maxwell's equations do not produce spurious charges.
"},{"location":"api/fdmath/#meanas.fdmath--wave-equation","title":"Wave equation","text":"Taking the backward curl of the \\(\\tilde{\\nabla} \\times \\tilde{E}\\) equation and replacing the resulting \\(\\hat{\\nabla} \\times \\hat{H}\\) term using its respective equation, and setting \\(\\hat{M}\\) to zero, we can form the discrete wave equation:
\\[ \\begin{aligned} \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}} &= -\\tilde{\\partial}_t \\hat{B}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} - \\hat{M}_{l-1, \\vec{r} + \\frac{1}{2}} \\\\ \\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}} &= -\\tilde{\\partial}_t \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &= \\hat{\\nabla} \\times (-\\tilde{\\partial}_t \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}}) \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &= -\\tilde{\\partial}_t \\hat{\\nabla} \\times \\hat{H}_{l-\\frac{1}{2}, \\vec{r} + \\frac{1}{2}} \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) &= -\\tilde{\\partial}_t \\hat{\\partial}_t \\epsilon_{\\vec{r}} \\tilde{E}_{l, \\vec{r}} + \\hat{\\partial}_t \\tilde{J}_{l-\\frac{1}{2},\\vec{r}} \\\\ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l,\\vec{r}}) + \\tilde{\\partial}_t \\hat{\\partial}_t \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{l, \\vec{r}} &= \\tilde{\\partial}_t \\tilde{J}_{l - \\frac{1}{2}, \\vec{r}} \\end{aligned} \\]"},{"location":"api/fdmath/#meanas.fdmath--frequency-domain","title":"Frequency domain","text":"We can substitute in a time-harmonic fields
\\[ \\begin{aligned} \\tilde{E}_{l, \\vec{r}} &= \\tilde{E}_{\\vec{r}} e^{-\\imath \\omega l \\Delta_t} \\\\ \\tilde{J}_{l, \\vec{r}} &= \\tilde{J}_{\\vec{r}} e^{-\\imath \\omega (l - \\frac{1}{2}) \\Delta_t} \\end{aligned} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_t &\\Rightarrow (e^{ \\imath \\omega \\Delta_t} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{-\\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{-\\imath \\omega \\Delta_t / 2}\\\\ \\hat{\\partial}_t &\\Rightarrow (1 - e^{-\\imath \\omega \\Delta_t}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_t} \\sin(\\omega \\Delta_t / 2) e^{ \\imath \\omega \\Delta_t / 2} = -\\imath \\Omega e^{ \\imath \\omega \\Delta_t / 2}\\\\ \\Omega &= 2 \\sin(\\omega \\Delta_t / 2) / \\Delta_t \\end{aligned} \\]This gives the frequency-domain wave equation,
\\[ \\hat{\\nabla} \\times (\\mu^{-1}_{\\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}}) -\\Omega^2 \\epsilon_{\\vec{r}} \\cdot \\tilde{E}_{\\vec{r}} = -\\imath \\Omega \\tilde{J}_{\\vec{r}} e^{\\imath \\omega \\Delta_t / 2} \\\\ \\]"},{"location":"api/fdmath/#meanas.fdmath--plane-waves-and-dispersion-relation","title":"Plane waves and Dispersion relation","text":"With uniform material distribution and no sources
\\[ \\begin{aligned} \\mu_{\\vec{r} + \\frac{1}{2}} &= \\mu \\\\ \\epsilon_{\\vec{r}} &= \\epsilon \\\\ \\tilde{J}_{\\vec{r}} &= 0 \\\\ \\end{aligned} \\]the frequency domain wave equation simplifies to
\\[ \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} - \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]Since \\(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}} = 0\\), we can simplify
\\[ \\begin{aligned} \\hat{\\nabla} \\times \\tilde{\\nabla} \\times \\tilde{E}_{\\vec{r}} &= \\tilde{\\nabla}(\\hat{\\nabla} \\cdot \\tilde{E}_{\\vec{r}}) - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\hat{\\nabla} \\cdot \\tilde{\\nabla} \\tilde{E}_{\\vec{r}} \\\\ &= - \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} \\end{aligned} \\]and we get
\\[ \\tilde{\\nabla}^2 \\tilde{E}_{\\vec{r}} + \\Omega^2 \\epsilon \\mu \\tilde{E}_{\\vec{r}} = 0 \\]We can convert this to three scalar-wave equations of the form
\\[ (\\tilde{\\nabla}^2 + K^2) \\phi_{\\vec{r}} = 0 \\]with \\(K^2 = \\Omega^2 \\mu \\epsilon\\). Now we let
\\[ \\phi_{\\vec{r}} = A e^{\\imath (k_x m \\Delta_x + k_y n \\Delta_y + k_z p \\Delta_z)} \\]resulting in
\\[ \\begin{aligned} \\tilde{\\partial}_x &\\Rightarrow (e^{ \\imath k_x \\Delta_x} - 1) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{ \\imath k_x \\Delta_x / 2} = \\imath K_x e^{ \\imath k_x \\Delta_x / 2}\\\\ \\hat{\\partial}_x &\\Rightarrow (1 - e^{-\\imath k_x \\Delta_x}) / \\Delta_t = \\frac{-2 \\imath}{\\Delta_x} \\sin(k_x \\Delta_x / 2) e^{-\\imath k_x \\Delta_x / 2} = \\imath K_x e^{-\\imath k_x \\Delta_x / 2}\\\\ K_x &= 2 \\sin(k_x \\Delta_x / 2) / \\Delta_x \\\\ \\end{aligned} \\]with similar expressions for the y and z dimnsions (and \\(K_y, K_z\\)).
This implies
\\[ \\tilde{\\nabla}^2 = -(K_x^2 + K_y^2 + K_z^2) \\phi_{\\vec{r}} \\\\ K_x^2 + K_y^2 + K_z^2 = \\Omega^2 \\mu \\epsilon = \\Omega^2 / c^2 \\]where \\(c = \\sqrt{\\mu \\epsilon}\\).
Assuming real \\((k_x, k_y, k_z), \\omega\\) will be real only if
\\[ c^2 \\Delta_t^2 = \\frac{\\Delta_t^2}{\\mu \\epsilon} < 1/(\\frac{1}{\\Delta_x^2} + \\frac{1}{\\Delta_y^2} + \\frac{1}{\\Delta_z^2}) \\]If \\(\\Delta_x = \\Delta_y = \\Delta_z\\), this simplifies to \\(c \\Delta_t < \\Delta_x / \\sqrt{3}\\). This last form can be interpreted as enforcing causality; the distance that light travels in one timestep (i.e., \\(c \\Delta_t\\)) must be less than the diagonal of the smallest cell ( \\(\\Delta_x / \\sqrt{3}\\) when on a uniform cubic grid).
"},{"location":"api/fdmath/#meanas.fdmath--grid-description","title":"Grid description","text":"As described in the section on scalar discrete derivatives above, cell widths (dx[i], dy[j], dz[k]) along each axis can be arbitrary and independently defined. Moreover, all field components are actually defined at \"derived\" or \"dual\" positions, in-between the \"base\" grid points on one or more axes.
To get a better sense of how this works, let's start by drawing a grid with uniform dy and dz and nonuniform dx. 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 dx affects the various components.
Place the E fore-vectors at integer indices \\(r = (m, n, p)\\) and the H back-vectors at fractional indices \\(r + \\frac{1}{2} = (m + \\frac{1}{2}, n + \\frac{1}{2}, p + \\frac{1}{2})\\). Remember that these are indices and not coordinates; they can correspond to arbitrary (monotonically increasing) coordinates depending on the cell widths.
Draw lines to denote the planes on which the H components and back-vectors are defined. For simplicity, don't draw the equivalent planes for the E components and fore-vectors, except as necessary to show their locations -- it's easiest to just connect them to their associated H-equivalents.
The result looks something like this:
[figure: Component centers]\n p=\n [H]__________Hx___________[H]_____Hx______[H] __ +1/2\n z y /: /: /: /: /| | |\n |/_x / : / : / : / : / | | |\n / : / : / : / : / | | |\n Hy : Ez...........Hy : Ez......Hy | | |\n /: : : : /: : : : /| | | |\n / : Hz : Ey....../.:..Hz : Ey./.|..Hz __ 0 | dz[0]\n / : /: : / / : /: : / / | /| | |\n /_________________________/_______________/ | / | | |\n | :/ : :/ | :/ : :/ | |/ | | |\n | Ex : [E].......|..Ex : [E]..|..Ex | | |\n | : | : | | | |\n | [H]..........Hx....|......[H].....H|x.....[H] __ --------- (n=+1/2, p=-1/2)\n | / | / | / / /\n Hz / Hz / Hz / / /\n | / | / | / / /\n | Hy | Hy | Hy __ 0 / dy[0]\n | / | / | / / /\n | / | / | / / /\n |/ |/ |/ / /\n [H]__________Hx___________[H]_____Hx______[H] __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ------------------------- ----------------\n dx[0] dx[1]\n\n Part of a nonuniform \"base grid\", with labels specifying\n positions of the various field components. [E] fore-vectors\n are at the cell centers, and [H] back-vectors are at the\n vertices. H components along the near (-y) top (+z) edge\n have been omitted to make the insides of the cubes easier\n to visualize.\n 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 m = +1/2: it is shifted in the x-direction by a half-cell from the E fore-vector at m = 0 (labeled [E] in the figure). It corresponds to a volume between m = 0 and m = +1 (the other dimensions are not shifted, i.e. they are still bounded by n, p = +-1/2). (See figure below). Since m 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 dx'[0] = (dx[0] + dx[1]) / 2 rather than the base dx. (See also Scalar derivatives and cell shifts).
[figure: Ex volumes]\n p=\n <_________________________________________> __ +1/2\n z y << /: / /: >> | |\n |/_x < < / : / / : > > | |\n < < / : / / : > > | |\n < < / : / / : > > | |\n <: < / : : / : >: > | |\n < : < / : : / : > : > __ 0 | dz[0]\n < : < / : : / :> : > | |\n <____________/____________________/_______> : > | |\n < : < | : : | > : > | |\n < Ex < | : Ex | > Ex > | |\n < : < | : : | > : > | |\n < : <....|.......:........:...|.......>...:...> __ --------- (n=+1/2, p=-1/2)\n < : < | / : /| /> : > / /\n < : < | / : / | / > : > / /\n < :< | / :/ | / > :> / /\n < < | / : | / > > _ 0 / dy[0]\n < < | / | / > > / /\n < < | / | / > > / /\n << |/ |/ >> / /\n <____________|____________________|_______> __ -1/2 /\n =n\n |------------|------------|-------|-------|\n -1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ -------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Ex values are positioned on the x-faces of the base\n grid. They represent the Ex field in volumes shifted by\n a half-cell in the x-dimension, as shown here. Only the\n center cell (with width dx'[0]) is fully shown; the\n other two are truncated (shown using >< markers).\n\n Note that the Ex positions are the in the same positions\n as the previous figure; only the cell boundaries have moved.\n Also note that the points at which Ex is defined are not\n necessarily centered in the volumes they represent; non-\n uniform cell sizes result in off-center volumes like the\n center cell here.\n 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.
[figure: Hy volumes]\n p=\n z y mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm __ +1/2 s\n |/_x << m: m: >> | |\n < < m : m : > > | | dz'[1]\n < < m : m : > > | |\n Hy........... m........Hy...........m......Hy > | |\n < < m : m : > > | |\n < ______ m_____:_______________m_____:_>______ __ 0\n < < m /: m / > > | |\n mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm > | |\n < < | / : | / > > | | dz'[0]\n < < | / : | / > > | |\n < < | / : | / > > | |\n < wwwww|w/wwwwwwwwwwwwwwwwwww|w/wwwww>wwwwwwww __ s\n < < |/ w |/ w> > / /\n _____________|_____________________|________ > / /\n < < | w | w > > / /\n < Hy........|...w........Hy.......|...w...>..Hy _ 0 / dy[0]\n < < | w | w > > / /\n << | w | w > > / /\n < |w |w >> / /\n wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww __ -1/2 /\n\n |------------|------------|--------|-------|\n-1/2 0 +1/2 +1 +3/2 = m\n\n ~------------ --------------------- -------~\n dx'[-1] dx'[0] dx'[1]\n\n The Hy values are positioned on the y-edges of the base\n grid. Again here, the 'Hy' labels represent the same points\n as in the basic grid figure above; the edges have shifted\n by a half-cell along the x- and z-axes.\n\n The grid lines _|:/ are edges of the area represented by\n each Hy value, and the lines drawn using <m>.w represent\n edges where a cell's faces extend beyond the drawn area\n (i.e. where the drawing is truncated in the x- or z-\n directions).\n"},{"location":"api/fdmath/#meanas.fdmath--datastructure-dx_lists_t","title":"Datastructure: dx_lists_t","text":"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.
The base grid's cell sizes could be fully described by a list of three 1D arrays, specifying the cell widths along all three axes:
[dx, dy, dz] = [[dx[0], dx[1], ...], [dy[0], ...], [dz[0], ...]]\n 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.
Knowing the base grid's cell widths and the boundary conditions (periodic unless otherwise noted) is enough information to calculate the cell widths dx', dy', and dz' for the derived grids.
However, since most operations are trivially generalized to allow either E or H to be defined on the base grid, they are written to take the a full set of base and derived cell widths, distinguished by which field they apply to rather than their \"base\" or \"derived\" status. This removes the need for each function to generate the derived widths, and makes the \"base\" vs \"derived\" distinction unnecessary in the code.
The resulting data structure containing all the cell widths takes the form of a list-of-lists-of-arrays. The first list-of-arrays provides the cell widths for the E-field fore-vectors, while the second list-of-arrays does the same for the H-field back-vectors:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\n where dx_e[0] is the x-width of the m=0 cells, as used when calculating dE/dx, and dy_h[0] is the y-width of the n=0 cells, as used when calculating dH/dy, etc.
Since each vector component of E and H is defined in a different location and represents a different volume, the value of the spatially-discrete epsilon and mu can also be different for all three field components, even when representing a simple planar interface between two isotropic materials.
As a result, epsilon and mu are taken to have the same dimensions as the field, and composed of the three diagonal tensor components:
[equations: epsilon_and_mu]\nepsilon = [epsilon_xx, epsilon_yy, epsilon_zz]\nmu = [mu_xx, mu_yy, mu_zz]\n or
\\[ \\epsilon = \\begin{bmatrix} \\epsilon_{xx} & 0 & 0 \\\\ 0 & \\epsilon_{yy} & 0 \\\\ 0 & 0 & \\epsilon_{zz} \\end{bmatrix} $$ $$ \\mu = \\begin{bmatrix} \\mu_{xx} & 0 & 0 \\\\ 0 & \\mu_{yy} & 0 \\\\ 0 & 0 & \\mu_{zz} \\end{bmatrix} \\]where the off-diagonal terms (e.g. epsilon_xy) are assumed to be zero.
High-accuracy volumetric integration of shapes on multiple grids can be performed by the gridlock module.
The values of the vacuum permittivity and permability effectively become scaling factors that appear in several locations (e.g. between the E and H fields). In order to limit floating-point inaccuracy and simplify calculations, they are often set to 1 and relative permittivities and permeabilities are used in their places; the true values can be multiplied back in after the simulation is complete if non- normalized results are needed.
"},{"location":"api/fdmath/#functional-and-sparse-operators","title":"Functional and sparse operators","text":""},{"location":"api/fdmath/#meanas.fdmath.functional","title":"meanas.fdmath.functional","text":"Math functions for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\n Utility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_e Sequence[NDArray[floating | complexfloating]] | None Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
None Returns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t] List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> tuple[\n fdfield_updater_t, fdfield_updater_t, fdfield_updater_t\n]\n Utility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_h Sequence[NDArray[floating | complexfloating]] | None Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
None Returns:
Type Descriptiontuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t] List of functions for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.functional.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\n Curl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_e Sequence[NDArray[floating | complexfloating]] | None Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
None Returns:
Type DescriptionCallable[[TT], TT] Function f for taking the discrete forward curl of a field,
Callable[[TT], TT] f(E) -> curlE \\(= \\nabla_f \\times E\\)
curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]]\n | None = None,\n) -> Callable[[TT], TT]\n Create a function which takes the backward curl of a field.
Parameters:
Name Type Description Defaultdx_h Sequence[NDArray[floating | complexfloating]] | None Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
None Returns:
Type DescriptionCallable[[TT], TT] Function f for taking the discrete backward curl of a field,
Callable[[TT], TT] f(H) -> curlH \\(= \\nabla_b \\times H\\)
Matrix operators for finite difference simulations
Basic discrete calculus etc.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_circ","title":"shift_circ","text":"shift_circ(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\n Utility operator for performing a circular shift along a specified axis by a specified number of elements.
Parameters:
Name Type Description Defaultaxis int Axis to shift along. x=0, y=1, z=2
requiredshape Sequence[int] Shape of the grid being shifted
requiredshift_distance int Number of cells to shift by. May be negative. Default 1.
1 Returns:
Type Descriptionsparray Sparse matrix for performing the circular shift.
"},{"location":"api/fdmath/#meanas.fdmath.operators.shift_with_mirror","title":"shift_with_mirror","text":"shift_with_mirror(\n axis: int, shape: Sequence[int], shift_distance: int = 1\n) -> sparse.sparray\n Utility operator for performing an n-element shift along a specified axis, with mirror boundary conditions applied to the cells beyond the receding edge.
Parameters:
Name Type Description Defaultaxis int Axis to shift along. x=0, y=1, z=2
requiredshape Sequence[int] Shape of the grid being shifted
requiredshift_distance int Number of cells to shift by. May be negative. Default 1.
1 Returns:
Type Descriptionsparray Sparse matrix for performing the shift-with-mirror.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_forward","title":"deriv_forward","text":"deriv_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\n Utility operators for taking discretized derivatives (forward variant).
Parameters:
Name Type Description Defaultdx_e Sequence[NDArray[floating | complexfloating]] Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
Returns:
Type Descriptionlist[sparray] List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.deriv_back","title":"deriv_back","text":"deriv_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> list[sparse.sparray]\n Utility operators for taking discretized derivatives (backward variant).
Parameters:
Name Type Description Defaultdx_h Sequence[NDArray[floating | complexfloating]] Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
Returns:
Type Descriptionlist[sparray] List of operators for taking forward derivatives along each axis.
"},{"location":"api/fdmath/#meanas.fdmath.operators.cross","title":"cross","text":"cross(B: Sequence[sparray]) -> sparse.sparray\n Cross product operator
Parameters:
Name Type Description DefaultB Sequence[sparray] List [Bx, By, Bz] of sparse matrices corresponding to the x, y, z portions of the operator on the left side of the cross product.
Returns:
Type Descriptionsparray Sparse matrix corresponding to (B x), where x is the cross product.
"},{"location":"api/fdmath/#meanas.fdmath.operators.vec_cross","title":"vec_cross","text":"vec_cross(b: vfdfield_t) -> sparse.sparray\n Vector cross product operator
Parameters:
Name Type Description Defaultb vfdfield_t Vector on the left side of the cross product.
requiredReturns:
Sparse matrix corresponding to (b x), where x is the cross product.\n"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_forward","title":"avg_forward","text":"avg_forward(\n axis: int, shape: Sequence[int]\n) -> sparse.sparray\n Forward average operator (x4 = (x4 + x5) / 2)
Parameters:
Name Type Description Defaultaxis int Axis to average along (x=0, y=1, z=2)
requiredshape Sequence[int] Shape of the grid to average
requiredReturns:
Type Descriptionsparray Sparse matrix for forward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.avg_back","title":"avg_back","text":"avg_back(axis: int, shape: Sequence[int]) -> sparse.sparray\n Backward average operator (x4 = (x4 + x3) / 2)
Parameters:
Name Type Description Defaultaxis int Axis to average along (x=0, y=1, z=2)
requiredshape Sequence[int] Shape of the grid to average
requiredReturns:
Type Descriptionsparray Sparse matrix for backward average operation.
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_forward","title":"curl_forward","text":"curl_forward(\n dx_e: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\n Curl operator for use with the E field.
Parameters:
Name Type Description Defaultdx_e Sequence[NDArray[floating | complexfloating]] Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
Returns:
Type Descriptionsparray Sparse matrix for taking the discretized curl of the E-field
"},{"location":"api/fdmath/#meanas.fdmath.operators.curl_back","title":"curl_back","text":"curl_back(\n dx_h: Sequence[NDArray[floating | complexfloating]],\n) -> sparse.sparray\n Curl operator for use with the H field.
Parameters:
Name Type Description Defaultdx_h Sequence[NDArray[floating | complexfloating]] Lists of cell sizes for all axes [[dx_0, dx_1, ...], [dy_0, dy_1, ...], ...].
Returns:
Type Descriptionsparray Sparse matrix for taking the discretized curl of the H-field
"},{"location":"api/fdmath/#meanas.fdmath.vectorization","title":"meanas.fdmath.vectorization","text":"Functions for moving between a vector field (list of 3 ndarrays, [f_x, f_y, f_z]) and a 1D array representation of that field [f_x0, f_x1, f_x2,... f_y0,... f_z0,...]. Vectorized versions of the field use row-major (ie., C-style) ordering.
vec(f: None) -> None\nvec(f: fdfield_t) -> vfdfield_t\nvec(f: cfdfield_t) -> vcfdfield_t\nvec(f: fdfield2_t) -> vfdfield2_t\nvec(f: cfdfield2_t) -> vcfdfield2_t\nvec(f: fdslice_t) -> vfdslice_t\nvec(f: cfdslice_t) -> vcfdslice_t\nvec(f: ArrayLike) -> NDArray\n vec(\n f: fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | ArrayLike\n | None,\n) -> (\n vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | NDArray\n | None\n)\n Create a 1D ndarray from a vector field which spans a 1-3D region.
Returns None if called with f=None.
Parameters:
Name Type Description Defaultf fdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | None A vector field, e.g. [f_x, f_y, f_z] where each f_ component is a 1- to 3-D ndarray (f_* should all be the same size). Doesn't fail with f=None.
Returns:
Type Descriptionvfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | NDArray | None 1D ndarray containing the linearized field (or None)
unvec(\n v: None, shape: Sequence[int], nvdim: int = 3\n) -> None\nunvec(\n v: vfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield_t\nunvec(\n v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield_t\nunvec(\n v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> fdfield2_t\nunvec(\n v: vcfdfield2_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdfield2_t\nunvec(\n v: vfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> fdslice_t\nunvec(\n v: vcfdslice_t, shape: Sequence[int], nvdim: int = 3\n) -> cfdslice_t\nunvec(\n v: ArrayLike, shape: Sequence[int], nvdim: int = 3\n) -> NDArray\n unvec(\n v: vfdfield_t\n | vcfdfield_t\n | vfdfield2_t\n | vcfdfield2_t\n | vfdslice_t\n | vcfdslice_t\n | ArrayLike\n | None,\n shape: Sequence[int],\n nvdim: int = 3,\n) -> (\n fdfield_t\n | cfdfield_t\n | fdfield2_t\n | cfdfield2_t\n | fdslice_t\n | cfdslice_t\n | NDArray\n | None\n)\n Perform the inverse of vec(): take a 1D ndarray and output an nvdim-component field of form e.g. [f_x, f_y, f_z] (nvdim=3) where each of f_* is a len(shape)-dimensional ndarray.
Returns None if called with v=None.
Parameters:
Name Type Description Defaultv vfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | ArrayLike | None 1D ndarray representing a vector field of shape shape (or None)
requiredshape Sequence[int] shape of the vector field
requirednvdim int Number of components in each vector
3 Returns:
Type Descriptionfdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | NDArray | None [f_x, f_y, f_z] where each f_ is a len(shape) dimensional ndarray (or None)
Types shared across multiple submodules
"},{"location":"api/fdmath/#meanas.fdmath.types.dx_lists_t","title":"dx_lists_tmodule-attribute","text":"dx_lists_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n 'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...], [dz_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...], [dz_h[0], ...]]]\n where dx_e[0] is the x-width of the x=0 cells, as used when calculating dE/dx, and dy_h[0] is the y-width of the y=0 cells, as used when calculating dH/dy, etc.
module-attribute","text":"dx_lists2_t = Sequence[\n Sequence[NDArray[floating | complexfloating]]\n]\n 2D 'dxes' datastructure which contains grid cell width information in the following format:
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]],\n [[dx_h[0], dx_h[1], ...], [dy_h[0], ...]]]\n where dx_e[0] is the x-width of the x=0 cells, as used when calculating dE/dx, and dy_h[0] is the y-width of the y=0 cells, as used when calculating dH/dy, etc.
module-attribute","text":"dx_lists_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\n Mutable version of dx_lists_t
module-attribute","text":"dx_lists2_mut = MutableSequence[\n MutableSequence[NDArray[floating | complexfloating]]\n]\n Mutable version of dx_lists2_t
module-attribute","text":"fdfield_updater_t = Callable[..., fdfield_t]\n Convenience type for functions which take and return an fdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield_updater_t","title":"cfdfield_updater_tmodule-attribute","text":"cfdfield_updater_t = Callable[..., cfdfield_t]\n Convenience type for functions which take and return an cfdfield_t
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield","title":"fdfield","text":"fdfield = fdfield_t | NDArray[floating]\n Vector field with shape (3, X, Y, Z) (e.g. [E_x, E_y, E_z])
vfdfield = vfdfield_t | NDArray[floating]\n Linearized vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield","title":"cfdfield","text":"cfdfield = cfdfield_t | NDArray[complexfloating]\n Complex vector field with shape (3, X, Y, Z) (e.g. [E_x, E_y, E_z])
vcfdfield = vcfdfield_t | NDArray[complexfloating]\n Linearized complex vector field (single vector of length 3XY*Z)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdslice","title":"fdslice","text":"fdslice = fdslice_t | NDArray[floating]\n Vector field slice with shape (3, X, Y) (e.g. [E_x, E_y, E_z] at a single Z position)
vfdslice = vfdslice_t | NDArray[floating]\n Linearized vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdslice","title":"cfdslice","text":"cfdslice = cfdslice_t | NDArray[complexfloating]\n Complex vector field slice with shape (3, X, Y) (e.g. [E_x, E_y, E_z] at a single Z position)
vcfdslice = vcfdslice_t | NDArray[complexfloating]\n Linearized complex vector field slice (single vector of length 3XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.fdfield2","title":"fdfield2","text":"fdfield2 = fdfield2_t | NDArray[floating]\n 2D Vector field with shape (2, X, Y) (e.g. [E_x, E_y])
vfdfield2 = vfdfield2_t | NDArray[floating]\n 2D Linearized vector field (single vector of length 2XY)
"},{"location":"api/fdmath/#meanas.fdmath.types.cfdfield2","title":"cfdfield2","text":"cfdfield2 = cfdfield2_t | NDArray[complexfloating]\n 2D Complex vector field with shape (2, X, Y) (e.g. [E_x, E_y])
vcfdfield2 = vcfdfield2_t | NDArray[complexfloating]\n 2D Linearized complex vector field (single vector of length 2XY)
"},{"location":"api/fdtd/","title":"fdtd","text":""},{"location":"api/fdtd/#meanas.fdtd","title":"meanas.fdtd","text":"Utilities for running finite-difference time-domain (FDTD) simulations
See the discussion of Maxwell's Equations in meanas.fdmath for basic mathematical background.
From the discussion of \"Plane waves and the Dispersion relation\" in meanas.fdmath, we have
or, if \\(\\Delta_x = \\Delta_y = \\Delta_z\\), then \\(c \\Delta_t < \\frac{\\Delta_x}{\\sqrt{3}}\\).
Based on this, we can set
dt = sqrt(mu.min() * epsilon.min()) / sqrt(1/dx_min**2 + 1/dy_min**2 + 1/dz_min**2)\n The dx_min, dy_min, dz_min should be the minimum value across both the base and derived grids.
Let
\\[ \\begin{aligned} \\tilde{S}_{l, l', \\vec{r}} &=& &\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &=& &\\vec{x} (\\tilde{E}^y_{l,m+1,n,p} \\hat{H}^z_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^z_{l,m+1,n,p} \\hat{H}^y_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{y} (\\tilde{E}^z_{l,m,n+1,p} \\hat{H}^x_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^x_{l,m,n+1,p} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\\\ & &+ &\\vec{z} (\\tilde{E}^x_{l,m,n,p+1} \\hat{H}^y_{l',\\vec{r} + \\frac{1}{2}} - \\tilde{E}^y_{l,m,n,p+1} \\hat{H}^z_{l', \\vec{r} + \\frac{1}{2}}) \\end{aligned} \\]where \\(\\vec{r} = (m, n, p)\\) and \\(\\otimes\\) is a modified cross product in which the \\(\\tilde{E}\\) terms are shifted as indicated.
By taking the divergence and rearranging terms, we can show that
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l', \\vec{r}} &= \\hat{\\nabla} \\cdot (\\tilde{E}_{l, \\vec{r}} \\otimes \\hat{H}_{l', \\vec{r} + \\frac{1}{2}}) \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot \\tilde{\\nabla} \\times \\tilde{E}_{l, \\vec{r}} - \\tilde{E}_{l, \\vec{r}} \\cdot \\hat{\\nabla} \\times \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\\\ &= \\hat{H}_{l', \\vec{r} + \\frac{1}{2}} \\cdot (-\\tilde{\\partial}_t \\mu_{\\vec{r} + \\frac{1}{2}} \\hat{H}_{l - \\frac{1}{2}, \\vec{r} + \\frac{1}{2}} - \\hat{M}_{l, \\vec{r} + \\frac{1}{2}}) - \\tilde{E}_{l, \\vec{r}} \\cdot (\\hat{\\partial}_t \\tilde{\\epsilon}_{\\vec{r}} \\tilde{E}_{l'+\\frac{1}{2}, \\vec{r}} + \\tilde{J}_{l', \\vec{r}}) \\\\ &= \\hat{H}_{l'} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t )(\\tilde{E}_{l'+\\frac{1}{2}} - \\tilde{E}_{l'-\\frac{1}{2}}) - \\hat{H}_{l'} \\cdot \\hat{M}_{l} - \\tilde{E}_l \\cdot \\tilde{J}_{l'} \\\\ \\end{aligned} \\]where in the last line the spatial subscripts have been dropped to emphasize the time subscripts \\(l, l'\\), i.e.
\\[ \\begin{aligned} \\tilde{E}_l &= \\tilde{E}_{l, \\vec{r}} \\\\ \\hat{H}_l &= \\tilde{H}_{l, \\vec{r} + \\frac{1}{2}} \\\\ \\tilde{\\epsilon} &= \\tilde{\\epsilon}_{\\vec{r}} \\\\ \\end{aligned} \\]etc. For \\(l' = l + \\frac{1}{2}\\) we get
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} &= \\hat{H}_{l + \\frac{1}{2}} \\cdot (-\\mu / \\Delta_t)(\\hat{H}_{l + \\frac{1}{2}} - \\hat{H}_{l - \\frac{1}{2}}) - \\tilde{E}_l \\cdot (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} - \\tilde{E}_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= (-\\mu / \\Delta_t)(\\hat{H}^2_{l + \\frac{1}{2}} - \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}}) - (\\epsilon / \\Delta_t)(\\tilde{E}_{l+1} \\cdot \\tilde{E}_l - \\tilde{E}^2_l) - \\hat{H}_{l'} \\cdot \\hat{M}_l - \\tilde{E}_l \\cdot \\tilde{J}_{l + \\frac{1}{2}} \\\\ &= -(\\mu \\hat{H}^2_{l + \\frac{1}{2}} +\\epsilon \\tilde{E}_{l+1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ +(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ \\end{aligned} \\]and for \\(l' = l - \\frac{1}{2}\\),
\\[ \\begin{aligned} \\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} &= (\\mu \\hat{H}^2_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}_{l-1} \\cdot \\tilde{E}_l) / \\Delta_t \\\\ -(\\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} +\\epsilon \\tilde{E}^2_l) / \\Delta_t \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]These two results form the discrete time-domain analogue to Poynting's theorem. They hint at the expressions for the energy, which can be calculated at the same time-index as either the E or H field:
\\[ \\begin{aligned} U_l &= \\epsilon \\tilde{E}^2_l + \\mu \\hat{H}_{l + \\frac{1}{2}} \\cdot \\hat{H}_{l - \\frac{1}{2}} \\\\ U_{l + \\frac{1}{2}} &= \\epsilon \\tilde{E}_l \\cdot \\tilde{E}_{l + 1} + \\mu \\hat{H}^2_{l + \\frac{1}{2}} \\\\ \\end{aligned} \\]Rewriting the Poynting theorem in terms of the energy expressions,
\\[ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l + \\frac{1}{2}} \\\\ - \\hat{H}_{l+\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l+\\frac{1}{2}} \\\\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}_{l, l - \\frac{1}{2}} \\\\ - \\hat{H}_{l-\\frac{1}{2}} \\cdot \\hat{M}_l \\\\ - \\tilde{E}_l \\cdot \\tilde{J}_{l-\\frac{1}{2}} \\\\ \\end{aligned} \\]This result is exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary.
Note that each value of \\(J\\) contributes to the energy twice (i.e. once per field update) despite only causing the value of \\(E\\) to change once (same for \\(M\\) and \\(H\\)).
"},{"location":"api/fdtd/#meanas.fdtd--sources","title":"Sources","text":"It is often useful to excite the simulation with an arbitrary broadband pulse and then extract the frequency-domain response by performing an on-the-fly Fourier transform of the time-domain fields.
accumulate_phasor in meanas.fdtd.phasor performs the phase accumulation for one or more target frequencies, while leaving source normalization and simulation-loop policy to the caller. Convenience wrappers accumulate_phasor_e, accumulate_phasor_h, and accumulate_phasor_j apply the usual Yee time offsets. The helpers accumulate
with caller-provided sample times and weights. In this codebase the matching electric-current convention is typically E -= dt * J / epsilon in FDTD and -i \\omega J on the right-hand side of the FDFD wave equation.
The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written
\\[ f_r(t) = (1 - \\frac{1}{2} (\\omega (t - \\tau))^2) e^{-(\\frac{\\omega (t - \\tau)}{2})^2} \\]with \\(\\tau > \\frac{2 * \\pi}{\\omega}\\) as a minimum delay to avoid a discontinuity at t=0 (assuming the source is off for t<0 this gives \\(\\sim 10^{-3}\\) error at t=0).
"},{"location":"api/fdtd/#meanas.fdtd--boundary-conditions","title":"Boundary conditions","text":"meanas.fdtd exposes two boundary-related building blocks:
conducting_boundary(...) for simple perfect-electric-conductor style field clamping at one face of the domain.cpml_params(...) and updates_with_cpml(...) for convolutional perfectly matched layers (CPMLs) attached to one or more faces of the Yee grid.updates_with_cpml(...) accepts a three-by-two table of CPML parameter blocks:
cpml_params[axis][polarity_index]\n where axis is 0, 1, or 2 and polarity_index corresponds to (-1, +1). Passing None for one entry disables CPML on that face while leaving the other directions unchanged. This is how mixed boundary setups such as \"absorbing in x, periodic in y/z\" are expressed.
When comparing an FDTD run against an FDFD solve, use the same stretched coordinate system in both places:
dxes with the matching SCPML transform.dxes.The electric-current sign convention used throughout the examples and tests is
\\[ E \\leftarrow E - \\Delta_t J / \\epsilon \\]which matches the FDFD right-hand side
\\[ -i \\omega J. \\]"},{"location":"api/fdtd/#core-update-and-analysis-helpers","title":"Core update and analysis helpers","text":""},{"location":"api/fdtd/#meanas.fdtd.base","title":"meanas.fdtd.base","text":"Basic FDTD field updates
"},{"location":"api/fdtd/#meanas.fdtd.base.maxwell_e","title":"maxwell_e","text":"maxwell_e(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\n Build a function which performs a portion the time-domain E-field update,
E += curl_back(H[t]) / epsilon\n The full update should be
E += (curl_back(H[t]) + J) / epsilon\n which requires an additional step of E += J / epsilon which is not performed by the generated function.
See meanas.fdmath for descriptions of
dxes: \"Datastructure: dx_lists_t\" sectionepsilon: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of meanas.fdtd for a discussion of the dt parameter.
Parameters:
Name Type Description Defaultdt float Timestep. See meanas.fdtd for details.
dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_updater_t Function f(E_old, H_old, epsilon) -> E_new.
maxwell_h(\n dt: float, dxes: dx_lists_t | None = None\n) -> fdfield_updater_t\n Build a function which performs part of the time-domain H-field update,
H -= curl_forward(E[t]) / mu\n The full update should be
H -= (curl_forward(E[t]) + M) / mu\n which requires an additional step of H -= M / mu which is not performed by the generated function; this step can be omitted if there is no magnetic current M.
See meanas.fdmath for descriptions of
dxes: \"Datastructure: dx_lists_t\" sectionmu: \"Permittivity and Permeability\" sectionAlso see the \"Timestep\" section of meanas.fdtd for a discussion of the dt parameter.
Parameters:
Name Type Description Defaultdt float Timestep. See meanas.fdtd for details.
dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_updater_t Function f(E_old, H_old, epsilon) -> E_new.
Convolutional perfectly matched layer (CPML) support for FDTD updates.
The helpers in this module construct per-face CPML parameters and then wrap the standard Yee updates with the additional auxiliary psi fields needed by the CPML recurrence.
The intended call pattern is:
cpml_params[axis][polarity_index] table with cpml_params(...).updates_with_cpml(...) together with dt, dxes, and epsilon.update_E / update_H closures in the simulation loop.Each face can be enabled or disabled independently by replacing one table entry with None.
cpml_params(\n axis: int,\n polarity: int,\n dt: float,\n thickness: int = 8,\n ln_R_per_layer: float = -1.6,\n epsilon_eff: float = 1,\n mu_eff: float = 1,\n m: float = 3.5,\n ma: float = 1,\n cfs_alpha: float = 0,\n) -> dict[str, Any]\n Construct the parameter block for one CPML face.
Parameters:
Name Type Description Defaultaxis int Which Cartesian axis the CPML is normal to (0, 1, or 2).
polarity int Which face along that axis (-1 for the low-index face, +1 for the high-index face).
dt float Timestep used by the Yee update.
requiredthickness int Number of Yee cells occupied by the CPML region.
8 ln_R_per_layer float Logarithmic attenuation target per layer.
-1.6 epsilon_eff float Effective permittivity used when choosing the CPML scaling.
1 mu_eff float Effective permeability used when choosing the CPML scaling.
1 m float Polynomial grading exponent for sigma and kappa.
3.5 ma float Polynomial grading exponent for the complex-frequency shift alpha.
1 cfs_alpha float Maximum complex-frequency shift parameter.
0 Returns:
Type Descriptiondict[str, Any] Dictionary with:
dict[str, Any] param_e: (p0, p1, p2) arrays for the E updatedict[str, Any] param_h: (p0, p1, p2) arrays for the H updatedict[str, Any] region: slice tuple selecting the CPML cells on that faceupdates_with_cpml(\n cpml_params: Sequence[Sequence[dict[str, Any] | None]],\n dt: float,\n dxes: dx_lists_t,\n epsilon: fdfield,\n *,\n dtype: DTypeLike = numpy.float32,\n) -> tuple[\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n Callable[[fdfield_t, fdfield_t, fdfield_t], None],\n]\n Build Yee-step update closures augmented with CPML terms.
Parameters:
Name Type Description Defaultcpml_params Sequence[Sequence[dict[str, Any] | None]] Three-by-two sequence indexed as [axis][polarity_index]. Entries are the dictionaries returned by cpml_params(...); use None to disable CPML on one face.
dt float Timestep.
requireddxes dx_lists_t Yee-grid spacing lists [dx_e, dx_h].
epsilon fdfield Electric material distribution used by the E update.
requireddtype DTypeLike Storage dtype for the auxiliary CPML state arrays.
float32 Returns:
Type DescriptionCallable[[fdfield_t, fdfield_t, fdfield_t], None] (update_E, update_H) closures with the same call shape as the basic
Callable[[fdfield_t, fdfield_t, fdfield_t], None] Yee updates:
tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]] update_E(e, h, epsilon)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]] update_H(e, h, mu)tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], Callable[[fdfield_t, fdfield_t, fdfield_t], None]] The closures retain the CPML auxiliary state internally.
"},{"location":"api/fdtd/#meanas.fdtd.boundaries","title":"meanas.fdtd.boundaries","text":"Boundary conditions
"},{"location":"api/fdtd/#meanas.fdtd.boundaries--todo-conducting-boundary-documentation","title":"TODO conducting boundary documentation","text":""},{"location":"api/fdtd/#meanas.fdtd.energy","title":"meanas.fdtd.energy","text":""},{"location":"api/fdtd/#meanas.fdtd.energy.poynting","title":"poynting","text":"poynting(\n e: fdfield, h: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\n Calculate the poynting vector S (\\(S\\)).
This is the energy transfer rate (amount of energy U per dt transferred between adjacent cells) in each direction that happens during the half-step bounded by the two provided fields.
The returned vector field S is the energy flow across +x, +y, and +z boundaries of the corresponding U cell. For example,
mx = numpy.roll(mask, -1, axis=0)\n my = numpy.roll(mask, -1, axis=1)\n mz = numpy.roll(mask, -1, axis=2)\n\n u_hstep = fdtd.energy_hstep(e0=es[ii - 1], h1=hs[ii], e2=es[ii], **args)\n u_estep = fdtd.energy_estep(h0=hs[ii], e1=es[ii], h2=hs[ii + 1], **args)\n delta_j_B = fdtd.delta_energy_j(j0=js[ii], e1=es[ii], dxes=dxes)\n du_half_h2e = u_estep - u_hstep - delta_j_B\n\n s_h2e = -fdtd.poynting(e=es[ii], h=hs[ii], dxes=dxes) * dt\n planes = [s_h2e[0, mask].sum(), -s_h2e[0, mx].sum(),\n s_h2e[1, mask].sum(), -s_h2e[1, my].sum(),\n s_h2e[2, mask].sum(), -s_h2e[2, mz].sum()]\n\n assert_close(sum(planes), du_half_h2e[mask])\n (see meanas.tests.test_fdtd.test_poynting_planes)
The full relationship is $$ \\begin{aligned} (U_{l+\\frac{1}{2}} - U_l) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}{l, l + \\frac{1}{2}} \\ - \\hat{H}}{2}} \\cdot \\hat{Ml \\ - \\tilde{E}_l \\cdot \\tilde{J} \\ (U_l - U_{l-\\frac{1}{2}}) / \\Delta_t &= -\\hat{\\nabla} \\cdot \\tilde{S}}{2}{l, l - \\frac{1}{2}} \\ - \\hat{H}}{2}} \\cdot \\hat{Ml \\ - \\tilde{E}_l \\cdot \\tilde{J} \\ \\end{aligned} $$}{2}
These equalities are exact and should practically hold to within numerical precision. No time- or spatial-averaging is necessary. (See meanas.fdtd docs for derivation.)
Parameters:
Name Type Description Defaulte fdfield E-field
requiredh fdfield H-field (one half-timestep before or after e)
dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Name Type Descriptions fdfield_t 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.
"},{"location":"api/fdtd/#meanas.fdtd.energy.poynting_divergence","title":"poynting_divergence","text":"poynting_divergence(\n s: fdfield | None = None,\n *,\n e: fdfield | None = None,\n h: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Calculate the divergence of the poynting vector.
This is the net energy flow for each cell, i.e. the change in energy U per dt caused by transfer of energy to nearby cells (rather than absorption/emission by currents J or M).
See poynting and meanas.fdtd for more details. Args: s: Poynting vector, as calculated with poynting. Optional; caller can provide e and h instead. e: E-field (optional; need either s or both e and h) h: H-field (optional; need either s or both e and h) dxes: Grid description; see meanas.fdmath.
Returns:
Name Type Descriptionds fdfield_t Divergence of the poynting vector. Entries indicate the net energy flow out of the corresponding energy cell.
"},{"location":"api/fdtd/#meanas.fdtd.energy.energy_hstep","title":"energy_hstep","text":"energy_hstep(\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Calculate energy U at the time of the provided H-field h1.
TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulte0 fdfield E-field one half-timestep before the energy.
requiredh1 fdfield H-field (at the same timestep as the energy).
requirede2 fdfield E-field one half-timestep after the energy.
requiredepsilon fdfield | None Dielectric constant distribution.
None mu fdfield | None Magnetic permeability distribution.
None dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_t Energy, at the time of the H-field h1.
energy_estep(\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Calculate energy U at the time of the provided E-field e1.
TODO: Figure out what this means spatially.
Parameters:
Name Type Description Defaulth0 fdfield H-field one half-timestep before the energy.
requirede1 fdfield E-field (at the same timestep as the energy).
requiredh2 fdfield H-field one half-timestep after the energy.
requiredepsilon fdfield | None Dielectric constant distribution.
None mu fdfield | None Magnetic permeability distribution.
None dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_t Energy, at the time of the E-field e1.
delta_energy_h2e(\n dt: float,\n e0: fdfield,\n h1: fdfield,\n e2: fdfield,\n h3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Change in energy during the half-step from h1 to e2.
This is just from (e2 * e2 + h3 * h1) - (h1 * h1 + e0 * e2)
Parameters:
Name Type Description Defaulte0 fdfield E-field one half-timestep before the start of the energy delta.
requiredh1 fdfield H-field at the start of the energy delta.
requirede2 fdfield E-field at the end of the energy delta (one half-timestep after h1).
h3 fdfield H-field one half-timestep after the end of the energy delta.
requiredepsilon fdfield | None Dielectric constant distribution.
None mu fdfield | None Magnetic permeability distribution.
None dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_t Change in energy from the time of h1 to the time of e2.
delta_energy_e2h(\n dt: float,\n h0: fdfield,\n e1: fdfield,\n h2: fdfield,\n e3: fdfield,\n epsilon: fdfield | None = None,\n mu: fdfield | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Change in energy during the half-step from e1 to h2.
This is just from (h2 * h2 + e3 * e1) - (e1 * e1 + h0 * h2)
Parameters:
Name Type Description Defaulth0 fdfield E-field one half-timestep before the start of the energy delta.
requirede1 fdfield H-field at the start of the energy delta.
requiredh2 fdfield E-field at the end of the energy delta (one half-timestep after e1).
e3 fdfield H-field one half-timestep after the end of the energy delta.
requiredepsilon fdfield | None Dielectric constant distribution.
None mu fdfield | None Magnetic permeability distribution.
None dxes dx_lists_t | None Grid description; see meanas.fdmath.
None Returns:
Type Descriptionfdfield_t Change in energy from the time of e1 to the time of h2.
delta_energy_j(\n j0: fdfield, e1: fdfield, dxes: dx_lists_t | None = None\n) -> fdfield_t\n Calculate the electric-current work term \\(J \\cdot E\\) on the Yee grid.
This is the source contribution that appears beside the flux divergence in the discrete Poynting identities documented in meanas.fdtd.
Note that each value of J contributes twice in a full Yee cycle (once per half-step energy balance) even though it directly changes E only once.
Parameters:
Name Type Description Defaultj0 fdfield Electric-current density sampled at the same half-step as the current work term.
requirede1 fdfield Electric field sampled at the matching integer timestep.
requireddxes dx_lists_t | None Grid description; defaults to unit spacing.
None Returns:
Type Descriptionfdfield_t Per-cell source-work contribution with the scalar field shape.
"},{"location":"api/fdtd/#meanas.fdtd.energy.dxmul","title":"dxmul","text":"dxmul(\n ee: fdfield,\n hh: fdfield,\n epsilon: fdfield | float | None = None,\n mu: fdfield | float | None = None,\n dxes: dx_lists_t | None = None,\n) -> fdfield_t\n Multiply E- and H-like field products by material weights and cell volumes.
Parameters:
Name Type Description Defaultee fdfield Three-component electric-field product, such as e0 * e2.
hh fdfield Three-component magnetic-field product, such as h1 * h1.
epsilon fdfield | float | None Electric material weight; defaults to 1.
None mu fdfield | float | None Magnetic material weight; defaults to 1.
None dxes dx_lists_t | None Grid description; defaults to unit spacing.
None Returns:
Type Descriptionfdfield_t Scalar field containing the weighted electric plus magnetic contribution
fdfield_t for each Yee cell.
"},{"location":"api/fdtd/#meanas.fdtd.phasor","title":"meanas.fdtd.phasor","text":"Helpers for extracting single- or multi-frequency phasors from FDTD samples.
These helpers are intentionally low-level: callers own the accumulator arrays and the sampling policy. The accumulated quantity is
dt * sum(weight * exp(-1j * omega * t_step) * sample_step)\n where t_step = (step + offset_steps) * dt.
The usual Yee offsets are:
accumulate_phasor_e(..., step=l) for E_laccumulate_phasor_h(..., step=l) for H_{l + 1/2}accumulate_phasor_j(..., step=l) for J_{l + 1/2}These helpers do not choose warmup/accumulation windows or pulse-envelope normalization. They also do not impose a current sign convention. In this codebase, electric-current injection normally appears as E -= dt * J / epsilon, which matches the FDFD right-hand side -1j * omega * J.
accumulate_phasor(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n offset_steps: float = 0.0,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\n Add one time-domain sample into a phasor accumulator.
The added quantity is
dt * weight * exp(-1j * omega * t_step) * sample\n where t_step = (step + offset_steps) * dt.
This helper already multiplies by dt. If the caller's normalization factor was derived from a discrete sum that already includes dt, pass weight / dt here.
accumulate_phasor_e(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\n Accumulate an E-field sample taken at integer timestep step.
accumulate_phasor_h(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\n Accumulate an H-field sample corresponding to H_{step + 1/2}.
accumulate_phasor_j(\n accumulator: NDArray[complexfloating],\n omegas: float\n | complex\n | Sequence[float | complex]\n | NDArray,\n dt: float,\n sample: ArrayLike,\n step: int,\n *,\n weight: ArrayLike = 1.0,\n) -> NDArray[numpy.complexfloating]\n Accumulate a current sample corresponding to J_{step + 1/2}.
Electromagnetic simulation tools
See the readme or import meanas; help(meanas) for more info.
Operators and helper functions for waveguides with unchanging cross-section.
The propagation direction is chosen to be along the z axis, and all fields are given an implicit z-dependence of the form exp(-1 * wavenumber * z).
As the z-dependence is known, all the functions in this file assume a 2D grid (i.e. dxes = [[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]], [[dx_h[0], ...], [dy_h[0], ...]]]).
===============
Consider Maxwell's equations in continuous space, in the frequency domain. Assuming a structure with some (x, y) cross-section extending uniformly into the z dimension, with a diagonal \\(\\epsilon\\) tensor, we have
\\[ \\begin{aligned} \\nabla \\times \\vec{E}(x, y, z) &= -\\imath \\omega \\mu \\vec{H} \\\\ \\nabla \\times \\vec{H}(x, y, z) &= \\imath \\omega \\epsilon \\vec{E} \\\\ \\vec{E}(x,y,z) &= (\\vec{E}_t(x, y) + E_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\vec{H}(x,y,z) &= (\\vec{H}_t(x, y) + H_z(x, y)\\vec{z}) e^{-\\imath \\beta z} \\\\ \\end{aligned} \\]Expanding the first two equations into vector components, we get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\partial_y E_z - \\partial_z E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= \\partial_z E_x - \\partial_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\partial_x E_y - \\partial_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\partial_y H_z - \\partial_z H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= \\partial_z H_x - \\partial_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\partial_x H_y - \\partial_y H_x \\\\ \\end{aligned} \\]Substituting in our expressions for \\(\\vec{E}\\), \\(\\vec{H}\\) and discretizing:
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} H_x &= \\tilde{\\partial}_y E_z + \\imath \\beta E_y \\\\ -\\imath \\omega \\mu_{yy} H_y &= -\\imath \\beta E_x - \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{zz} H_z &= \\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x \\\\ \\imath \\omega \\epsilon_{xx} E_x &= \\hat{\\partial}_y H_z + \\imath \\beta H_y \\\\ \\imath \\omega \\epsilon_{yy} E_y &= -\\imath \\beta H_x - \\hat{\\partial}_x H_z \\\\ \\imath \\omega \\epsilon_{zz} E_z &= \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Rewrite the last three equations as
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\imath \\omega E_z &= \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ \\end{aligned} \\]Now apply \\(\\imath \\beta \\tilde{\\partial}_x\\) to the last equation, then substitute in for \\(\\imath \\beta H_x\\) and \\(\\imath \\beta H_y\\):
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_x \\imath \\omega E_z &= \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x H_y - \\imath \\beta \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y H_x \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x ( \\imath \\omega \\epsilon_{xx} E_x) - \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (-\\imath \\omega \\epsilon_{yy} E_y) \\\\ \\imath \\beta \\tilde{\\partial}_x E_z &= \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_x \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]With a similar approach (but using \\(\\imath \\beta \\tilde{\\partial}_y\\) instead), we can get
\\[ \\begin{aligned} \\imath \\beta \\tilde{\\partial}_y E_z &= \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\tilde{\\partial}_y \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) \\\\ \\end{aligned} \\]We can combine this equation for \\(\\imath \\beta \\tilde{\\partial}_y E_z\\) with the unused \\(\\imath \\omega \\mu_{xx} H_x\\) and \\(\\imath \\omega \\mu_{yy} H_y\\) equations to get
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\imath \\beta \\tilde{\\partial}_y E_z \\\\ -\\imath \\omega \\mu_{xx} \\imath \\beta H_x &= -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]and
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\imath \\beta \\tilde{\\partial}_x E_z \\\\ -\\imath \\omega \\mu_{yy} \\imath \\beta H_y &= \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) )\\\\ \\end{aligned} \\]However, based on our rewritten equation for \\(\\imath \\beta H_x\\) and the so-far unused equation for \\(\\imath \\omega \\mu_{zz} H_z\\) we can also write
\\[ \\begin{aligned} -\\imath \\omega \\mu_{xx} (\\imath \\beta H_x) &= -\\imath \\omega \\mu_{xx} (-\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y + \\imath \\omega \\mu_{xx} \\hat{\\partial}_x ( \\frac{1}{-\\imath \\omega \\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x)) \\\\ &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]and, similarly,
\\[ \\begin{aligned} -\\imath \\omega \\mu_{yy} (\\imath \\beta H_y) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]By combining both pairs of expressions, we get
\\[ \\begin{aligned} \\beta^2 E_x - \\tilde{\\partial}_x ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= \\omega^2 \\mu_{yy} \\epsilon_{xx} E_x +\\mu_{yy} \\hat{\\partial}_y \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ -\\beta^2 E_y + \\tilde{\\partial}_y ( \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_x (\\epsilon_{xx} E_x) + \\frac{1}{\\epsilon_{zz}} \\hat{\\partial}_y (\\epsilon_{yy} E_y) ) &= -\\omega^2 \\mu_{xx} \\epsilon_{yy} E_y -\\mu_{xx} \\hat{\\partial}_x \\frac{1}{\\mu_{zz}} (\\tilde{\\partial}_x E_y - \\tilde{\\partial}_y E_x) \\\\ \\end{aligned} \\]Using these, we can construct the eigenvalue problem
\\[ \\beta^2 \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} = (\\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix} \\]In the literature, \\(\\beta\\) is usually used to denote the lossless/real part of the propagation constant, but in meanas it is allowed to be complex.
An equivalent eigenvalue problem can be formed using the \\(H_x\\) and \\(H_y\\) fields, if those are more convenient.
Note that \\(E_z\\) was never discretized, so \\(\\beta\\) will need adjustment to account for numerical dispersion if the result is introduced into a space with a discretized z-axis.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_e","title":"operator_e","text":"operator_e(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Waveguide operator of the form
omega**2 * mu * epsilon +\nmu * [[-Dy], [Dx]] / mu * [-Dy, Dx] +\n[[Dx], [Dy]] / epsilon * [Dx, Dy] * epsilon\n for use with a field vector of the form cat([E_x, E_y]).
More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\mu_{yy} \\hat{\\partial}_y \\\\ \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x \\epsilon_{xx} & \\hat{\\partial}_y \\epsilon_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form operator_e(...) @ [E_x, E_y] = wavenumber**2 * [E_x, E_y]
which can then be solved for the eigenmodes of the system (an exp(-i * wavenumber * z) z-dependence is assumed for the fields).
Parameters:
Name Type Description Defaultomega complex The angular frequency of the system.
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.operator_h","title":"operator_h","text":"operator_h(\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Waveguide operator of the form
omega**2 * epsilon * mu +\nepsilon * [[-Dy], [Dx]] / epsilon * [-Dy, Dx] +\n[[Dx], [Dy]] / mu * [Dx, Dy] * mu\n for use with a field vector of the form cat([H_x, H_y]).
More precisely, the operator is
\\[ \\omega^2 \\begin{bmatrix} \\epsilon_{yy} \\mu_{xx} & 0 \\\\ 0 & \\epsilon_{xx} \\mu_{yy} \\end{bmatrix} + \\begin{bmatrix} -\\epsilon_{yy} \\tilde{\\partial}_y \\\\ \\epsilon_{xx} \\tilde{\\partial}_x \\end{bmatrix} \\epsilon_{zz}^{-1} \\begin{bmatrix} -\\hat{\\partial}_y & \\hat{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\hat{\\partial}_x \\\\ \\hat{\\partial}_y \\end{bmatrix} \\mu_{zz}^{-1} \\begin{bmatrix} \\tilde{\\partial}_x \\mu_{xx} & \\tilde{\\partial}_y \\mu_{yy} \\end{bmatrix} \\]\\(\\tilde{\\partial}_x\\) and \\(\\hat{\\partial}_x\\) are the forward and backward derivatives along x, and each \\(\\epsilon_{xx}\\), \\(\\mu_{yy}\\), etc. is a diagonal matrix containing the vectorized material property distribution.
This operator can be used to form an eigenvalue problem of the form operator_h(...) @ [H_x, H_y] = wavenumber**2 * [H_x, H_y]
which can then be solved for the eigenmodes of the system (an exp(-i * wavenumber * z) z-dependence is assumed for the fields).
Parameters:
Name Type Description Defaultomega complex The angular frequency of the system.
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_e","title":"normalized_fields_e","text":"normalized_fields_e(\n e_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\n Given a vector e_xy containing the vectorized E_x and E_y fields, returns normalized, vectorized E and H fields for the system.
Parameters:
Name Type Description Defaulte_xy vcfdfield2 Vector containing E_x and E_y fields
requiredwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z). It should satisfy operator_e() @ e_xy == wavenumber**2 * e_xy
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None prop_phase float Phase shift (dz * corrected_wavenumber) over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).
0 Returns:
Type Descriptionvcfdslice_t (e, h), where each field is vectorized, normalized,
vcfdslice_t and contains all three vector components.
Notese_xy is only the transverse electric eigenvector. This helper first reconstructs the full three-component E and H fields with exy2e(...) and exy2h(...), then normalizes them to unit forward power using _normalized_fields(...).
The normalization target is
\\[ \\Re\\left[\\mathrm{inner\\_product}(e, h, \\mathrm{conj\\_h}=True)\\right] = 1, \\]so the returned fields represent a unit-power forward mode under the discrete Yee-grid Poynting inner product.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.normalized_fields_h","title":"normalized_fields_h","text":"normalized_fields_h(\n h_xy: vcfdfield2,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\n Given a vector h_xy containing the vectorized H_x and H_y fields, returns normalized, vectorized E and H fields for the system.
Parameters:
Name Type Description Defaulth_xy vcfdfield2 Vector containing H_x and H_y fields
requiredwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z). It should satisfy operator_h() @ h_xy == wavenumber**2 * h_xy
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None prop_phase float Phase shift (dz * corrected_wavenumber) over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).
0 Returns:
Type Descriptionvcfdslice_t (e, h), where each field is vectorized, normalized,
vcfdslice_t and contains all three vector components.
NotesThis is the H_x/H_y analogue of normalized_fields_e(...). The final normalized mode should describe the same physical solution, but because the overall complex phase and sign are chosen heuristically, normalized_fields_e(...) and normalized_fields_h(...) need not return identical representatives for nearly symmetric modes.
exy2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Operator which transforms the vector e_xy containing the vectorized E_x and E_y fields, into a vectorized H containing all three H components
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z). It should satisfy operator_e() @ e_xy == wavenumber**2 * e_xy
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2e","title":"hxy2e","text":"hxy2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Operator which transforms the vector h_xy containing the vectorized H_x and H_y fields, into a vectorized E containing all three E components
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z). It should satisfy operator_h() @ h_xy == wavenumber**2 * h_xy
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.hxy2h","title":"hxy2h","text":"hxy2h(\n wavenumber: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Operator which transforms the vector h_xy containing the vectorized H_x and H_y fields, into a vectorized H containing all three H components
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z). It should satisfy operator_h() @ h_xy == wavenumber**2 * h_xy
dxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
mu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.exy2e","title":"exy2e","text":"exy2e(\n wavenumber: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\n Operator which transforms the vector e_xy containing the vectorized E_x and E_y fields, into a vectorized E containing all three E components
From the operator derivation (see module docs), we have
\\[ \\imath \\omega \\epsilon_{zz} E_z = \\hat{\\partial}_x H_y - \\hat{\\partial}_y H_x \\\\ \\]as well as the intermediate equations
\\[ \\begin{aligned} \\imath \\beta H_y &= \\imath \\omega \\epsilon_{xx} E_x - \\hat{\\partial}_y H_z \\\\ \\imath \\beta H_x &= -\\imath \\omega \\epsilon_{yy} E_y - \\hat{\\partial}_x H_z \\\\ \\end{aligned} \\]Combining these, we get
\\[ \\begin{aligned} E_z &= \\frac{1}{- \\omega \\beta \\epsilon_{zz}} (( \\hat{\\partial}_y \\hat{\\partial}_x H_z -\\hat{\\partial}_x \\hat{\\partial}_y H_z) + \\imath \\omega (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y)) &= \\frac{1}{\\imath \\beta \\epsilon_{zz}} (\\hat{\\partial}_x \\epsilon_{xx} E_x + \\hat{\\partial}_y \\epsilon{yy} E_y) \\end{aligned} \\]Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z) It should satisfy operator_e() @ e_xy == wavenumber**2 * e_xy
dxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredReturns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.e2h","title":"e2h","text":"e2h(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Returns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield slice.
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
mu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h2e","title":"h2e","text":"h2e(\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n) -> sparse.sparray\n Returns an operator which, when applied to a vectorized H eigenfield, produces the vectorized E eigenfield slice.
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredReturns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_e","title":"curl_e","text":"curl_e(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\n Discretized curl operator for use with the waveguide E field slice.
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
dxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.curl_h","title":"curl_h","text":"curl_h(\n wavenumber: complex, dxes: dx_lists2_t\n) -> sparse.sparray\n Discretized curl operator for use with the waveguide H field slice.
Parameters:
Name Type Description Defaultwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
dxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.h_err","title":"h_err","text":"h_err(\n h: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\n Calculates the relative error in the H field
Parameters:
Name Type Description Defaulth vcfdslice Vectorized H field
requiredwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionfloat Relative error norm(A_h @ h) / norm(h).
e_err(\n e: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> float\n Calculates the relative error in the E field
Parameters:
Name Type Description Defaulte vcfdslice Vectorized E field
requiredwavenumber complex Wavenumber assuming fields have z-dependence of exp(-i * wavenumber * z)
omega complex The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionfloat Relative error norm(A_e @ e) / norm(e).
sensitivity(\n e_norm: vcfdslice,\n h_norm: vcfdslice,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> vcfdslice_t\n Given a waveguide structure (dxes, epsilon, mu) and mode fields (e_norm, h_norm, wavenumber, omega), calculates the sensitivity of the wavenumber \\(\\beta\\) to changes in the dielectric structure \\(\\epsilon\\).
The output is a vector of the same size as vec(epsilon), with each element specifying the sensitivity of wavenumber to changes in the corresponding element in vec(epsilon), i.e.
An adjoint approach is used to calculate the sensitivity; the derivation is provided here:
Starting with the eigenvalue equation
\\[\\beta^2 E_{xy} = A_E E_{xy}\\]where \\(A_E\\) is the waveguide operator from operator_e(), and \\(E_{xy} = \\begin{bmatrix} E_x \\\\ E_y \\end{bmatrix}\\), we can differentiate with respect to one of the \\(\\epsilon\\) elements (i.e. at one Yee grid point), \\(\\epsilon_i\\):
We then multiply by \\(H_{yx}^\\star = \\begin{bmatrix}H_y^\\star \\\\ -H_x^\\star \\end{bmatrix}\\) from the left:
\\[ (2 \\beta) \\partial_{\\epsilon_i}(\\beta) H_{yx}^\\star E_{xy} + \\beta^2 H_{yx}^\\star \\partial_{\\epsilon_i} E_{xy} = H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} + H_{yx}^\\star A_E \\partial_{\\epsilon_i} E_{xy} \\]However, \\(H_{yx}^\\star\\) is actually a left-eigenvector of \\(A_E\\). This can be verified by inspecting the form of operator_h (\\(A_H\\)) and comparing its conjugate transpose to operator_e (\\(A_E\\)). Also, note \\(H_{yx}^\\star \\cdot E_{xy} = H^\\star \\times E\\) recalls the mode orthogonality relation. See doi:10.5194/ars-9-85-201 for a similar approach. Therefore,
and we can simplify to
\\[ \\partial_{\\epsilon_i}(\\beta) = \\frac{1}{2 \\beta} \\frac{H_{yx}^\\star \\partial_{\\epsilon_i}(A_E) E_{xy} }{H_{yx}^\\star E_{xy}} \\]This expression can be quickly calculated for all \\(i\\) by writing out the various terms of \\(\\partial_{\\epsilon_i} A_E\\) and recognizing that the vector-matrix-vector products (i.e. scalars) \\(sens_i = \\vec{v}_{left} \\partial_{\\epsilon_i} (\\epsilon_{xyz}) \\vec{v}_{right}\\), indexed by \\(i\\), can be expressed as elementwise multiplications \\(\\vec{sens} = \\vec{v}_{left} \\star \\vec{v}_{right}\\)
Parameters:
Name Type Description Defaulte_norm vcfdslice Normalized, vectorized E_xyz field for the mode. E.g. as returned by normalized_fields_e.
h_norm vcfdslice Normalized, vectorized H_xyz field for the mode. E.g. as returned by normalized_fields_e.
wavenumber complex Propagation constant for the mode. The z-axis is assumed to be continuous (i.e. without numerical dispersion).
requiredomega complex The angular frequency of the system.
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionvcfdslice_t Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: complex,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\n Given a 2D region, attempts to solve for the eigenmode with the specified mode numbers.
Parameters:
Name Type Description Defaultmode_numbers Sequence[int] List of 0-indexed mode numbers to solve for
requiredomega complex Angular frequency of the simulation
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
epsilon vfdslice Dielectric constant
requiredmu vfdslice | None Magnetic permeability (default 1 everywhere)
None mode_margin int The eigensolver will actually solve for (max(mode_number) + mode_margin) modes, but only return the target mode. Increasing this value can improve the solver's ability to find the correct mode. Default 2.
2 Returns:
Name Type Descriptione_xys NDArray[complex128] NDArray of vfdfield_t specifying fields. First dimension is mode number.
wavenumbers NDArray[complex128] list of wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdfield2_t, complex]\n Wrapper around solve_modes() that solves for a single mode.
Parameters:
Name Type Description Defaultmode_number int 0-indexed mode number to solve for
required*args Any passed to solve_modes()
() **kwargs Any passed to solve_modes()
{} Returns:
Type Descriptiontuple[vcfdfield2_t, complex] (e_xy, wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_2d.inner_product","title":"inner_product","text":"inner_product(\n e1: vcfdfield2,\n h2: vcfdfield2,\n dxes: dx_lists2_t,\n prop_phase: float = 0,\n conj_h: bool = False,\n trapezoid: bool = False,\n) -> complex\n Compute the discrete waveguide overlap / Poynting inner product.
This is the 2D transverse integral corresponding to the time-averaged longitudinal Poynting flux,
\\[ \\frac{1}{2}\\int (E_x H_y - E_y H_x) \\, dx \\, dy \\]with the Yee-grid staggering and optional propagation-phase adjustment used by the waveguide helpers in this module.
Parameters:
Name Type Description Defaulte1 vcfdfield2 Vectorized electric field, typically from exy2e(...) or normalized_fields_e(...).
h2 vcfdfield2 Vectorized magnetic field, typically from hxy2h(...), exy2h(...), or one of the normalization helpers.
dxes dx_lists2_t Two-dimensional Yee-grid spacing lists [dx_e, dx_h].
prop_phase float Phase advance over one propagation cell. This is used to shift the H field into the same longitudinal reference plane as the E field.
0 conj_h bool Whether to conjugate h2 before forming the overlap. Use True for the usual time-averaged power normalization.
False trapezoid bool Whether to use trapezoidal quadrature instead of the default rectangular Yee-cell sum.
False Returns:
Type Descriptioncomplex Complex overlap / longitudinal power integral.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d","title":"meanas.fdfd.waveguide_3d","text":"Tools for working with waveguide modes in 3D domains.
This module relies heavily on waveguide_2d and mostly just transforms its parameters into 2D equivalents and expands the results back into 3D.
The intended workflow is:
solve_mode(...).compute_source(...).compute_overlap_e(...) for port readout.polarity is part of the public convention throughout this module:
+1 means forward propagation toward increasing index along axis-1 means backward propagation toward decreasing index along axisThat same convention controls which side of the selected slice is used for the overlap window and how the expanded field is phased.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> dict[str, complex | NDArray[complexfloating]]\n Given a 3D grid, selects a slice from the grid and attempts to solve for an eigenmode propagating through that slice.
Parameters:
Name Type Description Defaultmode_number int Number of the mode, 0-indexed
requiredomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
axis int Propagation axis (0=x, 1=y, 2=z)
requiredpolarity int Propagation direction (+1 for +ve, -1 for -ve)
requiredslices Sequence[slice] epsilon[tuple(slices)] is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] must select exactly one item.
epsilon fdfield Dielectric constant
requiredmu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptiondict[str, complex | NDArray[complexfloating]] Dictionary containing:
dict[str, complex | NDArray[complexfloating]] E: full-grid electric field for the solved modedict[str, complex | NDArray[complexfloating]] H: full-grid magnetic field for the solved modedict[str, complex | NDArray[complexfloating]] wavenumber: propagation constant corrected for the discretized propagation axisdict[str, complex | NDArray[complexfloating]] wavenumber_2d: propagation constant of the reduced 2D eigenproblemThe returned fields are normalized through the waveguide_2d normalization convention before being expanded back to 3D.
compute_source(\n E: cfdfield,\n wavenumber: complex,\n omega: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n epsilon: fdfield,\n mu: fdfield | None = None,\n) -> cfdfield_t\n Given an eigenmode obtained by solve_mode, returns the current source distribution necessary to position a unidirectional source at the slice location.
Parameters:
Name Type Description DefaultE cfdfield E-field of the mode
requiredwavenumber complex Wavenumber of the mode
requiredomega complex Angular frequency of the simulation
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
axis int Propagation axis (0=x, 1=y, 2=z)
requiredpolarity int Propagation direction (+1 for +ve, -1 for -ve)
requiredslices Sequence[slice] epsilon[tuple(slices)] is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.
mu fdfield | None Magnetic permeability (default 1 everywhere)
None Returns:
Type Descriptioncfdfield_t J distribution for a one-sided electric-current source.
The source is built from the expanded mode field and a boundary-source operator. The resulting current is intended to be injected with the same sign convention used elsewhere in the package:
E -= dt * J / epsilon
compute_overlap_e(\n E: cfdfield_t,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n omega: float,\n) -> cfdfield_t\n Build an overlap field for projecting another 3D electric field onto a mode.
The returned field is intended for the discrete overlap expression
\\[ \\sum \\mathrm{overlap\\_e} \\; E_\\mathrm{other}^* \\]where the sum is over the full Yee-grid field storage.
The construction uses a two-cell window immediately upstream of the selected slice:
polarity=+1, the two cells just before slices[axis].startpolarity=-1, the two cells just after slices[axis].stopThe window is clipped to the simulation domain if necessary. A clipped but non-empty window raises RuntimeWarning; an empty window raises ValueError.
The derivation below assumes reflection symmetry and the standard waveguide overlap relation involving
\\[ \\int ((E \\times H_\\mathrm{mode}) + (E_\\mathrm{mode} \\times H)) \\cdot dn. \\]E x H_mode + E_mode x H -> Ex Hmy - EyHmx + Emx Hy - Emy Hx (Z-prop) Ex we/B Emx + Ex i/B dy Hmz - Ey (-we/B Emy) - Ey i/B dx Hmz we/B (Ex Emx + Ey Emy) + i/B (Ex dy Hmz - Ey dx Hmz) we/B (Ex Emx + Ey Emy) + i/B (Ex dy (dx Emy - dy Emx) - Ey dx (dx Emy - dy Emx)) we/B (Ex Emx + Ey Emy) + i/B (Ex dy dx Emy - Ex dy dy Emx - Ey dx dx Emy - Ey dx dy Emx)
Ex j/wu (-jB Emx - dx Emz) - Ey j/wu (dy Emz + jB Emy) B/wu (Ex Emx + Ey Emy) - j/wu (Ex dx Emz + Ey dy Emz)
Parameters:
Name Type Description DefaultE cfdfield_t E-field of the mode
requiredwavenumber complex Wavenumber of the mode
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
axis int Propagation axis (0=x, 1=y, 2=z)
requiredpolarity int Propagation direction (+1 for +ve, -1 for -ve)
requiredslices Sequence[slice] epsilon[tuple(slices)] is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.
Returns:
Type Descriptioncfdfield_t overlap_e normalized so that numpy.sum(overlap_e * E.conj()) == 1
cfdfield_t over the retained overlap window.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_3d.expand_e","title":"expand_e","text":"expand_e(\n E: cfdfield,\n wavenumber: complex,\n dxes: dx_lists_t,\n axis: int,\n polarity: int,\n slices: Sequence[slice],\n) -> cfdfield_t\n Given an eigenmode obtained by solve_mode, expands the E-field from the 2D slice where the mode was calculated to the entire domain (along the propagation axis). This assumes the epsilon cross-section remains constant throughout the entire domain; it is up to the caller to truncate the expansion to any regions where it is valid.
Parameters:
Name Type Description DefaultE cfdfield E-field of the mode
requiredwavenumber complex Wavenumber of the mode
requireddxes dx_lists_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types
axis int Propagation axis (0=x, 1=y, 2=z)
requiredpolarity int Propagation direction (+1 for +ve, -1 for -ve)
requiredslices Sequence[slice] epsilon[tuple(slices)] is used to select the portion of the grid to use as the waveguide cross-section. slices[axis] should select only one item.
Returns:
Type Descriptioncfdfield_t E, with the original field expanded along the specified axis.
This helper assumes that the waveguide cross-section remains constant along the propagation axis and applies the phase factor
\\[ e^{-i \\, \\mathrm{polarity} \\, wavenumber \\, \\Delta z} \\]to each copied slice.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl","title":"meanas.fdfd.waveguide_cyl","text":"Operators and helper functions for cylindrical waveguides with unchanging cross-section.
Waveguide operator is derived according to 10.1364/OL.33.001848.
As in waveguide_2d, the propagation dependence is separated from the transverse solve. Here the propagation coordinate is the bend angle \\theta, and the fields are assumed to have the form
where m is the angular wavenumber returned by solve_mode(s). It is often convenient to introduce the corresponding linear wavenumber
so that the cylindrical problem resembles the straight-waveguide problem with additional metric factors.
Those metric factors live on the staggered radial Yee grids. If the left edge of the computational window is at r = r_{\\min}, define the electric-grid and magnetic-grid radial sample locations by
and from them the diagonal metric matrices
\\[ \\begin{aligned} T_a &= \\operatorname{diag}(r_a / r_{\\min}), \\\\ T_b &= \\operatorname{diag}(r_b / r_{\\min}). \\end{aligned} \\]With the same forward/backward derivative notation used in waveguide_2d, the coordinate-transformed discrete curl equations used here are
The first three equations are the cylindrical analogue of the straight-guide relations for H_r, H_y, and H_z. The next two are the metric-weighted versions of the straight-guide identities for \\imath \\beta H_y and \\imath \\beta H_r, and the last equation plays the same role as the longitudinal E_z reconstruction in waveguide_2d.
Following the same elimination steps as in waveguide_2d, apply \\imath \\beta \\tilde{\\partial}_r and \\imath \\beta \\tilde{\\partial}_y to the equation for E_z, substitute for \\imath \\beta H_r and \\imath \\beta H_y, and then eliminate H_z with
This yields the transverse electric eigenproblem implemented by cylindrical_operator(...):
Since \\beta = m / r_{\\min}, the solver implemented in this file returns the angular wavenumber m, while the operator itself is most naturally written in terms of the linear quantity \\beta. The helpers below reconstruct the full field components from the solved transverse eigenvector and then normalize the mode to unit forward power with the same discrete longitudinal Poynting inner product used by waveguide_2d.
As in the straight-waveguide case, all functions here assume a 2D grid:
dxes = [[[dr_e_0, dr_e_1, ...], [dy_e_0, ...]], [[dr_h_0, ...], [dy_h_0, ...]]].
cylindrical_operator(\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n) -> sparse.sparray\n Cylindrical coordinate waveguide operator of the form
\\[ (\\omega^2 \\begin{bmatrix} T_b T_b \\mu_{yy} \\epsilon_{xx} & 0 \\\\ 0 & T_a T_a \\mu_{xx} \\epsilon_{yy} \\end{bmatrix} + \\begin{bmatrix} -T_b \\mu_{yy} \\hat{\\partial}_y \\\\ T_a \\mu_{xx} \\hat{\\partial}_x \\end{bmatrix} T_b \\mu_{zz}^{-1} \\begin{bmatrix} -\\tilde{\\partial}_y & \\tilde{\\partial}_x \\end{bmatrix} + \\begin{bmatrix} \\tilde{\\partial}_x \\\\ \\tilde{\\partial}_y \\end{bmatrix} T_a \\epsilon_{zz}^{-1} \\begin{bmatrix} \\hat{\\partial}_x T_b \\epsilon_{xx} & \\hat{\\partial}_y T_a \\epsilon_{yy} \\end{bmatrix}) \\begin{bmatrix} E_r \\\\ E_y \\end{bmatrix} \\]for use with a field vector of the form [E_r, E_y].
This operator can be used to form an eigenvalue problem of the form A @ [E_r, E_y] = beta**2 * [E_r, E_y]
which can then be solved for the eigenmodes of the system (an exp(-i * angular_wavenumber * theta) theta-dependence is assumed for the fields, with beta = angular_wavenumber / rmin).
(NOTE: See module docs and 10.1364/OL.33.001848)
Parameters:
Name Type Description Defaultomega float The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
epsilon vfdslice Vectorized dielectric constant grid
requiredrmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type Descriptionsparray Sparse matrix representation of the operator
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_modes","title":"solve_modes","text":"solve_modes(\n mode_numbers: Sequence[int],\n omega: float,\n dxes: dx_lists2_t,\n epsilon: vfdslice,\n rmin: float,\n mode_margin: int = 2,\n) -> tuple[\n NDArray[numpy.complex128], NDArray[numpy.complex128]\n]\n Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode of the bent waveguide with the specified mode number.
Parameters:
Name Type Description Defaultmode_number Number of the mode, 0-indexed
requiredomega float Angular frequency of the simulation
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types. The first coordinate is assumed to be r, the second is y.
requiredepsilon vfdslice Dielectric constant
requiredrmin float Radius of curvature for the simulation. This should be the minimum value of r within the simulation domain.
requiredReturns:
Name Type Descriptione_xys NDArray[complex128] NDArray of vfdfield_t specifying fields. First dimension is mode number.
angular_wavenumbers NDArray[complex128] list of wavenumbers in 1/rad units.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.solve_mode","title":"solve_mode","text":"solve_mode(\n mode_number: int, *args: Any, **kwargs: Any\n) -> tuple[vcfdslice, complex]\n Wrapper around solve_modes() that solves for a single mode.
Parameters:
Name Type Description Defaultmode_number int 0-indexed mode number to solve for
required*args Any passed to solve_modes()
() **kwargs Any passed to solve_modes()
{} Returns:
Type Descriptiontuple[vcfdslice, complex] (e_xy, angular_wavenumber)
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.linear_wavenumbers","title":"linear_wavenumbers","text":"linear_wavenumbers(\n e_xys: list[vcfdfield2_t],\n angular_wavenumbers: ArrayLike,\n epsilon: vfdslice,\n dxes: dx_lists2_t,\n rmin: float,\n) -> NDArray[numpy.complex128]\n Calculate linear wavenumbers (1/distance) based on angular wavenumbers (1/rad) and the mode's energy distribution.
Parameters:
Name Type Description Defaulte_xys list[vcfdfield2_t] Vectorized mode fields with shape (num_modes, 2 * x *y)
requiredangular_wavenumbers ArrayLike Wavenumbers assuming fields have theta-dependence of exp(-i * angular_wavenumber * theta). They should satisfy operator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xy
epsilon vfdslice Vectorized dielectric constant grid with shape (3, x, y)
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type DescriptionNDArray[complex128] NDArray containing the calculated linear (1/distance) wavenumbers
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2h","title":"exy2h","text":"exy2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Operator which transforms the vector e_xy containing the vectorized E_r and E_y fields, into a vectorized H containing all three H components
Parameters:
Name Type Description Defaultangular_wavenumber complex Wavenumber assuming fields have theta-dependence of exp(-i * angular_wavenumber * theta). It should satisfy operator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xy
omega float The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredepsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.exy2e","title":"exy2e","text":"exy2e(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n) -> sparse.sparray\n Operator which transforms the vector e_xy containing the vectorized E_r and E_y fields, into a vectorized E containing all three E components
Unlike the straight waveguide case, the H_z components do not cancel and must be calculated from E_r and E_y in order to then calculate E_z.
Parameters:
Name Type Description Defaultangular_wavenumber complex Wavenumber assuming fields have theta-dependence of exp(-i * angular_wavenumber * theta). It should satisfy operator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xy
omega float The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredepsilon vfdslice Vectorized dielectric constant grid
requiredReturns:
Type Descriptionsparray Sparse matrix representing the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.e2h","title":"e2h","text":"e2h(\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n mu: vfdslice | None = None,\n) -> sparse.sparray\n Returns an operator which, when applied to a vectorized E eigenfield, produces the vectorized H eigenfield.
This operator is created directly from the initial coordinate-transformed equations: $$ \\begin{aligned} -\\imath \\omega \\mu_{rr} H_r &= \\tilde{\\partial}y E_z + \\imath \\beta T_a^{-1} E_y, \\ -\\imath \\omega \\mu E_r - T_b^{-1} \\tilde{\\partial}} H_y &= -\\imath \\beta T_b^{-1r (T_a E_z), \\ -\\imath \\omega \\mu_y E_r, \\end{aligned} $$} H_z &= \\tilde{\\partial}_r E_y - \\tilde{\\partial
Parameters:
Name Type Description Defaultangular_wavenumber complex Wavenumber assuming fields have theta-dependence of exp(-i * angular_wavenumber * theta). It should satisfy operator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xy
omega float The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None Returns:
Type Descriptionsparray Sparse matrix representation of the operator.
"},{"location":"api/waveguides/#meanas.fdfd.waveguide_cyl.dxes2T","title":"dxes2T","text":"dxes2T(\n dxes: dx_lists2_t, rmin: float\n) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]\n Construct the cylindrical metric matrices \\(T_a\\) and \\(T_b\\).
T_a is sampled on the E-grid radial locations, while T_b is sampled on the staggered H-grid radial locations. These are the diagonal matrices that convert the straight-waveguide algebra into its cylindrical counterpart.
Parameters:
Name Type Description Defaultdxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredReturns:
Type Descriptiontuple[NDArray[float64], NDArray[float64]] Sparse diagonal matrices (T_a, T_b).
normalized_fields_e(\n e_xy: vcfdfield2,\n angular_wavenumber: complex,\n omega: float,\n dxes: dx_lists2_t,\n rmin: float,\n epsilon: vfdslice,\n mu: vfdslice | None = None,\n prop_phase: float = 0,\n) -> tuple[vcfdslice_t, vcfdslice_t]\n Given a vector e_xy containing the vectorized E_r and E_y fields, returns normalized, vectorized E and H fields for the system.
Parameters:
Name Type Description Defaulte_xy vcfdfield2 Vector containing E_r and E_y fields
requiredangular_wavenumber complex Wavenumber assuming fields have theta-dependence of exp(-i * angular_wavenumber * theta). It should satisfy operator_e() @ e_xy == (angular_wavenumber / rmin) ** 2 * e_xy
omega float The angular frequency of the system
requireddxes dx_lists2_t Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types (2D)
rmin float Radius at the left edge of the simulation domain (at minimum 'x')
requiredepsilon vfdslice Vectorized dielectric constant grid
requiredmu vfdslice | None Vectorized magnetic permeability grid (default 1 everywhere)
None prop_phase float Phase shift (dz * corrected_wavenumber) over 1 cell in propagation direction. Default 0 (continuous propagation direction, i.e. dz->0).
0 Returns:
Type Descriptionvcfdslice_t (e, h), where each field is vectorized, normalized,
vcfdslice_t and contains all three vector components.
NotesThe normalization step is delegated to _normalized_fields(...), which enforces unit forward power under the discrete inner product
The angular wavenumber m is first converted into the full three-component fields, then the overall complex phase and sign are fixed so the result is reproducible for symmetric modes.