Rework field types, use sparse arrays instead of matrices, rework eme arg naming, improve type annotations and linter cleanup
This commit is contained in:
parent
b7ad5dea2b
commit
b486fa325b
20 changed files with 571 additions and 433 deletions
|
|
@ -106,7 +106,7 @@ import scipy.optimize
|
|||
from scipy.linalg import norm
|
||||
import scipy.sparse.linalg as spalg
|
||||
|
||||
from ..fdmath import fdfield_t, cfdfield_t
|
||||
from ..fdmath import fdfield, cfdfield, cfdfield_t
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -183,8 +183,8 @@ def generate_kmn(
|
|||
def maxwell_operator(
|
||||
k0: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None
|
||||
) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]:
|
||||
"""
|
||||
Generate the Maxwell operator
|
||||
|
|
@ -276,7 +276,7 @@ def maxwell_operator(
|
|||
def hmn_2_exyz(
|
||||
k0: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t,
|
||||
epsilon: fdfield,
|
||||
) -> Callable[[NDArray[numpy.complex128]], cfdfield_t]:
|
||||
"""
|
||||
Generate an operator which converts a vectorized spatial-frequency-space
|
||||
|
|
@ -308,7 +308,8 @@ def hmn_2_exyz(
|
|||
- m * hin_n) * k_mag
|
||||
|
||||
# divide by epsilon
|
||||
return numpy.moveaxis(ifftn(d_xyz, axes=range(3)) / epsilon, 3, 0)
|
||||
exyz = numpy.moveaxis(ifftn(d_xyz, axes=range(3)) / epsilon, 3, 0)
|
||||
return cfdfield_t(exyz)
|
||||
|
||||
return operator
|
||||
|
||||
|
|
@ -316,7 +317,7 @@ def hmn_2_exyz(
|
|||
def hmn_2_hxyz(
|
||||
k0: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t
|
||||
epsilon: fdfield,
|
||||
) -> Callable[[NDArray[numpy.complex128]], cfdfield_t]:
|
||||
"""
|
||||
Generate an operator which converts a vectorized spatial-frequency-space
|
||||
|
|
@ -343,8 +344,8 @@ def hmn_2_hxyz(
|
|||
def operator(h: NDArray[numpy.complex128]) -> cfdfield_t:
|
||||
hin_m, hin_n = (hi.reshape(shape) for hi in numpy.split(h, 2))
|
||||
h_xyz = (m * hin_m
|
||||
+ n * hin_n) # noqa: E128
|
||||
return numpy.array([ifftn(hi) for hi in numpy.moveaxis(h_xyz, 3, 0)])
|
||||
+ n * hin_n)
|
||||
return cfdfield_t(numpy.array([ifftn(hi) for hi in numpy.moveaxis(h_xyz, 3, 0)]))
|
||||
|
||||
return operator
|
||||
|
||||
|
|
@ -352,8 +353,8 @@ def hmn_2_hxyz(
|
|||
def inverse_maxwell_operator_approx(
|
||||
k0: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]:
|
||||
"""
|
||||
Generate an approximate inverse of the Maxwell operator,
|
||||
|
|
@ -440,8 +441,8 @@ def find_k(
|
|||
tolerance: float,
|
||||
direction: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
band: int = 0,
|
||||
k_bounds: tuple[float, float] = (0, 0.5),
|
||||
k_guess: float | None = None,
|
||||
|
|
@ -508,8 +509,8 @@ def eigsolve(
|
|||
num_modes: int,
|
||||
k0: ArrayLike,
|
||||
G_matrix: ArrayLike,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
tolerance: float = 1e-7,
|
||||
max_iters: int = 10000,
|
||||
reset_iters: int = 100,
|
||||
|
|
@ -649,7 +650,7 @@ def eigsolve(
|
|||
|
||||
def Qi_func(theta: float, Qi_memo=Qi_memo, ZtZ=ZtZ, DtD=DtD, symZtD=symZtD) -> float: # noqa: ANN001
|
||||
if Qi_memo[0] == theta:
|
||||
return cast(float, Qi_memo[1])
|
||||
return cast('float', Qi_memo[1])
|
||||
|
||||
c = numpy.cos(theta)
|
||||
s = numpy.sin(theta)
|
||||
|
|
@ -668,8 +669,8 @@ def eigsolve(
|
|||
else:
|
||||
raise Exception('Inexplicable singularity in trace_func') from err
|
||||
Qi_memo[0] = theta
|
||||
Qi_memo[1] = cast(float, Qi)
|
||||
return cast(float, Qi)
|
||||
Qi_memo[1] = cast('float', Qi)
|
||||
return cast('float', Qi)
|
||||
|
||||
def trace_func(theta: float, ZtAZ=ZtAZ, DtAD=DtAD, symZtAD=symZtAD) -> float: # noqa: ANN001
|
||||
c = numpy.cos(theta)
|
||||
|
|
@ -801,7 +802,12 @@ def _symmetrize(A: NDArray[numpy.complex128]) -> NDArray[numpy.complex128]:
|
|||
|
||||
|
||||
|
||||
def inner_product(eL, hL, eR, hR) -> complex:
|
||||
def inner_product(
|
||||
eL: cfdfield,
|
||||
hL: cfdfield,
|
||||
eR: cfdfield,
|
||||
hR: cfdfield,
|
||||
) -> complex:
|
||||
# assumes x-axis propagation
|
||||
|
||||
assert numpy.array_equal(eR.shape, hR.shape)
|
||||
|
|
@ -829,7 +835,12 @@ def inner_product(eL, hL, eR, hR) -> complex:
|
|||
return eRxhL_x.sum() - eLxhR_x.sum()
|
||||
|
||||
|
||||
def trq(eI, hI, eO, hO) -> tuple[complex, complex]:
|
||||
def trq(
|
||||
eI: cfdfield,
|
||||
hI: cfdfield,
|
||||
eO: cfdfield,
|
||||
hO: cfdfield,
|
||||
) -> tuple[complex, complex]:
|
||||
pp = inner_product(eO, hO, eI, hI)
|
||||
pn = inner_product(eO, hO, eI, -hI)
|
||||
np = inner_product(eO, -hO, eI, hI)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,29 @@
|
|||
from collections.abc import Sequence
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
from scipy import sparse
|
||||
|
||||
from ..fdmath import vec, unvec, dx_lists_t, vfdfield_t, vcfdfield_t
|
||||
from ..fdmath import dx_lists2_t, vcfdfield2
|
||||
from .waveguide_2d import inner_product
|
||||
|
||||
|
||||
def get_tr(ehL, wavenumbers_L, ehR, wavenumbers_R, dxes: dx_lists_t):
|
||||
def get_tr(
|
||||
ehLs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_L: Sequence[complex],
|
||||
ehRs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_R: Sequence[complex],
|
||||
dxes: dx_lists2_t,
|
||||
) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
|
||||
nL = len(wavenumbers_L)
|
||||
nR = len(wavenumbers_R)
|
||||
A12 = numpy.zeros((nL, nR), dtype=complex)
|
||||
A21 = numpy.zeros((nL, nR), dtype=complex)
|
||||
B11 = numpy.zeros((nL,), dtype=complex)
|
||||
for ll in range(nL):
|
||||
eL, hL = ehL[ll]
|
||||
eL, hL = ehLs[ll]
|
||||
B11[ll] = inner_product(eL, hL, dxes=dxes, conj_h=False)
|
||||
for rr in range(nR):
|
||||
eR, hR = ehR[rr]
|
||||
eR, hR = ehRs[rr]
|
||||
A12[ll, rr] = inner_product(eL, hR, dxes=dxes, conj_h=False) # TODO optimize loop?
|
||||
A21[ll, rr] = inner_product(eR, hL, dxes=dxes, conj_h=False)
|
||||
|
||||
|
|
@ -32,9 +41,15 @@ def get_tr(ehL, wavenumbers_L, ehR, wavenumbers_R, dxes: dx_lists_t):
|
|||
return tt, rr
|
||||
|
||||
|
||||
def get_abcd(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, **kwargs):
|
||||
t12, r12 = get_tr(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, **kwargs)
|
||||
t21, r21 = get_tr(eR_xys, wavenumbers_R, eL_xys, wavenumbers_L, **kwargs)
|
||||
def get_abcd(
|
||||
ehLs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_L: Sequence[complex],
|
||||
ehRs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_R: Sequence[complex],
|
||||
**kwargs,
|
||||
) -> sparse.sparray:
|
||||
t12, r12 = get_tr(ehLs, wavenumbers_L, ehRs, wavenumbers_R, **kwargs)
|
||||
t21, r21 = get_tr(ehRs, wavenumbers_R, ehLs, wavenumbers_L, **kwargs)
|
||||
t21i = numpy.linalg.pinv(t21)
|
||||
A = t12 - r21 @ t21i @ r12
|
||||
B = r21 @ t21i
|
||||
|
|
@ -44,15 +59,16 @@ def get_abcd(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, **kwargs):
|
|||
|
||||
|
||||
def get_s(
|
||||
eL_xys,
|
||||
wavenumbers_L,
|
||||
eR_xys,
|
||||
wavenumbers_R,
|
||||
ehLs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_L: Sequence[complex],
|
||||
ehRs: Sequence[Sequence[vcfdfield2]],
|
||||
wavenumbers_R: Sequence[complex],
|
||||
force_nogain: bool = False,
|
||||
force_reciprocal: bool = False,
|
||||
**kwargs):
|
||||
t12, r12 = get_tr(eL_xys, wavenumbers_L, eR_xys, wavenumbers_R, **kwargs)
|
||||
t21, r21 = get_tr(eR_xys, wavenumbers_R, eL_xys, wavenumbers_L, **kwargs)
|
||||
**kwargs,
|
||||
) -> NDArray[numpy.complex128]:
|
||||
t12, r12 = get_tr(ehLs, wavenumbers_L, ehRs, wavenumbers_R, **kwargs)
|
||||
t21, r21 = get_tr(ehRs, wavenumbers_R, ehLs, wavenumbers_L, **kwargs)
|
||||
|
||||
ss = numpy.block([[r12, t12],
|
||||
[t21, r21]])
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
"""
|
||||
Functions for performing near-to-farfield transformation (and the reverse).
|
||||
"""
|
||||
from typing import Any, cast
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, cast, TYPE_CHECKING
|
||||
import numpy
|
||||
from numpy.fft import fft2, fftshift, fftfreq, ifft2, ifftshift
|
||||
from numpy import pi
|
||||
|
||||
from ..fdmath import cfdfield_t
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def near_to_farfield(
|
||||
E_near: cfdfield_t,
|
||||
|
|
@ -63,7 +65,7 @@ def near_to_farfield(
|
|||
padded_size = (2**numpy.ceil(numpy.log2(s))).astype(int)
|
||||
if not hasattr(padded_size, '__len__'):
|
||||
padded_size = (padded_size, padded_size) # type: ignore # checked if sequence
|
||||
padded_shape = cast(Sequence[int], padded_size)
|
||||
padded_shape = cast('Sequence[int]', padded_size)
|
||||
|
||||
En_fft = [fftshift(fft2(fftshift(Eni), s=padded_shape)) for Eni in E_near]
|
||||
Hn_fft = [fftshift(fft2(fftshift(Hni), s=padded_shape)) for Hni in H_near]
|
||||
|
|
@ -172,7 +174,7 @@ def far_to_nearfield(
|
|||
padded_size = (2 ** numpy.ceil(numpy.log2(s))).astype(int)
|
||||
if not hasattr(padded_size, '__len__'):
|
||||
padded_size = (padded_size, padded_size) # type: ignore # checked if sequence
|
||||
padded_shape = cast(Sequence[int], padded_size)
|
||||
padded_shape = cast('Sequence[int]', padded_size)
|
||||
|
||||
k = 2 * pi
|
||||
kxs = fftshift(fftfreq(s[0], 1 / (s[0] * dkx)))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)
|
|||
from collections.abc import Callable
|
||||
import numpy
|
||||
|
||||
from ..fdmath import dx_lists_t, fdfield_t, cfdfield_t, cfdfield_updater_t
|
||||
from ..fdmath import dx_lists_t, cfdfield_t, fdfield, cfdfield, cfdfield_updater_t
|
||||
from ..fdmath.functional import curl_forward, curl_back
|
||||
|
||||
|
||||
|
|
@ -18,8 +18,8 @@ __author__ = 'Jan Petykiewicz'
|
|||
def e_full(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> cfdfield_updater_t:
|
||||
"""
|
||||
Wave operator for use with E-field. See `operators.e_full` for details.
|
||||
|
|
@ -37,13 +37,13 @@ def e_full(
|
|||
ch = curl_back(dxes[1])
|
||||
ce = curl_forward(dxes[0])
|
||||
|
||||
def op_1(e: cfdfield_t) -> cfdfield_t:
|
||||
def op_1(e: cfdfield) -> cfdfield_t:
|
||||
curls = ch(ce(e))
|
||||
return curls - omega ** 2 * epsilon * e
|
||||
return cfdfield_t(curls - omega ** 2 * epsilon * e)
|
||||
|
||||
def op_mu(e: cfdfield_t) -> cfdfield_t:
|
||||
def op_mu(e: cfdfield) -> cfdfield_t:
|
||||
curls = ch(mu * ce(e)) # type: ignore # mu = None ok because we don't return the function
|
||||
return curls - omega ** 2 * epsilon * e
|
||||
return cfdfield_t(curls - omega ** 2 * epsilon * e)
|
||||
|
||||
if mu is None:
|
||||
return op_1
|
||||
|
|
@ -53,9 +53,9 @@ def e_full(
|
|||
def eh_full(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
) -> Callable[[cfdfield_t, cfdfield_t], tuple[cfdfield_t, cfdfield_t]]:
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> Callable[[cfdfield, cfdfield], tuple[cfdfield_t, cfdfield_t]]:
|
||||
"""
|
||||
Wave operator for full (both E and H) field representation.
|
||||
See `operators.eh_full`.
|
||||
|
|
@ -73,13 +73,13 @@ def eh_full(
|
|||
ch = curl_back(dxes[1])
|
||||
ce = curl_forward(dxes[0])
|
||||
|
||||
def op_1(e: cfdfield_t, h: cfdfield_t) -> tuple[cfdfield_t, cfdfield_t]:
|
||||
return (ch(h) - 1j * omega * epsilon * e,
|
||||
ce(e) + 1j * omega * h)
|
||||
def op_1(e: cfdfield, h: cfdfield) -> tuple[cfdfield_t, cfdfield_t]:
|
||||
return (cfdfield_t(ch(h) - 1j * omega * epsilon * e),
|
||||
cfdfield_t(ce(e) + 1j * omega * h))
|
||||
|
||||
def op_mu(e: cfdfield_t, h: cfdfield_t) -> tuple[cfdfield_t, cfdfield_t]:
|
||||
return (ch(h) - 1j * omega * epsilon * e,
|
||||
ce(e) + 1j * omega * mu * h) # type: ignore # mu=None ok
|
||||
def op_mu(e: cfdfield, h: cfdfield) -> tuple[cfdfield_t, cfdfield_t]:
|
||||
return (cfdfield_t(ch(h) - 1j * omega * epsilon * e),
|
||||
cfdfield_t(ce(e) + 1j * omega * mu * h)) # type: ignore # mu=None ok
|
||||
|
||||
if mu is None:
|
||||
return op_1
|
||||
|
|
@ -89,7 +89,7 @@ def eh_full(
|
|||
def e2h(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: fdfield_t | None = None,
|
||||
mu: fdfield | None = None,
|
||||
) -> cfdfield_updater_t:
|
||||
"""
|
||||
Utility operator for converting the `E` field into the `H` field.
|
||||
|
|
@ -106,11 +106,11 @@ def e2h(
|
|||
"""
|
||||
ce = curl_forward(dxes[0])
|
||||
|
||||
def e2h_1_1(e: cfdfield_t) -> cfdfield_t:
|
||||
return ce(e) / (-1j * omega)
|
||||
def e2h_1_1(e: cfdfield) -> cfdfield_t:
|
||||
return cfdfield_t(ce(e) / (-1j * omega))
|
||||
|
||||
def e2h_mu(e: cfdfield_t) -> cfdfield_t:
|
||||
return ce(e) / (-1j * omega * mu) # type: ignore # mu=None ok
|
||||
def e2h_mu(e: cfdfield) -> cfdfield_t:
|
||||
return cfdfield_t(ce(e) / (-1j * omega * mu)) # type: ignore # mu=None ok
|
||||
|
||||
if mu is None:
|
||||
return e2h_1_1
|
||||
|
|
@ -120,7 +120,7 @@ def e2h(
|
|||
def m2j(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: fdfield_t | None = None,
|
||||
mu: fdfield | None = None,
|
||||
) -> cfdfield_updater_t:
|
||||
"""
|
||||
Utility operator for converting magnetic current `M` distribution
|
||||
|
|
@ -138,13 +138,13 @@ def m2j(
|
|||
"""
|
||||
ch = curl_back(dxes[1])
|
||||
|
||||
def m2j_mu(m: cfdfield_t) -> cfdfield_t:
|
||||
def m2j_mu(m: cfdfield) -> cfdfield_t:
|
||||
J = ch(m / mu) / (-1j * omega) # type: ignore # mu=None ok
|
||||
return J
|
||||
return cfdfield_t(J)
|
||||
|
||||
def m2j_1(m: cfdfield_t) -> cfdfield_t:
|
||||
def m2j_1(m: cfdfield) -> cfdfield_t:
|
||||
J = ch(m) / (-1j * omega)
|
||||
return J
|
||||
return cfdfield_t(J)
|
||||
|
||||
if mu is None:
|
||||
return m2j_1
|
||||
|
|
@ -152,11 +152,11 @@ def m2j(
|
|||
|
||||
|
||||
def e_tfsf_source(
|
||||
TF_region: fdfield_t,
|
||||
TF_region: fdfield,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> cfdfield_updater_t:
|
||||
"""
|
||||
Operator that turns an E-field distribution into a total-field/scattered-field
|
||||
|
|
@ -178,13 +178,13 @@ def e_tfsf_source(
|
|||
# TODO documentation
|
||||
A = e_full(omega, dxes, epsilon, mu)
|
||||
|
||||
def op(e: cfdfield_t) -> cfdfield_t:
|
||||
def op(e: cfdfield) -> cfdfield_t:
|
||||
neg_iwj = A(TF_region * e) - TF_region * A(e)
|
||||
return neg_iwj / (-1j * omega)
|
||||
return cfdfield_t(neg_iwj / (-1j * omega))
|
||||
return op
|
||||
|
||||
|
||||
def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[cfdfield_t, cfdfield_t], cfdfield_t]:
|
||||
def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[cfdfield, cfdfield], cfdfield_t]:
|
||||
r"""
|
||||
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
|
||||
|
|
@ -206,7 +206,7 @@ def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[cfdfield_t, cfdfield_t], c
|
|||
Returns:
|
||||
Function `f` that returns E x H as required for the poynting vector.
|
||||
"""
|
||||
def exh(e: cfdfield_t, h: cfdfield_t) -> cfdfield_t:
|
||||
def exh(e: cfdfield, h: cfdfield) -> cfdfield_t:
|
||||
s = numpy.empty_like(e)
|
||||
ex = e[0] * dxes[0][0][:, None, None]
|
||||
ey = e[1] * dxes[0][1][None, :, None]
|
||||
|
|
@ -217,5 +217,5 @@ def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[cfdfield_t, cfdfield_t], c
|
|||
s[0] = numpy.roll(ey, -1, axis=0) * hz - numpy.roll(ez, -1, axis=0) * hy
|
||||
s[1] = numpy.roll(ez, -1, axis=1) * hx - numpy.roll(ex, -1, axis=1) * hz
|
||||
s[2] = numpy.roll(ex, -1, axis=2) * hy - numpy.roll(ey, -1, axis=2) * hx
|
||||
return s
|
||||
return cfdfield_t(s)
|
||||
return exh
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Sparse matrix operators for use with electromagnetic wave equations.
|
||||
|
||||
These functions return sparse-matrix (`scipy.sparse.spmatrix`) representations of
|
||||
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.
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ The following operators are included:
|
|||
import numpy
|
||||
from scipy import sparse
|
||||
|
||||
from ..fdmath import vec, dx_lists_t, vfdfield_t, vcfdfield_t
|
||||
from ..fdmath import vec, dx_lists_t, vfdfield, vcfdfield
|
||||
from ..fdmath.operators import shift_with_mirror, shift_circ, curl_forward, curl_back
|
||||
|
||||
|
||||
|
|
@ -40,11 +40,11 @@ __author__ = 'Jan Petykiewicz'
|
|||
def e_full(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t | vcfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
pec: vfdfield_t | None = None,
|
||||
pmc: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdfield | vcfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
pec: vfdfield | None = None,
|
||||
pmc: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Wave operator
|
||||
$$ \nabla \times (\frac{1}{\mu} \nabla \times) - \Omega^2 \epsilon $$
|
||||
|
|
@ -77,20 +77,20 @@ def e_full(
|
|||
ce = curl_forward(dxes[0])
|
||||
|
||||
if pec is None:
|
||||
pe = sparse.eye(epsilon.size)
|
||||
pe = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pe = sparse.diags(numpy.where(pec, 0, 1)) # Set pe to (not PEC)
|
||||
pe = sparse.diags_array(numpy.where(pec, 0, 1)) # Set pe to (not PEC)
|
||||
|
||||
if pmc is None:
|
||||
pm = sparse.eye(epsilon.size)
|
||||
pm = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pm = sparse.diags(numpy.where(pmc, 0, 1)) # set pm to (not PMC)
|
||||
pm = sparse.diags_array(numpy.where(pmc, 0, 1)) # set pm to (not PMC)
|
||||
|
||||
e = sparse.diags(epsilon)
|
||||
e = sparse.diags_array(epsilon)
|
||||
if mu is None:
|
||||
m_div = sparse.eye(epsilon.size)
|
||||
m_div = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
m_div = sparse.diags(1 / mu)
|
||||
m_div = sparse.diags_array(1 / mu)
|
||||
|
||||
op = pe @ (ch @ pm @ m_div @ ce - omega**2 * e) @ pe
|
||||
return op
|
||||
|
|
@ -98,7 +98,7 @@ def e_full(
|
|||
|
||||
def e_full_preconditioners(
|
||||
dxes: dx_lists_t,
|
||||
) -> tuple[sparse.spmatrix, sparse.spmatrix]:
|
||||
) -> tuple[sparse.sparray, sparse.sparray]:
|
||||
"""
|
||||
Left and right preconditioners `(Pl, Pr)` for symmetrizing the `e_full` wave operator.
|
||||
|
||||
|
|
@ -118,19 +118,19 @@ def e_full_preconditioners(
|
|||
dxes[1][0][:, None, None] * dxes[1][1][None, :, None] * dxes[0][2][None, None, :]]
|
||||
|
||||
p_vector = numpy.sqrt(vec(p_squared))
|
||||
P_left = sparse.diags(p_vector)
|
||||
P_right = sparse.diags(1 / p_vector)
|
||||
P_left = sparse.diags_array(p_vector)
|
||||
P_right = sparse.diags_array(1 / p_vector)
|
||||
return P_left, P_right
|
||||
|
||||
|
||||
def h_full(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
pec: vfdfield_t | None = None,
|
||||
pmc: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
pec: vfdfield | None = None,
|
||||
pmc: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Wave operator
|
||||
$$ \nabla \times (\frac{1}{\epsilon} \nabla \times) - \omega^2 \mu $$
|
||||
|
|
@ -161,20 +161,20 @@ def h_full(
|
|||
ce = curl_forward(dxes[0])
|
||||
|
||||
if pec is None:
|
||||
pe = sparse.eye(epsilon.size)
|
||||
pe = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pe = sparse.diags(numpy.where(pec, 0, 1)) # set pe to (not PEC)
|
||||
pe = sparse.diags_array(numpy.where(pec, 0, 1)) # set pe to (not PEC)
|
||||
|
||||
if pmc is None:
|
||||
pm = sparse.eye(epsilon.size)
|
||||
pm = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pm = sparse.diags(numpy.where(pmc, 0, 1)) # Set pe to (not PMC)
|
||||
pm = sparse.diags_array(numpy.where(pmc, 0, 1)) # Set pe to (not PMC)
|
||||
|
||||
e_div = sparse.diags(1 / epsilon)
|
||||
e_div = sparse.diags_array(1 / epsilon)
|
||||
if mu is None:
|
||||
m = sparse.eye(epsilon.size)
|
||||
m = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
m = sparse.diags(mu)
|
||||
m = sparse.diags_array(mu)
|
||||
|
||||
A = pm @ (ce @ pe @ e_div @ ch - omega**2 * m) @ pm
|
||||
return A
|
||||
|
|
@ -183,11 +183,11 @@ def h_full(
|
|||
def eh_full(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
pec: vfdfield_t | None = None,
|
||||
pmc: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
pec: vfdfield | None = None,
|
||||
pmc: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Wave operator for `[E, H]` field representation. This operator implements Maxwell's
|
||||
equations without cancelling out either E or H. The operator is
|
||||
|
|
@ -227,35 +227,35 @@ def eh_full(
|
|||
Sparse matrix containing the wave operator.
|
||||
"""
|
||||
if pec is None:
|
||||
pe = sparse.eye(epsilon.size)
|
||||
pe = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pe = sparse.diags(numpy.where(pec, 0, 1)) # set pe to (not PEC)
|
||||
pe = sparse.diags_array(numpy.where(pec, 0, 1)) # set pe to (not PEC)
|
||||
|
||||
if pmc is None:
|
||||
pm = sparse.eye(epsilon.size)
|
||||
pm = sparse.eye_array(epsilon.size)
|
||||
else:
|
||||
pm = sparse.diags(numpy.where(pmc, 0, 1)) # set pm to (not PMC)
|
||||
pm = sparse.diags_array(numpy.where(pmc, 0, 1)) # set pm to (not PMC)
|
||||
|
||||
iwe = pe @ (1j * omega * sparse.diags(epsilon)) @ pe
|
||||
iwe = pe @ (1j * omega * sparse.diags_array(epsilon)) @ pe
|
||||
iwm = 1j * omega
|
||||
if mu is not None:
|
||||
iwm *= sparse.diags(mu)
|
||||
iwm *= sparse.diags_array(mu)
|
||||
iwm = pm @ iwm @ pm
|
||||
|
||||
A1 = pe @ curl_back(dxes[1]) @ pm
|
||||
A2 = pm @ curl_forward(dxes[0]) @ pe
|
||||
|
||||
A = sparse.bmat([[-iwe, A1],
|
||||
[A2, iwm]])
|
||||
A = sparse.block_array([[-iwe, A1],
|
||||
[A2, iwm]])
|
||||
return A
|
||||
|
||||
|
||||
def e2h(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
pmc: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
mu: vfdfield | None = None,
|
||||
pmc: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Utility operator for converting the E field into the H field.
|
||||
For use with `e_full()` -- assumes that there is no magnetic current M.
|
||||
|
|
@ -274,10 +274,10 @@ def e2h(
|
|||
op = curl_forward(dxes[0]) / (-1j * omega)
|
||||
|
||||
if mu is not None:
|
||||
op = sparse.diags(1 / mu) @ op
|
||||
op = sparse.diags_array(1 / mu) @ op
|
||||
|
||||
if pmc is not None:
|
||||
op = sparse.diags(numpy.where(pmc, 0, 1)) @ op
|
||||
op = sparse.diags_array(numpy.where(pmc, 0, 1)) @ op
|
||||
|
||||
return op
|
||||
|
||||
|
|
@ -285,8 +285,8 @@ def e2h(
|
|||
def m2j(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
mu: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Operator for converting a magnetic current M into an electric current J.
|
||||
For use with eg. `e_full()`.
|
||||
|
|
@ -302,12 +302,12 @@ def m2j(
|
|||
op = curl_back(dxes[1]) / (1j * omega)
|
||||
|
||||
if mu is not None:
|
||||
op = op @ sparse.diags(1 / mu)
|
||||
op = op @ sparse.diags_array(1 / mu)
|
||||
|
||||
return op
|
||||
|
||||
|
||||
def poynting_e_cross(e: vcfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
|
||||
def poynting_e_cross(e: vcfdfield, dxes: dx_lists_t) -> sparse.sparray:
|
||||
"""
|
||||
Operator for computing the Poynting vector, containing the
|
||||
(E x) portion of the Poynting vector.
|
||||
|
|
@ -330,13 +330,13 @@ def poynting_e_cross(e: vcfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
|
|||
block_diags = [[ None, fx @ -Ez, fx @ Ey],
|
||||
[ fy @ Ez, None, fy @ -Ex],
|
||||
[ fz @ -Ey, fz @ Ex, None]]
|
||||
block_matrix = sparse.bmat([[sparse.diags(x) if x is not None else None for x in row]
|
||||
for row in block_diags])
|
||||
P = block_matrix @ sparse.diags(numpy.concatenate(dxbg))
|
||||
block_matrix = sparse.block_array([[sparse.diags_array(x) if x is not None else None for x in row]
|
||||
for row in block_diags])
|
||||
P = block_matrix @ sparse.diags_array(numpy.concatenate(dxbg))
|
||||
return P
|
||||
|
||||
|
||||
def poynting_h_cross(h: vcfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
|
||||
def poynting_h_cross(h: vcfdfield, dxes: dx_lists_t) -> sparse.sparray:
|
||||
"""
|
||||
Operator for computing the Poynting vector, containing the (H x) portion of the Poynting vector.
|
||||
|
||||
|
|
@ -353,23 +353,23 @@ def poynting_h_cross(h: vcfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
|
|||
|
||||
dxag = [dx.ravel(order='C') for dx in numpy.meshgrid(*dxes[0], indexing='ij')]
|
||||
dxbg = [dx.ravel(order='C') for dx in numpy.meshgrid(*dxes[1], indexing='ij')]
|
||||
Hx, Hy, Hz = (sparse.diags(hi * db) for hi, db in zip(numpy.split(h, 3), dxbg, strict=True))
|
||||
Hx, Hy, Hz = (sparse.diags_array(hi * db) for hi, db in zip(numpy.split(h, 3), dxbg, strict=True))
|
||||
|
||||
P = (sparse.bmat(
|
||||
P = (sparse.block_array(
|
||||
[[ None, -Hz @ fx, Hy @ fx],
|
||||
[ Hz @ fy, None, -Hx @ fy],
|
||||
[-Hy @ fz, Hx @ fz, None]])
|
||||
@ sparse.diags(numpy.concatenate(dxag)))
|
||||
@ sparse.diags_array(numpy.concatenate(dxag)))
|
||||
return P
|
||||
|
||||
|
||||
def e_tfsf_source(
|
||||
TF_region: vfdfield_t,
|
||||
TF_region: vfdfield,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Operator that turns a desired E-field distribution into a
|
||||
total-field/scattered-field (TFSF) source.
|
||||
|
|
@ -390,18 +390,18 @@ def e_tfsf_source(
|
|||
"""
|
||||
# TODO documentation
|
||||
A = e_full(omega, dxes, epsilon, mu)
|
||||
Q = sparse.diags(TF_region)
|
||||
Q = sparse.diags_array(TF_region)
|
||||
return (A @ Q - Q @ A) / (-1j * omega)
|
||||
|
||||
|
||||
def e_boundary_source(
|
||||
mask: vfdfield_t,
|
||||
mask: vfdfield,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
epsilon: vfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
periodic_mask_edges: bool = False,
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -424,10 +424,10 @@ def e_boundary_source(
|
|||
shape = [len(dxe) for dxe in dxes[0]]
|
||||
jmask = numpy.zeros_like(mask, dtype=bool)
|
||||
|
||||
def shift_rot(axis: int, polarity: int) -> sparse.spmatrix:
|
||||
def shift_rot(axis: int, polarity: int) -> sparse.sparray:
|
||||
return shift_circ(axis=axis, shape=shape, shift_distance=polarity)
|
||||
|
||||
def shift_mir(axis: int, polarity: int) -> sparse.spmatrix:
|
||||
def shift_mir(axis: int, polarity: int) -> sparse.sparray:
|
||||
return shift_with_mirror(axis=axis, shape=shape, shift_distance=polarity)
|
||||
|
||||
shift = shift_rot if periodic_mask_edges else shift_mir
|
||||
|
|
@ -436,7 +436,7 @@ def e_boundary_source(
|
|||
if shape[axis] == 1:
|
||||
continue
|
||||
for polarity in (-1, +1):
|
||||
r = shift(axis, polarity) - sparse.eye(numpy.prod(shape)) # shifted minus original
|
||||
r = shift(axis, polarity) - sparse.eye_array(numpy.prod(shape)) # shifted minus original
|
||||
r3 = sparse.block_diag((r, r, r))
|
||||
jmask = numpy.logical_or(jmask, numpy.abs(r3 @ mask))
|
||||
|
||||
|
|
@ -447,5 +447,5 @@ def e_boundary_source(
|
|||
# (numpy.roll(mask, -1, axis=2) != mask) |
|
||||
# (numpy.roll(mask, +1, axis=2) != mask))
|
||||
|
||||
return sparse.diags(jmask.astype(int)) @ full
|
||||
return sparse.diags_array(jmask.astype(int)) @ full
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from numpy.typing import ArrayLike, NDArray
|
|||
from numpy.linalg import norm
|
||||
import scipy.sparse.linalg
|
||||
|
||||
from ..fdmath import dx_lists_t, vfdfield_t, vcfdfield_t
|
||||
from ..fdmath import dx_lists_t, vfdfield, vcfdfield, vcfdfield_t
|
||||
from . import operators
|
||||
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _scipy_qmr(
|
||||
A: scipy.sparse.csr_matrix,
|
||||
A: scipy.sparse.csr_array,
|
||||
b: ArrayLike,
|
||||
**kwargs: Any,
|
||||
) -> NDArray[numpy.float64]:
|
||||
|
|
@ -66,16 +66,16 @@ def _scipy_qmr(
|
|||
def generic(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
J: vcfdfield_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
J: vcfdfield,
|
||||
epsilon: vfdfield,
|
||||
mu: vfdfield | None = None,
|
||||
*,
|
||||
pec: vfdfield_t | None = None,
|
||||
pmc: vfdfield_t | None = None,
|
||||
pec: vfdfield | None = None,
|
||||
pmc: vfdfield | None = None,
|
||||
adjoint: bool = False,
|
||||
matrix_solver: Callable[..., ArrayLike] = _scipy_qmr,
|
||||
matrix_solver_opts: dict[str, Any] | None = None,
|
||||
E_guess: vcfdfield_t | None = None,
|
||||
E_guess: vcfdfield | None = None,
|
||||
) -> vcfdfield_t:
|
||||
"""
|
||||
Conjugate gradient FDFD solver using CSR sparse matrices.
|
||||
|
|
@ -95,7 +95,7 @@ def generic(
|
|||
(at H-field locations; non-zero value indicates PMC is present)
|
||||
adjoint: If true, solves the adjoint problem.
|
||||
matrix_solver: Called as `matrix_solver(A, b, **matrix_solver_opts) -> x`,
|
||||
where `A`: `scipy.sparse.csr_matrix`;
|
||||
where `A`: `scipy.sparse.csr_array`;
|
||||
`b`: `ArrayLike`;
|
||||
`x`: `ArrayLike`;
|
||||
Default is a wrapped version of `scipy.sparse.linalg.qmr()`
|
||||
|
|
@ -138,4 +138,4 @@ def generic(
|
|||
else:
|
||||
x0 = Pr @ x
|
||||
|
||||
return x0
|
||||
return vcfdfield_t(x0)
|
||||
|
|
|
|||
|
|
@ -180,12 +180,12 @@ if the result is introduced into a space with a discretized z-axis.
|
|||
from typing import Any
|
||||
from collections.abc import Sequence
|
||||
import numpy
|
||||
from numpy.typing import NDArray, ArrayLike
|
||||
from numpy.typing import NDArray
|
||||
from numpy.linalg import norm
|
||||
from scipy import sparse
|
||||
|
||||
from ..fdmath.operators import deriv_forward, deriv_back, cross
|
||||
from ..fdmath import vec, unvec, dx_lists_t, vfdfield_t, vcfdfield_t
|
||||
from ..fdmath import vec, unvec, dx_lists2_t, vcfdfield2_t, vcfdslice_t, vcfdfield2, vfdslice, vcfdslice
|
||||
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
|
||||
|
||||
|
||||
|
|
@ -194,10 +194,10 @@ __author__ = 'Jan Petykiewicz'
|
|||
|
||||
def operator_e(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Waveguide operator of the form
|
||||
|
||||
|
|
@ -246,12 +246,12 @@ def operator_e(
|
|||
Dbx, Dby = deriv_back(dxes[1])
|
||||
|
||||
eps_parts = numpy.split(epsilon, 3)
|
||||
eps_xy = sparse.diags(numpy.hstack((eps_parts[0], eps_parts[1])))
|
||||
eps_z_inv = sparse.diags(1 / eps_parts[2])
|
||||
eps_xy = sparse.diags_array(numpy.hstack((eps_parts[0], eps_parts[1])))
|
||||
eps_z_inv = sparse.diags_array(1 / eps_parts[2])
|
||||
|
||||
mu_parts = numpy.split(mu, 3)
|
||||
mu_yx = sparse.diags(numpy.hstack((mu_parts[1], mu_parts[0])))
|
||||
mu_z_inv = sparse.diags(1 / mu_parts[2])
|
||||
mu_yx = sparse.diags_array(numpy.hstack((mu_parts[1], mu_parts[0])))
|
||||
mu_z_inv = sparse.diags_array(1 / mu_parts[2])
|
||||
|
||||
op = (
|
||||
omega * omega * mu_yx @ eps_xy
|
||||
|
|
@ -263,10 +263,10 @@ def operator_e(
|
|||
|
||||
def operator_h(
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Waveguide operator of the form
|
||||
|
||||
|
|
@ -315,12 +315,12 @@ def operator_h(
|
|||
Dbx, Dby = deriv_back(dxes[1])
|
||||
|
||||
eps_parts = numpy.split(epsilon, 3)
|
||||
eps_yx = sparse.diags(numpy.hstack((eps_parts[1], eps_parts[0])))
|
||||
eps_z_inv = sparse.diags(1 / eps_parts[2])
|
||||
eps_yx = sparse.diags_array(numpy.hstack((eps_parts[1], eps_parts[0])))
|
||||
eps_z_inv = sparse.diags_array(1 / eps_parts[2])
|
||||
|
||||
mu_parts = numpy.split(mu, 3)
|
||||
mu_xy = sparse.diags(numpy.hstack((mu_parts[0], mu_parts[1])))
|
||||
mu_z_inv = sparse.diags(1 / mu_parts[2])
|
||||
mu_xy = sparse.diags_array(numpy.hstack((mu_parts[0], mu_parts[1])))
|
||||
mu_z_inv = sparse.diags_array(1 / mu_parts[2])
|
||||
|
||||
op = (
|
||||
omega * omega * eps_yx @ mu_xy
|
||||
|
|
@ -331,14 +331,14 @@ def operator_h(
|
|||
|
||||
|
||||
def normalized_fields_e(
|
||||
e_xy: ArrayLike,
|
||||
e_xy: vcfdfield2,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
prop_phase: float = 0,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
) -> tuple[vcfdslice_t, vcfdslice_t]:
|
||||
"""
|
||||
Given a vector `e_xy` containing the vectorized E_x and E_y fields,
|
||||
returns normalized, vectorized E and H fields for the system.
|
||||
|
|
@ -366,14 +366,14 @@ def normalized_fields_e(
|
|||
|
||||
|
||||
def normalized_fields_h(
|
||||
h_xy: ArrayLike,
|
||||
h_xy: vcfdfield2,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
prop_phase: float = 0,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
) -> tuple[vcfdslice_t, vcfdslice_t]:
|
||||
"""
|
||||
Given a vector `h_xy` containing the vectorized H_x and H_y fields,
|
||||
returns normalized, vectorized E and H fields for the system.
|
||||
|
|
@ -401,21 +401,19 @@ def normalized_fields_h(
|
|||
|
||||
|
||||
def _normalized_fields(
|
||||
e: vcfdfield_t,
|
||||
h: vcfdfield_t,
|
||||
e: vcfdslice,
|
||||
h: vcfdslice,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
prop_phase: float = 0,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
) -> tuple[vcfdslice_t, vcfdslice_t]:
|
||||
# TODO documentation
|
||||
shape = [s.size for s in dxes[0]]
|
||||
dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
|
||||
|
||||
# Find time-averaged Sz and normalize to it
|
||||
# H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
|
||||
phase = numpy.exp(-1j * -prop_phase / 2)
|
||||
Sz_tavg = inner_product(e, h, dxes=dxes, prop_phase=prop_phase, conj_h=True).real
|
||||
assert Sz_tavg > 0, f'Found a mode propagating in the wrong direction! {Sz_tavg=}'
|
||||
|
||||
|
|
@ -436,16 +434,16 @@ def _normalized_fields(
|
|||
e *= norm_factor
|
||||
h *= norm_factor
|
||||
|
||||
return e, h
|
||||
return vcfdslice_t(e), vcfdslice_t(h)
|
||||
|
||||
|
||||
def exy2h(
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -468,10 +466,10 @@ def exy2h(
|
|||
def hxy2e(
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -493,9 +491,9 @@ def hxy2e(
|
|||
|
||||
def hxy2h(
|
||||
wavenumber: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -514,22 +512,22 @@ def hxy2h(
|
|||
|
||||
if mu is not None:
|
||||
mu_parts = numpy.split(mu, 3)
|
||||
mu_xy = sparse.diags(numpy.hstack((mu_parts[0], mu_parts[1])))
|
||||
mu_z_inv = sparse.diags(1 / mu_parts[2])
|
||||
mu_xy = sparse.diags_array(numpy.hstack((mu_parts[0], mu_parts[1])))
|
||||
mu_z_inv = sparse.diags_array(1 / mu_parts[2])
|
||||
|
||||
hxy2hz = mu_z_inv @ hxy2hz @ mu_xy
|
||||
|
||||
n_pts = dxes[1][0].size * dxes[1][1].size
|
||||
op = sparse.vstack((sparse.eye(2 * n_pts),
|
||||
op = sparse.vstack((sparse.eye_array(2 * n_pts),
|
||||
hxy2hz))
|
||||
return op
|
||||
|
||||
|
||||
def exy2e(
|
||||
wavenumber: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
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
|
||||
|
|
@ -575,13 +573,13 @@ def exy2e(
|
|||
|
||||
if epsilon is not None:
|
||||
epsilon_parts = numpy.split(epsilon, 3)
|
||||
epsilon_xy = sparse.diags(numpy.hstack((epsilon_parts[0], epsilon_parts[1])))
|
||||
epsilon_z_inv = sparse.diags(1 / epsilon_parts[2])
|
||||
epsilon_xy = sparse.diags_array(numpy.hstack((epsilon_parts[0], epsilon_parts[1])))
|
||||
epsilon_z_inv = sparse.diags_array(1 / epsilon_parts[2])
|
||||
|
||||
exy2ez = epsilon_z_inv @ exy2ez @ epsilon_xy
|
||||
|
||||
n_pts = dxes[0][0].size * dxes[0][1].size
|
||||
op = sparse.vstack((sparse.eye(2 * n_pts),
|
||||
op = sparse.vstack((sparse.eye_array(2 * n_pts),
|
||||
exy2ez))
|
||||
return op
|
||||
|
||||
|
|
@ -589,12 +587,12 @@ def exy2e(
|
|||
def e2h(
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Returns an operator which, when applied to a vectorized E eigenfield, produces
|
||||
the vectorized H eigenfield.
|
||||
the vectorized H eigenfield slice.
|
||||
|
||||
Args:
|
||||
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
|
||||
|
|
@ -607,19 +605,19 @@ def e2h(
|
|||
"""
|
||||
op = curl_e(wavenumber, dxes) / (-1j * omega)
|
||||
if mu is not None:
|
||||
op = sparse.diags(1 / mu) @ op
|
||||
op = sparse.diags_array(1 / mu) @ op
|
||||
return op
|
||||
|
||||
|
||||
def h2e(
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t
|
||||
) -> sparse.spmatrix:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Returns an operator which, when applied to a vectorized H eigenfield, produces
|
||||
the vectorized E eigenfield.
|
||||
the vectorized E eigenfield slice.
|
||||
|
||||
Args:
|
||||
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
|
||||
|
|
@ -630,13 +628,13 @@ def h2e(
|
|||
Returns:
|
||||
Sparse matrix representation of the operator.
|
||||
"""
|
||||
op = sparse.diags(1 / (1j * omega * epsilon)) @ curl_h(wavenumber, dxes)
|
||||
op = sparse.diags_array(1 / (1j * omega * epsilon)) @ curl_h(wavenumber, dxes)
|
||||
return op
|
||||
|
||||
|
||||
def curl_e(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
|
||||
def curl_e(wavenumber: complex, dxes: dx_lists2_t) -> sparse.sparray:
|
||||
"""
|
||||
Discretized curl operator for use with the waveguide E field.
|
||||
Discretized curl operator for use with the waveguide E field slice.
|
||||
|
||||
Args:
|
||||
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
|
||||
|
|
@ -645,18 +643,18 @@ def curl_e(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
|
|||
Returns:
|
||||
Sparse matrix representation of the operator.
|
||||
"""
|
||||
n = 1
|
||||
for d in dxes[0]:
|
||||
n *= len(d)
|
||||
nn = 1
|
||||
for dd in dxes[0]:
|
||||
nn *= len(dd)
|
||||
|
||||
Bz = -1j * wavenumber * sparse.eye(n)
|
||||
Bz = -1j * wavenumber * sparse.eye_array(nn)
|
||||
Dfx, Dfy = deriv_forward(dxes[0])
|
||||
return cross([Dfx, Dfy, Bz])
|
||||
|
||||
|
||||
def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
|
||||
def curl_h(wavenumber: complex, dxes: dx_lists2_t) -> sparse.sparray:
|
||||
"""
|
||||
Discretized curl operator for use with the waveguide H field.
|
||||
Discretized curl operator for use with the waveguide H field slice.
|
||||
|
||||
Args:
|
||||
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
|
||||
|
|
@ -665,22 +663,22 @@ def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
|
|||
Returns:
|
||||
Sparse matrix representation of the operator.
|
||||
"""
|
||||
n = 1
|
||||
for d in dxes[1]:
|
||||
n *= len(d)
|
||||
nn = 1
|
||||
for dd in dxes[1]:
|
||||
nn *= len(dd)
|
||||
|
||||
Bz = -1j * wavenumber * sparse.eye(n)
|
||||
Bz = -1j * wavenumber * sparse.eye_array(nn)
|
||||
Dbx, Dby = deriv_back(dxes[1])
|
||||
return cross([Dbx, Dby, Bz])
|
||||
|
||||
|
||||
def h_err(
|
||||
h: vcfdfield_t,
|
||||
h: vcfdslice,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None
|
||||
) -> float:
|
||||
"""
|
||||
Calculates the relative error in the H field
|
||||
|
|
@ -699,7 +697,7 @@ def h_err(
|
|||
ce = curl_e(wavenumber, dxes)
|
||||
ch = curl_h(wavenumber, dxes)
|
||||
|
||||
eps_inv = sparse.diags(1 / epsilon)
|
||||
eps_inv = sparse.diags_array(1 / epsilon)
|
||||
|
||||
if mu is None:
|
||||
op = ce @ eps_inv @ ch @ h - omega ** 2 * h
|
||||
|
|
@ -710,12 +708,12 @@ def h_err(
|
|||
|
||||
|
||||
def e_err(
|
||||
e: vcfdfield_t,
|
||||
e: vcfdslice,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
) -> float:
|
||||
"""
|
||||
Calculates the relative error in the E field
|
||||
|
|
@ -737,21 +735,21 @@ def e_err(
|
|||
if mu is None:
|
||||
op = ch @ ce @ e - omega ** 2 * (epsilon * e)
|
||||
else:
|
||||
mu_inv = sparse.diags(1 / mu)
|
||||
mu_inv = sparse.diags_array(1 / mu)
|
||||
op = ch @ mu_inv @ ce @ e - omega ** 2 * (epsilon * e)
|
||||
|
||||
return float(norm(op) / norm(e))
|
||||
|
||||
|
||||
def sensitivity(
|
||||
e_norm: vcfdfield_t,
|
||||
h_norm: vcfdfield_t,
|
||||
e_norm: vcfdslice,
|
||||
h_norm: vcfdslice,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
) -> vcfdfield_t:
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
) -> vcfdslice_t:
|
||||
r"""
|
||||
Given a waveguide structure (`dxes`, `epsilon`, `mu`) and mode fields
|
||||
(`e_norm`, `h_norm`, `wavenumber`, `omega`), calculates the sensitivity of the wavenumber
|
||||
|
|
@ -825,11 +823,11 @@ def sensitivity(
|
|||
Dbx, Dby = deriv_back(dxes[1])
|
||||
|
||||
eps_x, eps_y, eps_z = numpy.split(epsilon, 3)
|
||||
eps_xy = sparse.diags(numpy.hstack((eps_x, eps_y)))
|
||||
eps_z_inv = sparse.diags(1 / eps_z)
|
||||
eps_xy = sparse.diags_array(numpy.hstack((eps_x, eps_y)))
|
||||
eps_z_inv = sparse.diags_array(1 / eps_z)
|
||||
|
||||
mu_x, mu_y, _mu_z = numpy.split(mu, 3)
|
||||
mu_yx = sparse.diags(numpy.hstack((mu_y, mu_x)))
|
||||
mu_yx = sparse.diags_array(numpy.hstack((mu_y, mu_x)))
|
||||
|
||||
da_exxhyy = vec(dxes[1][0][:, None] * dxes[0][1][None, :])
|
||||
da_eyyhxx = vec(dxes[1][1][None, :] * dxes[0][0][:, None])
|
||||
|
|
@ -843,15 +841,15 @@ def sensitivity(
|
|||
norm = hv_yx_conj @ ev_xy
|
||||
|
||||
sens_tot = numpy.concatenate([sens_xy1 + sens_xy2, sens_z]) / (2 * wavenumber * norm)
|
||||
return sens_tot
|
||||
return vcfdslice_t(sens_tot)
|
||||
|
||||
|
||||
def solve_modes(
|
||||
mode_numbers: Sequence[int],
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
mode_margin: int = 2,
|
||||
) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
|
||||
"""
|
||||
|
|
@ -907,7 +905,7 @@ def solve_mode(
|
|||
mode_number: int,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> tuple[vcfdfield_t, complex]:
|
||||
) -> tuple[vcfdfield2_t, complex]:
|
||||
"""
|
||||
Wrapper around `solve_modes()` that solves for a single mode.
|
||||
|
||||
|
|
@ -921,13 +919,13 @@ def solve_mode(
|
|||
"""
|
||||
kwargs['mode_numbers'] = [mode_number]
|
||||
e_xys, wavenumbers = solve_modes(*args, **kwargs)
|
||||
return e_xys[0], wavenumbers[0]
|
||||
return vcfdfield2_t(e_xys[0]), wavenumbers[0]
|
||||
|
||||
|
||||
def inner_product( # TODO documentation
|
||||
e1: vcfdfield_t,
|
||||
h2: vcfdfield_t,
|
||||
dxes: dx_lists_t,
|
||||
e1: vcfdfield2,
|
||||
h2: vcfdfield2,
|
||||
dxes: dx_lists2_t,
|
||||
prop_phase: float = 0,
|
||||
conj_h: bool = False,
|
||||
trapezoid: bool = False,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ 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.
|
||||
"""
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
from collections.abc import Sequence
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
from numpy import complexfloating
|
||||
|
||||
from ..fdmath import vec, unvec, dx_lists_t, fdfield_t, cfdfield_t
|
||||
from ..fdmath import vec, unvec, dx_lists_t, cfdfield_t, fdfield, cfdfield
|
||||
from . import operators, waveguide_2d
|
||||
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ def solve_mode(
|
|||
axis: int,
|
||||
polarity: int,
|
||||
slices: Sequence[slice],
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> dict[str, complex | NDArray[complexfloating]]:
|
||||
"""
|
||||
Given a 3D grid, selects a slice from the grid and attempts to
|
||||
|
|
@ -95,9 +95,10 @@ def solve_mode(
|
|||
# Expand E, H to full epsilon space we were given
|
||||
E = numpy.zeros_like(epsilon, dtype=complex)
|
||||
H = numpy.zeros_like(epsilon, dtype=complex)
|
||||
for a, o in enumerate(reverse_order):
|
||||
E[(a, *slices)] = e[o][:, :, None].transpose(reverse_order)
|
||||
H[(a, *slices)] = h[o][:, :, None].transpose(reverse_order)
|
||||
for aa, oo in enumerate(reverse_order):
|
||||
iii = cast('tuple[slice | int]', (aa, *slices))
|
||||
E[iii] = e[oo][:, :, None].transpose(reverse_order)
|
||||
H[iii] = h[oo][:, :, None].transpose(reverse_order)
|
||||
|
||||
results = {
|
||||
'wavenumber': wavenumber,
|
||||
|
|
@ -109,15 +110,15 @@ def solve_mode(
|
|||
|
||||
|
||||
def compute_source(
|
||||
E: cfdfield_t,
|
||||
E: cfdfield,
|
||||
wavenumber: complex,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
axis: int,
|
||||
polarity: int,
|
||||
slices: Sequence[slice],
|
||||
epsilon: fdfield_t,
|
||||
mu: fdfield_t | None = None,
|
||||
epsilon: fdfield,
|
||||
mu: fdfield | None = None,
|
||||
) -> cfdfield_t:
|
||||
"""
|
||||
Given an eigenmode obtained by `solve_mode`, returns the current source distribution
|
||||
|
|
@ -151,11 +152,11 @@ def compute_source(
|
|||
|
||||
masked_e2j = operators.e_boundary_source(mask=vec(mask), omega=omega, dxes=dxes, epsilon=vec(epsilon), mu=vec(mu))
|
||||
J = unvec(masked_e2j @ vec(E_expanded), E.shape[1:])
|
||||
return J
|
||||
return cfdfield_t(J)
|
||||
|
||||
|
||||
def compute_overlap_e(
|
||||
E: cfdfield_t,
|
||||
E: cfdfield,
|
||||
wavenumber: complex,
|
||||
dxes: dx_lists_t,
|
||||
axis: int,
|
||||
|
|
@ -195,12 +196,12 @@ def compute_overlap_e(
|
|||
Etgt = numpy.zeros_like(Ee)
|
||||
Etgt[slices2] = Ee[slices2]
|
||||
|
||||
Etgt /= (Etgt.conj() * Etgt).sum()
|
||||
return Etgt
|
||||
Etgt /= (Etgt.conj() * Etgt).sum() # type: ignore
|
||||
return cfdfield_t(Etgt)
|
||||
|
||||
|
||||
def expand_e(
|
||||
E: cfdfield_t,
|
||||
E: cfdfield,
|
||||
wavenumber: complex,
|
||||
dxes: dx_lists_t,
|
||||
axis: int,
|
||||
|
|
@ -245,4 +246,4 @@ def expand_e(
|
|||
slices_in = (slice(None), *slices)
|
||||
|
||||
E_expanded[slices_exp] = phase_E * numpy.array(E)[slices_in]
|
||||
return E_expanded
|
||||
return cfdfield_t(E_expanded)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ import numpy
|
|||
from numpy.typing import NDArray, ArrayLike
|
||||
from scipy import sparse
|
||||
|
||||
from ..fdmath import vec, unvec, dx_lists_t, vfdfield_t, vcfdfield_t
|
||||
from ..fdmath import vec, unvec, dx_lists2_t, vcfdslice_t, vcfdfield2_t, vfdslice, vcfdslice, vcfdfield2
|
||||
from ..fdmath.operators import deriv_forward, deriv_back
|
||||
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
|
||||
from . import waveguide_2d
|
||||
|
|
@ -78,10 +78,10 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def cylindrical_operator(
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
rmin: float,
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Cylindrical coordinate waveguide operator of the form
|
||||
|
||||
|
|
@ -143,11 +143,11 @@ def cylindrical_operator(
|
|||
def solve_modes(
|
||||
mode_numbers: Sequence[int],
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdfield_t,
|
||||
dxes: dx_lists2_t,
|
||||
epsilon: vfdslice,
|
||||
rmin: float,
|
||||
mode_margin: int = 2,
|
||||
) -> tuple[vcfdfield_t, NDArray[numpy.complex128]]:
|
||||
) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
|
||||
"""
|
||||
Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode
|
||||
of the bent waveguide with the specified mode number.
|
||||
|
|
@ -191,7 +191,7 @@ def solve_modes(
|
|||
|
||||
# Wavenumbers assume the mode is at rmin, which is unlikely
|
||||
# Instead, return the wavenumber in inverse radians
|
||||
angular_wavenumbers = wavenumbers * cast(complex, rmin)
|
||||
angular_wavenumbers = wavenumbers * cast('complex', rmin)
|
||||
|
||||
order = angular_wavenumbers.argsort()[::-1]
|
||||
e_xys = e_xys[order]
|
||||
|
|
@ -204,7 +204,7 @@ def solve_mode(
|
|||
mode_number: int,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> tuple[vcfdfield_t, complex]:
|
||||
) -> tuple[vcfdslice, complex]:
|
||||
"""
|
||||
Wrapper around `solve_modes()` that solves for a single mode.
|
||||
|
||||
|
|
@ -222,10 +222,10 @@ def solve_mode(
|
|||
|
||||
|
||||
def linear_wavenumbers(
|
||||
e_xys: vcfdfield_t,
|
||||
e_xys: list[vcfdfield2_t],
|
||||
angular_wavenumbers: ArrayLike,
|
||||
epsilon: vfdfield_t,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: vfdslice,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
) -> NDArray[numpy.complex128]:
|
||||
"""
|
||||
|
|
@ -247,7 +247,6 @@ def linear_wavenumbers(
|
|||
angular_wavenumbers = numpy.asarray(angular_wavenumbers)
|
||||
mode_radii = numpy.empty_like(angular_wavenumbers, dtype=float)
|
||||
|
||||
wavenumbers = numpy.empty_like(angular_wavenumbers)
|
||||
shape2d = (len(dxes[0][0]), len(dxes[0][1]))
|
||||
epsilon2d = unvec(epsilon, shape2d)[:2]
|
||||
grid_radii = rmin + numpy.cumsum(dxes[0][0])
|
||||
|
|
@ -265,11 +264,11 @@ def linear_wavenumbers(
|
|||
def exy2h(
|
||||
angular_wavenumber: complex,
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -294,10 +293,10 @@ def exy2h(
|
|||
def exy2e(
|
||||
angular_wavenumber: complex,
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
epsilon: vfdfield_t,
|
||||
) -> sparse.spmatrix:
|
||||
epsilon: vfdslice,
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
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
|
||||
|
|
@ -323,7 +322,7 @@ def exy2e(
|
|||
|
||||
Ta, Tb = dxes2T(dxes=dxes, rmin=rmin)
|
||||
Tai = sparse.diags_array(1 / Ta.diagonal())
|
||||
Tbi = sparse.diags_array(1 / Tb.diagonal())
|
||||
#Tbi = sparse.diags_array(1 / Tb.diagonal())
|
||||
|
||||
epsilon_parts = numpy.split(epsilon, 3)
|
||||
epsilon_x, epsilon_y = (sparse.diags_array(epsi) for epsi in epsilon_parts[:2])
|
||||
|
|
@ -331,8 +330,6 @@ def exy2e(
|
|||
|
||||
n_pts = dxes[0][0].size * dxes[0][1].size
|
||||
zeros = sparse.coo_array((n_pts, n_pts))
|
||||
keep_x = sparse.block_array([[sparse.eye_array(n_pts), None], [None, zeros]])
|
||||
keep_y = sparse.block_array([[zeros, None], [None, sparse.eye_array(n_pts)]])
|
||||
|
||||
mu_z = numpy.ones(n_pts)
|
||||
mu_z_inv = sparse.diags_array(1 / mu_z)
|
||||
|
|
@ -352,10 +349,10 @@ def exy2e(
|
|||
def e2h(
|
||||
angular_wavenumber: complex,
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
mu: vfdfield_t | None = None
|
||||
) -> sparse.spmatrix:
|
||||
mu: vfdslice | None = None
|
||||
) -> sparse.sparray:
|
||||
r"""
|
||||
Returns an operator which, when applied to a vectorized E eigenfield, produces
|
||||
the vectorized H eigenfield.
|
||||
|
|
@ -396,7 +393,7 @@ def e2h(
|
|||
|
||||
|
||||
def dxes2T(
|
||||
dxes: dx_lists_t,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]:
|
||||
r"""
|
||||
|
|
@ -421,15 +418,15 @@ def dxes2T(
|
|||
|
||||
|
||||
def normalized_fields_e(
|
||||
e_xy: ArrayLike,
|
||||
e_xy: vcfdfield2,
|
||||
angular_wavenumber: complex,
|
||||
omega: float,
|
||||
dxes: dx_lists_t,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
prop_phase: float = 0,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
) -> tuple[vcfdslice_t, vcfdslice_t]:
|
||||
"""
|
||||
Given a vector `e_xy` containing the vectorized E_x and E_y fields,
|
||||
returns normalized, vectorized E and H fields for the system.
|
||||
|
|
@ -459,23 +456,21 @@ def normalized_fields_e(
|
|||
|
||||
|
||||
def _normalized_fields(
|
||||
e: vcfdfield_t,
|
||||
h: vcfdfield_t,
|
||||
e: vcfdslice,
|
||||
h: vcfdslice,
|
||||
omega: complex,
|
||||
dxes: dx_lists_t,
|
||||
rmin: float,
|
||||
epsilon: vfdfield_t,
|
||||
mu: vfdfield_t | None = None,
|
||||
dxes: dx_lists2_t,
|
||||
rmin: float, # Currently unused, but may want to use cylindrical poynting
|
||||
epsilon: vfdslice,
|
||||
mu: vfdslice | None = None,
|
||||
prop_phase: float = 0,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
) -> tuple[vcfdslice_t, vcfdslice_t]:
|
||||
h *= -1
|
||||
# TODO documentation for normalized_fields
|
||||
shape = [s.size for s in dxes[0]]
|
||||
dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
|
||||
|
||||
# Find time-averaged Sz and normalize to it
|
||||
# H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
|
||||
phase = numpy.exp(-1j * -prop_phase / 2)
|
||||
Sz_tavg = waveguide_2d.inner_product(e, h, dxes=dxes, prop_phase=prop_phase, conj_h=True).real # Note, using linear poynting vector
|
||||
assert Sz_tavg > 0, f'Found a mode propagating in the wrong direction! {Sz_tavg=}'
|
||||
|
||||
|
|
@ -495,4 +490,4 @@ def _normalized_fields(
|
|||
|
||||
e *= norm_factor
|
||||
h *= norm_factor
|
||||
return e, h
|
||||
return vcfdslice_t(e), vcfdslice_t(h)
|
||||
|
|
|
|||
Loading…
Reference in a new issue