[docs] expand API and derivation docs

This commit is contained in:
Jan Petykiewicz 2026-04-18 14:24:18 -07:00
commit 5e95d66a7e
12 changed files with 608 additions and 127 deletions

View file

@ -175,8 +175,6 @@ if the result is introduced into a space with a discretized z-axis.
"""
# TODO update module docs
from typing import Any
from collections.abc import Sequence
import numpy
@ -339,7 +337,7 @@ def normalized_fields_e(
mu: vfdslice | None = None,
prop_phase: float = 0,
) -> tuple[vcfdslice_t, vcfdslice_t]:
"""
r"""
Given a vector `e_xy` containing the vectorized E_x and E_y fields,
returns normalized, vectorized E and H fields for the system.
@ -357,6 +355,21 @@ def normalized_fields_e(
Returns:
`(e, h)`, where each field is vectorized, normalized,
and contains all three vector components.
Notes:
`e_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.
"""
e = exy2e(wavenumber=wavenumber, dxes=dxes, epsilon=epsilon) @ e_xy
h = exy2h(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ e_xy
@ -374,7 +387,7 @@ def normalized_fields_h(
mu: vfdslice | None = None,
prop_phase: float = 0,
) -> tuple[vcfdslice_t, vcfdslice_t]:
"""
r"""
Given a vector `h_xy` containing the vectorized H_x and H_y fields,
returns normalized, vectorized E and H fields for the system.
@ -392,6 +405,13 @@ def normalized_fields_h(
Returns:
`(e, h)`, where each field is vectorized, normalized,
and contains all three vector components.
Notes:
This 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.
"""
e = hxy2e(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ h_xy
h = hxy2h(wavenumber=wavenumber, dxes=dxes, mu=mu) @ h_xy
@ -409,7 +429,25 @@ def _normalized_fields(
mu: vfdslice | None = None,
prop_phase: float = 0,
) -> tuple[vcfdslice_t, vcfdslice_t]:
# TODO documentation
r"""
Normalize a reconstructed waveguide mode to unit forward power.
The eigenproblem solved by `solve_mode(s)` determines only the mode shape and
propagation constant. The overall complex amplitude and sign are still free.
This helper fixes those remaining degrees of freedom in two steps:
1. Compute the discrete longitudinal Poynting flux with
`inner_product(e, h, conj_h=True)`, including the half-cell longitudinal
phase adjustment controlled by `prop_phase`.
2. Multiply both fields by a scalar chosen so that the real forward power is
`1`, then choose a reproducible phase/sign representative by making a
dominant-energy sample real and using a weighted quadrant sum to break
mirror-symmetry ties.
The sign heuristic is intentionally pragmatic rather than fundamental: it is
only there to make downstream tests and source/overlap construction choose a
consistent representative when the physical mode is symmetric.
"""
shape = [s.size for s in dxes[0]]
# Find time-averaged Sz and normalize to it
@ -921,7 +959,7 @@ def solve_mode(
return vcfdfield2_t(e_xys[0]), wavenumbers[0]
def inner_product( # TODO documentation
def inner_product(
e1: vcfdfield2,
h2: vcfdfield2,
dxes: dx_lists2_t,
@ -929,6 +967,36 @@ def inner_product( # TODO documentation
conj_h: bool = False,
trapezoid: bool = False,
) -> complex:
r"""
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.
Args:
e1: Vectorized electric field, typically from `exy2e(...)` or
`normalized_fields_e(...)`.
h2: Vectorized magnetic field, typically from `hxy2h(...)`,
`exy2h(...)`, or one of the normalization helpers.
dxes: Two-dimensional Yee-grid spacing lists `[dx_e, dx_h]`.
prop_phase: Phase advance over one propagation cell. This is used to
shift the H field into the same longitudinal reference plane as the
E field.
conj_h: Whether to conjugate `h2` before forming the overlap. Use
`True` for the usual time-averaged power normalization.
trapezoid: Whether to use trapezoidal quadrature instead of the default
rectangular Yee-cell sum.
Returns:
Complex overlap / longitudinal power integral.
"""
shape = [s.size for s in dxes[0]]
@ -951,5 +1019,3 @@ def inner_product( # TODO documentation
Sz_b = E1[1] * H2[0] * dxes_real[0][0][:, None] * dxes_real[1][1][None, :]
Sz = 0.5 * (Sz_a.sum() - Sz_b.sum())
return Sz