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
@ -64,10 +64,10 @@ def rayleigh_quotient_iteration(
|
||||
(eigenvalues, eigenvectors)
|
||||
"""
|
||||
try:
|
||||
(operator - sparse.eye(operator.shape[0]))
|
||||
(operator - sparse.eye_array(operator.shape[0]))
|
||||
|
||||
def shift(eigval: float) -> sparse:
|
||||
return eigval * sparse.eye(operator.shape[0])
|
||||
def shift(eigval: float) -> sparse.sparray:
|
||||
return eigval * sparse.eye_array(operator.shape[0])
|
||||
|
||||
if solver is None:
|
||||
solver = spalg.spsolve
|
||||
@ -130,7 +130,7 @@ def signed_eigensolve(
|
||||
|
||||
# Try to combine, use general LinearOperator if we fail
|
||||
try:
|
||||
shifted_operator = operator + shift * sparse.eye(operator.shape[0])
|
||||
shifted_operator = operator + shift * sparse.eye_array(operator.shape[0])
|
||||
except TypeError:
|
||||
shifted_operator = operator + spalg.LinearOperator(shape=operator.shape,
|
||||
matvec=lambda v: shift * v)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -742,12 +742,34 @@ normalized results are needed.
|
||||
"""
|
||||
|
||||
from .types import (
|
||||
fdfield_t as fdfield_t,
|
||||
vfdfield_t as vfdfield_t,
|
||||
cfdfield_t as cfdfield_t,
|
||||
vcfdfield_t as vcfdfield_t,
|
||||
dx_lists_t as dx_lists_t,
|
||||
dx_lists_mut as dx_lists_mut,
|
||||
fdfield_t as fdfield_t,
|
||||
vfdfield_t as vfdfield_t,
|
||||
cfdfield_t as cfdfield_t,
|
||||
vcfdfield_t as vcfdfield_t,
|
||||
fdfield2_t as fdfield2_t,
|
||||
vfdfield2_t as vfdfield2_t,
|
||||
cfdfield2_t as cfdfield2_t,
|
||||
vcfdfield2_t as vcfdfield2_t,
|
||||
fdfield as fdfield,
|
||||
vfdfield as vfdfield,
|
||||
cfdfield as cfdfield,
|
||||
vcfdfield as vcfdfield,
|
||||
fdfield2 as fdfield2,
|
||||
vfdfield2 as vfdfield2,
|
||||
cfdfield2 as cfdfield2,
|
||||
vcfdfield2 as vcfdfield2,
|
||||
fdslice_t as fdslice_t,
|
||||
vfdslice_t as vfdslice_t,
|
||||
cfdslice_t as cfdslice_t,
|
||||
vcfdslice_t as vcfdslice_t,
|
||||
fdslice as fdslice,
|
||||
vfdslice as vfdslice,
|
||||
cfdslice as cfdslice,
|
||||
vcfdslice as vcfdslice,
|
||||
dx_lists_t as dx_lists_t,
|
||||
dx_lists2_t as dx_lists2_t,
|
||||
dx_lists_mut as dx_lists_mut,
|
||||
dx_lists2_mut as dx_lists2_mut,
|
||||
fdfield_updater_t as fdfield_updater_t,
|
||||
cfdfield_updater_t as cfdfield_updater_t,
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ def shift_circ(
|
||||
axis: int,
|
||||
shape: Sequence[int],
|
||||
shift_distance: int = 1,
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Utility operator for performing a circular shift along a specified axis by a
|
||||
specified number of elements.
|
||||
@ -44,7 +44,7 @@ def shift_circ(
|
||||
|
||||
vij = (numpy.ones(n), (i_ind, j_ind.ravel(order='C')))
|
||||
|
||||
d = sparse.csr_matrix(vij, shape=(n, n))
|
||||
d = sparse.csr_array(vij, shape=(n, n))
|
||||
|
||||
if shift_distance < 0:
|
||||
d = d.T
|
||||
@ -56,7 +56,7 @@ def shift_with_mirror(
|
||||
axis: int,
|
||||
shape: Sequence[int],
|
||||
shift_distance: int = 1,
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Utility operator for performing an n-element shift along a specified axis, with mirror
|
||||
boundary conditions applied to the cells beyond the receding edge.
|
||||
@ -92,13 +92,13 @@ def shift_with_mirror(
|
||||
|
||||
vij = (numpy.ones(n), (i_ind, j_ind.ravel(order='C')))
|
||||
|
||||
d = sparse.csr_matrix(vij, shape=(n, n))
|
||||
d = sparse.csr_array(vij, shape=(n, n))
|
||||
return d
|
||||
|
||||
|
||||
def deriv_forward(
|
||||
dx_e: Sequence[NDArray[floating | complexfloating]],
|
||||
) -> list[sparse.spmatrix]:
|
||||
) -> list[sparse.sparray]:
|
||||
"""
|
||||
Utility operators for taking discretized derivatives (forward variant).
|
||||
|
||||
@ -114,10 +114,10 @@ def deriv_forward(
|
||||
|
||||
dx_e_expanded = numpy.meshgrid(*dx_e, indexing='ij')
|
||||
|
||||
def deriv(axis: int) -> sparse.spmatrix:
|
||||
return shift_circ(axis, shape, 1) - sparse.eye(n)
|
||||
def deriv(axis: int) -> sparse.sparray:
|
||||
return shift_circ(axis, shape, 1) - sparse.eye_array(n)
|
||||
|
||||
Ds = [sparse.diags(+1 / dx.ravel(order='C')) @ deriv(a)
|
||||
Ds = [sparse.diags_array(+1 / dx.ravel(order='C')) @ deriv(a)
|
||||
for a, dx in enumerate(dx_e_expanded)]
|
||||
|
||||
return Ds
|
||||
@ -125,7 +125,7 @@ def deriv_forward(
|
||||
|
||||
def deriv_back(
|
||||
dx_h: Sequence[NDArray[floating | complexfloating]],
|
||||
) -> list[sparse.spmatrix]:
|
||||
) -> list[sparse.sparray]:
|
||||
"""
|
||||
Utility operators for taking discretized derivatives (backward variant).
|
||||
|
||||
@ -141,18 +141,18 @@ def deriv_back(
|
||||
|
||||
dx_h_expanded = numpy.meshgrid(*dx_h, indexing='ij')
|
||||
|
||||
def deriv(axis: int) -> sparse.spmatrix:
|
||||
return shift_circ(axis, shape, -1) - sparse.eye(n)
|
||||
def deriv(axis: int) -> sparse.sparray:
|
||||
return shift_circ(axis, shape, -1) - sparse.eye_array(n)
|
||||
|
||||
Ds = [sparse.diags(-1 / dx.ravel(order='C')) @ deriv(a)
|
||||
Ds = [sparse.diags_array(-1 / dx.ravel(order='C')) @ deriv(a)
|
||||
for a, dx in enumerate(dx_h_expanded)]
|
||||
|
||||
return Ds
|
||||
|
||||
|
||||
def cross(
|
||||
B: Sequence[sparse.spmatrix],
|
||||
) -> sparse.spmatrix:
|
||||
B: Sequence[sparse.sparray],
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Cross product operator
|
||||
|
||||
@ -164,13 +164,14 @@ def cross(
|
||||
Sparse matrix corresponding to (B x), where x is the cross product.
|
||||
"""
|
||||
n = B[0].shape[0]
|
||||
zero = sparse.csr_matrix((n, n))
|
||||
return sparse.bmat([[zero, -B[2], B[1]],
|
||||
[B[2], zero, -B[0]],
|
||||
[-B[1], B[0], zero]])
|
||||
zero = sparse.csr_array((n, n))
|
||||
return sparse.block_array([
|
||||
[zero, -B[2], B[1]],
|
||||
[B[2], zero, -B[0]],
|
||||
[-B[1], B[0], zero]])
|
||||
|
||||
|
||||
def vec_cross(b: vfdfield_t) -> sparse.spmatrix:
|
||||
def vec_cross(b: vfdfield_t) -> sparse.sparray:
|
||||
"""
|
||||
Vector cross product operator
|
||||
|
||||
@ -182,11 +183,11 @@ def vec_cross(b: vfdfield_t) -> sparse.spmatrix:
|
||||
Sparse matrix corresponding to (b x), where x is the cross product.
|
||||
|
||||
"""
|
||||
B = [sparse.diags(c) for c in numpy.split(b, 3)]
|
||||
B = [sparse.diags_array(c) for c in numpy.split(b, 3)]
|
||||
return cross(B)
|
||||
|
||||
|
||||
def avg_forward(axis: int, shape: Sequence[int]) -> sparse.spmatrix:
|
||||
def avg_forward(axis: int, shape: Sequence[int]) -> sparse.sparray:
|
||||
"""
|
||||
Forward average operator `(x4 = (x4 + x5) / 2)`
|
||||
|
||||
@ -201,10 +202,10 @@ def avg_forward(axis: int, shape: Sequence[int]) -> sparse.spmatrix:
|
||||
raise Exception(f'Invalid shape: {shape}')
|
||||
|
||||
n = numpy.prod(shape)
|
||||
return 0.5 * (sparse.eye(n) + shift_circ(axis, shape))
|
||||
return 0.5 * (sparse.eye_array(n) + shift_circ(axis, shape))
|
||||
|
||||
|
||||
def avg_back(axis: int, shape: Sequence[int]) -> sparse.spmatrix:
|
||||
def avg_back(axis: int, shape: Sequence[int]) -> sparse.sparray:
|
||||
"""
|
||||
Backward average operator `(x4 = (x4 + x3) / 2)`
|
||||
|
||||
@ -220,7 +221,7 @@ def avg_back(axis: int, shape: Sequence[int]) -> sparse.spmatrix:
|
||||
|
||||
def curl_forward(
|
||||
dx_e: Sequence[NDArray[floating | complexfloating]],
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Curl operator for use with the E field.
|
||||
|
||||
@ -236,7 +237,7 @@ def curl_forward(
|
||||
|
||||
def curl_back(
|
||||
dx_h: Sequence[NDArray[floating | complexfloating]],
|
||||
) -> sparse.spmatrix:
|
||||
) -> sparse.sparray:
|
||||
"""
|
||||
Curl operator for use with the H field.
|
||||
|
||||
|
||||
@ -1,25 +1,64 @@
|
||||
"""
|
||||
Types shared across multiple submodules
|
||||
"""
|
||||
from typing import NewType
|
||||
from collections.abc import Sequence, Callable, MutableSequence
|
||||
from numpy.typing import NDArray
|
||||
from numpy import floating, complexfloating
|
||||
|
||||
|
||||
# Field types
|
||||
fdfield_t = NDArray[floating]
|
||||
fdfield_t = NewType('fdfield_t', NDArray[floating])
|
||||
type fdfield = fdfield_t | NDArray[floating]
|
||||
"""Vector field with shape (3, X, Y, Z) (e.g. `[E_x, E_y, E_z]`)"""
|
||||
|
||||
vfdfield_t = NDArray[floating]
|
||||
vfdfield_t = NewType('vfdfield_t', NDArray[floating])
|
||||
type vfdfield = vfdfield_t | NDArray[floating]
|
||||
"""Linearized vector field (single vector of length 3*X*Y*Z)"""
|
||||
|
||||
cfdfield_t = NDArray[complexfloating]
|
||||
cfdfield_t = NewType('cfdfield_t', NDArray[complexfloating])
|
||||
type cfdfield = cfdfield_t | NDArray[complexfloating]
|
||||
"""Complex vector field with shape (3, X, Y, Z) (e.g. `[E_x, E_y, E_z]`)"""
|
||||
|
||||
vcfdfield_t = NDArray[complexfloating]
|
||||
vcfdfield_t = NewType('vcfdfield_t', NDArray[complexfloating])
|
||||
type vcfdfield = vcfdfield_t | NDArray[complexfloating]
|
||||
"""Linearized complex vector field (single vector of length 3*X*Y*Z)"""
|
||||
|
||||
|
||||
fdslice_t = NewType('fdslice_t', NDArray[floating])
|
||||
type fdslice = fdslice_t | NDArray[floating]
|
||||
"""Vector field slice with shape (3, X, Y) (e.g. `[E_x, E_y, E_z]` at a single Z position)"""
|
||||
|
||||
vfdslice_t = NewType('vfdslice_t', NDArray[floating])
|
||||
type vfdslice = vfdslice_t | NDArray[floating]
|
||||
"""Linearized vector field slice (single vector of length 3*X*Y)"""
|
||||
|
||||
cfdslice_t = NewType('cfdslice_t', NDArray[complexfloating])
|
||||
type cfdslice = cfdslice_t | NDArray[complexfloating]
|
||||
"""Complex vector field slice with shape (3, X, Y) (e.g. `[E_x, E_y, E_z]` at a single Z position)"""
|
||||
|
||||
vcfdslice_t = NewType('vcfdslice_t', NDArray[complexfloating])
|
||||
type vcfdslice = vcfdslice_t | NDArray[complexfloating]
|
||||
"""Linearized complex vector field slice (single vector of length 3*X*Y)"""
|
||||
|
||||
|
||||
fdfield2_t = NewType('fdfield2_t', NDArray[floating])
|
||||
type fdfield2 = fdfield2_t | NDArray[floating]
|
||||
"""2D Vector field with shape (2, X, Y) (e.g. `[E_x, E_y]`)"""
|
||||
|
||||
vfdfield2_t = NewType('vfdfield2_t', NDArray[floating])
|
||||
type vfdfield2 = vfdfield2_t | NDArray[floating]
|
||||
"""2D Linearized vector field (single vector of length 2*X*Y)"""
|
||||
|
||||
cfdfield2_t = NewType('cfdfield2_t', NDArray[complexfloating])
|
||||
type cfdfield2 = cfdfield2_t | NDArray[complexfloating]
|
||||
"""2D Complex vector field with shape (2, X, Y) (e.g. `[E_x, E_y]`)"""
|
||||
|
||||
vcfdfield2_t = NewType('vcfdfield2_t', NDArray[complexfloating])
|
||||
type vcfdfield2 = vcfdfield2_t | NDArray[complexfloating]
|
||||
"""2D Linearized complex vector field (single vector of length 2*X*Y)"""
|
||||
|
||||
|
||||
dx_lists_t = Sequence[Sequence[NDArray[floating | complexfloating]]]
|
||||
"""
|
||||
'dxes' datastructure which contains grid cell width information in the following format:
|
||||
@ -31,9 +70,23 @@ dx_lists_t = Sequence[Sequence[NDArray[floating | complexfloating]]]
|
||||
and `dy_h[0]` is the y-width of the `y=0` cells, as used when calculating dH/dy, etc.
|
||||
"""
|
||||
|
||||
dx_lists2_t = Sequence[Sequence[NDArray[floating | complexfloating]]]
|
||||
"""
|
||||
2D 'dxes' datastructure which contains grid cell width information in the following format:
|
||||
|
||||
[[[dx_e[0], dx_e[1], ...], [dy_e[0], ...]],
|
||||
[[dx_h[0], dx_h[1], ...], [dy_h[0], ...]]]
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
dx_lists_mut = MutableSequence[MutableSequence[NDArray[floating | complexfloating]]]
|
||||
"""Mutable version of `dx_lists_t`"""
|
||||
|
||||
dx_lists2_mut = MutableSequence[MutableSequence[NDArray[floating | complexfloating]]]
|
||||
"""Mutable version of `dx_lists2_t`"""
|
||||
|
||||
|
||||
fdfield_updater_t = Callable[..., fdfield_t]
|
||||
"""Convenience type for functions which take and return an fdfield_t"""
|
||||
|
||||
@ -7,9 +7,13 @@ Vectorized versions of the field use row-major (ie., C-style) ordering.
|
||||
from typing import overload
|
||||
from collections.abc import Sequence
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
||||
from .types import fdfield_t, vfdfield_t, cfdfield_t, vcfdfield_t
|
||||
from .types import (
|
||||
fdfield_t, vfdfield_t, cfdfield_t, vcfdfield_t,
|
||||
fdslice_t, vfdslice_t, cfdslice_t, vcfdslice_t,
|
||||
fdfield2_t, vfdfield2_t, cfdfield2_t, vcfdfield2_t,
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
@ -25,12 +29,28 @@ def vec(f: cfdfield_t) -> vcfdfield_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def vec(f: ArrayLike) -> vfdfield_t | vcfdfield_t:
|
||||
def vec(f: fdfield2_t) -> vfdfield2_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def vec(f: cfdfield2_t) -> vcfdfield2_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def vec(f: fdslice_t) -> vfdslice_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def vec(f: cfdslice_t) -> vcfdslice_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def vec(f: ArrayLike) -> NDArray:
|
||||
pass
|
||||
|
||||
def vec(
|
||||
f: fdfield_t | cfdfield_t | ArrayLike | None,
|
||||
) -> vfdfield_t | vcfdfield_t | None:
|
||||
f: fdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | None,
|
||||
) -> vfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | NDArray | None:
|
||||
"""
|
||||
Create a 1D ndarray from a vector field which spans a 1-3D region.
|
||||
|
||||
@ -45,7 +65,7 @@ def vec(
|
||||
"""
|
||||
if f is None:
|
||||
return None
|
||||
return numpy.ravel(f, order='C')
|
||||
return numpy.ravel(f, order='C') # type: ignore
|
||||
|
||||
|
||||
@overload
|
||||
@ -60,11 +80,31 @@ def unvec(v: vfdfield_t, shape: Sequence[int], nvdim: int = 3) -> fdfield_t:
|
||||
def unvec(v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3) -> cfdfield_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def unvec(v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3) -> fdfield2_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def unvec(v: vcfdfield2_t, shape: Sequence[int], nvdim: int = 3) -> cfdfield2_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def unvec(v: vfdslice_t, shape: Sequence[int], nvdim: int = 3) -> fdslice_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def unvec(v: vcfdslice_t, shape: Sequence[int], nvdim: int = 3) -> cfdslice_t:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def unvec(v: ArrayLike, shape: Sequence[int], nvdim: int = 3) -> NDArray:
|
||||
pass
|
||||
|
||||
def unvec(
|
||||
v: vfdfield_t | vcfdfield_t | None,
|
||||
v: vfdfield_t | vcfdfield_t | vfdfield2_t | vcfdfield2_t | vfdslice_t | vcfdslice_t | ArrayLike | None,
|
||||
shape: Sequence[int],
|
||||
nvdim: int = 3,
|
||||
) -> fdfield_t | cfdfield_t | None:
|
||||
) -> fdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | NDArray | None:
|
||||
"""
|
||||
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
|
||||
@ -82,5 +122,5 @@ def unvec(
|
||||
"""
|
||||
if v is None:
|
||||
return None
|
||||
return v.reshape((nvdim, *shape), order='C')
|
||||
return v.reshape((nvdim, *shape), order='C') # type: ignore
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import numpy
|
||||
|
||||
from ..fdmath import dx_lists_t, fdfield_t
|
||||
from ..fdmath import dx_lists_t, fdfield_t, fdfield
|
||||
from ..fdmath.functional import deriv_back
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ from ..fdmath.functional import deriv_back
|
||||
|
||||
|
||||
def poynting(
|
||||
e: fdfield_t,
|
||||
h: fdfield_t,
|
||||
e: fdfield,
|
||||
h: fdfield,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
r"""
|
||||
@ -84,14 +84,14 @@ def poynting(
|
||||
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 fdfield_t(s)
|
||||
|
||||
|
||||
def poynting_divergence(
|
||||
s: fdfield_t | None = None,
|
||||
s: fdfield | None = None,
|
||||
*,
|
||||
e: fdfield_t | None = None,
|
||||
h: fdfield_t | None = None,
|
||||
e: fdfield | None = None,
|
||||
h: fdfield | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -122,15 +122,15 @@ def poynting_divergence(
|
||||
|
||||
Dx, Dy, Dz = deriv_back()
|
||||
ds = Dx(s[0]) + Dy(s[1]) + Dz(s[2])
|
||||
return ds
|
||||
return fdfield_t(ds)
|
||||
|
||||
|
||||
def energy_hstep(
|
||||
e0: fdfield_t,
|
||||
h1: fdfield_t,
|
||||
e2: fdfield_t,
|
||||
epsilon: fdfield_t | None = None,
|
||||
mu: fdfield_t | None = None,
|
||||
e0: fdfield,
|
||||
h1: fdfield,
|
||||
e2: fdfield,
|
||||
epsilon: fdfield | None = None,
|
||||
mu: fdfield | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -150,15 +150,15 @@ def energy_hstep(
|
||||
Energy, at the time of the H-field `h1`.
|
||||
"""
|
||||
u = dxmul(e0 * e2, h1 * h1, epsilon, mu, dxes)
|
||||
return u
|
||||
return fdfield_t(u)
|
||||
|
||||
|
||||
def energy_estep(
|
||||
h0: fdfield_t,
|
||||
e1: fdfield_t,
|
||||
h2: fdfield_t,
|
||||
epsilon: fdfield_t | None = None,
|
||||
mu: fdfield_t | None = None,
|
||||
h0: fdfield,
|
||||
e1: fdfield,
|
||||
h2: fdfield,
|
||||
epsilon: fdfield | None = None,
|
||||
mu: fdfield | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -178,17 +178,17 @@ def energy_estep(
|
||||
Energy, at the time of the E-field `e1`.
|
||||
"""
|
||||
u = dxmul(e1 * e1, h0 * h2, epsilon, mu, dxes)
|
||||
return u
|
||||
return fdfield_t(u)
|
||||
|
||||
|
||||
def delta_energy_h2e(
|
||||
dt: float,
|
||||
e0: fdfield_t,
|
||||
h1: fdfield_t,
|
||||
e2: fdfield_t,
|
||||
h3: fdfield_t,
|
||||
epsilon: fdfield_t | None = None,
|
||||
mu: fdfield_t | None = None,
|
||||
e0: fdfield,
|
||||
h1: fdfield,
|
||||
e2: fdfield,
|
||||
h3: fdfield,
|
||||
epsilon: fdfield | None = None,
|
||||
mu: fdfield | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -211,17 +211,17 @@ def delta_energy_h2e(
|
||||
de = e2 * (e2 - e0) / dt
|
||||
dh = h1 * (h3 - h1) / dt
|
||||
du = dxmul(de, dh, epsilon, mu, dxes)
|
||||
return du
|
||||
return fdfield_t(du)
|
||||
|
||||
|
||||
def delta_energy_e2h(
|
||||
dt: float,
|
||||
h0: fdfield_t,
|
||||
e1: fdfield_t,
|
||||
h2: fdfield_t,
|
||||
e3: fdfield_t,
|
||||
epsilon: fdfield_t | None = None,
|
||||
mu: fdfield_t | None = None,
|
||||
h0: fdfield,
|
||||
e1: fdfield,
|
||||
h2: fdfield,
|
||||
e3: fdfield,
|
||||
epsilon: fdfield | None = None,
|
||||
mu: fdfield | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -244,12 +244,12 @@ def delta_energy_e2h(
|
||||
de = e1 * (e3 - e1) / dt
|
||||
dh = h2 * (h2 - h0) / dt
|
||||
du = dxmul(de, dh, epsilon, mu, dxes)
|
||||
return du
|
||||
return fdfield_t(du)
|
||||
|
||||
|
||||
def delta_energy_j(
|
||||
j0: fdfield_t,
|
||||
e1: fdfield_t,
|
||||
j0: fdfield,
|
||||
e1: fdfield,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
"""
|
||||
@ -267,14 +267,14 @@ def delta_energy_j(
|
||||
* dxes[0][0][:, None, None]
|
||||
* dxes[0][1][None, :, None]
|
||||
* dxes[0][2][None, None, :])
|
||||
return du
|
||||
return fdfield_t(du)
|
||||
|
||||
|
||||
def dxmul(
|
||||
ee: fdfield_t,
|
||||
hh: fdfield_t,
|
||||
epsilon: fdfield_t | float | None = None,
|
||||
mu: fdfield_t | float | None = None,
|
||||
ee: fdfield,
|
||||
hh: fdfield,
|
||||
epsilon: fdfield | float | None = None,
|
||||
mu: fdfield | float | None = None,
|
||||
dxes: dx_lists_t | None = None,
|
||||
) -> fdfield_t:
|
||||
if epsilon is None:
|
||||
@ -292,4 +292,4 @@ def dxmul(
|
||||
* dxes[1][0][:, None, None]
|
||||
* dxes[1][1][None, :, None]
|
||||
* dxes[1][2][None, None, :])
|
||||
return result
|
||||
return fdfield_t(result)
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from typing import Callable
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
|
||||
import numpy
|
||||
@ -95,10 +94,10 @@ def ricker_pulse(
|
||||
logger.warning('meanas.fdtd.misc functions are still very WIP!') # TODO
|
||||
omega = 2 * pi / wl
|
||||
freq = 1 / wl
|
||||
r0 = omega / 2
|
||||
# r0 = omega / 2
|
||||
|
||||
from scipy.optimize import root_scalar
|
||||
delay_results = root_scalar(lambda xx: (1 - omega * omega * tt * tt / 2) * numpy.exp(-omega * omega / 4 * tt * tt) - turn_on, x0=0, x1=-2 / omega)
|
||||
delay_results = root_scalar(lambda tt: (1 - omega * omega * tt * tt / 2) * numpy.exp(-omega * omega / 4 * tt * tt) - turn_on, x0=0, x1=-2 / omega)
|
||||
delay = delay_results.root
|
||||
delay = numpy.ceil(delay * freq) / freq # force delay to integer number of periods to maintain phase
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ from copy import deepcopy
|
||||
import numpy
|
||||
from numpy.typing import NDArray, DTypeLike
|
||||
|
||||
from ..fdmath import fdfield_t, dx_lists_t
|
||||
from ..fdmath import fdfield, fdfield_t, dx_lists_t
|
||||
from ..fdmath.functional import deriv_forward, deriv_back
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ def updates_with_cpml(
|
||||
cpml_params: Sequence[Sequence[dict[str, Any] | None]],
|
||||
dt: float,
|
||||
dxes: dx_lists_t,
|
||||
epsilon: fdfield_t,
|
||||
epsilon: fdfield,
|
||||
*,
|
||||
dtype: DTypeLike = numpy.float32,
|
||||
) -> tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None],
|
||||
|
||||
@ -6,7 +6,7 @@ from numpy.typing import NDArray
|
||||
#from numpy.testing import assert_allclose, assert_array_equal
|
||||
|
||||
from .. import fdfd
|
||||
from ..fdmath import vec, unvec
|
||||
from ..fdmath import vec, unvec, vcfdfield, vfdfield, dx_lists_t
|
||||
from .utils import assert_close # , assert_fields_close
|
||||
from .conftest import FixtureRequest
|
||||
|
||||
@ -102,16 +102,16 @@ def j_distribution(
|
||||
@dataclasses.dataclass()
|
||||
class FDResult:
|
||||
shape: tuple[int, ...]
|
||||
dxes: list[list[NDArray[numpy.float64]]]
|
||||
epsilon: NDArray[numpy.float64]
|
||||
dxes: dx_lists_t
|
||||
epsilon: vfdfield
|
||||
omega: complex
|
||||
j: NDArray[numpy.complex128]
|
||||
e: NDArray[numpy.complex128]
|
||||
pmc: NDArray[numpy.float64] | None
|
||||
pec: NDArray[numpy.float64] | None
|
||||
j: vcfdfield
|
||||
e: vcfdfield
|
||||
pmc: vfdfield | None
|
||||
pec: vfdfield | None
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def sim(
|
||||
request: FixtureRequest,
|
||||
shape: tuple[int, ...],
|
||||
@ -141,11 +141,11 @@ def sim(
|
||||
j_vec = vec(j_distribution)
|
||||
eps_vec = vec(epsilon)
|
||||
e_vec = fdfd.solvers.generic(
|
||||
J=j_vec,
|
||||
omega=omega,
|
||||
dxes=dxes,
|
||||
epsilon=eps_vec,
|
||||
matrix_solver_opts={'atol': 1e-15, 'rtol': 1e-11},
|
||||
J = j_vec,
|
||||
omega = omega,
|
||||
dxes = dxes,
|
||||
epsilon = eps_vec,
|
||||
matrix_solver_opts = dict(atol=1e-15, rtol=1e-11),
|
||||
)
|
||||
e = unvec(e_vec, shape[1:])
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ from numpy.typing import NDArray
|
||||
from numpy.testing import assert_allclose
|
||||
|
||||
from .. import fdfd
|
||||
from ..fdmath import vec, unvec, dx_lists_mut
|
||||
from ..fdmath import vec, unvec, dx_lists_mut, vfdfield, cfdfield_t
|
||||
#from .utils import assert_close, assert_fields_close
|
||||
from .test_fdfd import FDResult
|
||||
from .conftest import FixtureRequest
|
||||
@ -70,15 +70,15 @@ def src_polarity(request: FixtureRequest) -> int:
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def j_distribution(
|
||||
request: FixtureRequest,
|
||||
shape: tuple[int, ...],
|
||||
epsilon: NDArray[numpy.float64],
|
||||
epsilon: vfdfield,
|
||||
dxes: dx_lists_mut,
|
||||
omega: float,
|
||||
src_polarity: int,
|
||||
) -> NDArray[numpy.complex128]:
|
||||
) -> cfdfield_t:
|
||||
j = numpy.zeros(shape, dtype=complex)
|
||||
|
||||
dim = numpy.where(numpy.array(shape[1:]) > 1)[0][0] # Propagation axis
|
||||
@ -109,7 +109,7 @@ def j_distribution(
|
||||
return j
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def epsilon(
|
||||
request: FixtureRequest,
|
||||
shape: tuple[int, ...],
|
||||
@ -144,7 +144,7 @@ def dxes(
|
||||
return dxes
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def sim(
|
||||
request: FixtureRequest,
|
||||
shape: tuple[int, ...],
|
||||
|
||||
@ -189,7 +189,7 @@ def j_distribution(
|
||||
return j
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def sim(
|
||||
request: FixtureRequest,
|
||||
shape: tuple[int, ...],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user