modernize type annotations

master
Jan Petykiewicz 1 year ago
parent 7009e505e7
commit 44465f1bc9

@ -1,7 +1,7 @@
""" """
Solvers for eigenvalue / eigenvector problems Solvers for eigenvalue / eigenvector problems
""" """
from typing import Tuple, Callable, Optional, Union from typing import Callable
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray, ArrayLike
from numpy.linalg import norm from numpy.linalg import norm
@ -11,9 +11,9 @@ import scipy.sparse.linalg as spalg # type: ignore
def power_iteration( def power_iteration(
operator: sparse.spmatrix, operator: sparse.spmatrix,
guess_vector: Optional[NDArray[numpy.complex128]] = None, guess_vector: NDArray[numpy.complex128] | None = None,
iterations: int = 20, iterations: int = 20,
) -> Tuple[complex, NDArray[numpy.complex128]]: ) -> tuple[complex, NDArray[numpy.complex128]]:
""" """
Use power iteration to estimate the dominant eigenvector of a matrix. Use power iteration to estimate the dominant eigenvector of a matrix.
@ -40,12 +40,12 @@ def power_iteration(
def rayleigh_quotient_iteration( def rayleigh_quotient_iteration(
operator: Union[sparse.spmatrix, spalg.LinearOperator], operator: sparse.spmatrix | spalg.LinearOperator,
guess_vector: NDArray[numpy.complex128], guess_vector: NDArray[numpy.complex128],
iterations: int = 40, iterations: int = 40,
tolerance: float = 1e-13, tolerance: float = 1e-13,
solver: Optional[Callable[..., NDArray[numpy.complex128]]] = None, solver: Callable[..., NDArray[numpy.complex128]] | None = None,
) -> Tuple[complex, NDArray[numpy.complex128]]: ) -> tuple[complex, NDArray[numpy.complex128]]:
""" """
Use Rayleigh quotient iteration to refine an eigenvector guess. Use Rayleigh quotient iteration to refine an eigenvector guess.
@ -73,14 +73,14 @@ def rayleigh_quotient_iteration(
except TypeError: except TypeError:
def shift(eigval: float) -> spalg.LinearOperator: def shift(eigval: float) -> spalg.LinearOperator:
return spalg.LinearOperator( return spalg.LinearOperator(
shape=operator.shape, shape=operator.shape,
dtype=operator.dtype, dtype=operator.dtype,
matvec=lambda v: eigval * v, matvec=lambda v: eigval * v,
) )
if solver is None: if solver is None:
def solver(A: spalg.LinearOperator, b: ArrayLike) -> NDArray[numpy.complex128]: def solver(A: spalg.LinearOperator, b: ArrayLike) -> NDArray[numpy.complex128]:
return spalg.bicgstab(A, b)[0] return spalg.bicgstab(A, b)[0]
assert(solver is not None) assert solver is not None
v = numpy.squeeze(guess_vector) v = numpy.squeeze(guess_vector)
v /= norm(v) v /= norm(v)
@ -96,10 +96,10 @@ def rayleigh_quotient_iteration(
def signed_eigensolve( def signed_eigensolve(
operator: Union[sparse.spmatrix, spalg.LinearOperator], operator: sparse.spmatrix | spalg.LinearOperator,
how_many: int, how_many: int,
negative: bool = False, negative: bool = False,
) -> Tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]: ) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
""" """
Find the largest-magnitude positive-only (or negative-only) eigenvalues and Find the largest-magnitude positive-only (or negative-only) eigenvalues and
eigenvectors of the provided matrix. eigenvectors of the provided matrix.

@ -94,7 +94,7 @@ This module contains functions for generating and solving the
''' '''
from typing import Tuple, Callable, Any, List, Optional, cast, Union, Sequence from typing import Callable, Any, cast, Sequence
import logging import logging
import numpy import numpy
from numpy import pi, real, trace from numpy import pi, real, trace
@ -140,7 +140,7 @@ def generate_kmn(
k0: ArrayLike, k0: ArrayLike,
G_matrix: ArrayLike, G_matrix: ArrayLike,
shape: Sequence[int], shape: Sequence[int],
) -> Tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]: ) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]:
""" """
Generate a (k, m, n) orthogonal basis for each k-vector in the simulation grid. Generate a (k, m, n) orthogonal basis for each k-vector in the simulation grid.
@ -155,8 +155,9 @@ def generate_kmn(
All are given in the xyz basis (e.g. `|k|[0,0,0] = norm(G_matrix @ k0)`). All are given in the xyz basis (e.g. `|k|[0,0,0] = norm(G_matrix @ k0)`).
""" """
k0 = numpy.array(k0) k0 = numpy.array(k0)
G_matrix = numpy.array(G_matrix, copy=False)
Gi_grids = numpy.meshgrid(*(fftfreq(n, 1 / n) for n in shape[:3]), indexing='ij') Gi_grids = numpy.array(numpy.meshgrid(*(fftfreq(n, 1 / n) for n in shape[:3]), indexing='ij'))
Gi = numpy.moveaxis(Gi_grids, 0, -1) Gi = numpy.moveaxis(Gi_grids, 0, -1)
k_G = k0[None, None, None, :] - Gi k_G = k0[None, None, None, :] - Gi
@ -183,7 +184,7 @@ def maxwell_operator(
k0: ArrayLike, k0: ArrayLike,
G_matrix: ArrayLike, G_matrix: ArrayLike,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None mu: fdfield_t | None = None
) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]: ) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]:
""" """
Generate the Maxwell operator Generate the Maxwell operator
@ -237,7 +238,7 @@ def maxwell_operator(
# cross product and transform into xyz basis # cross product and transform into xyz basis
d_xyz = (n * hin_m d_xyz = (n * hin_m
- m * hin_n) * k_mag - m * hin_n) * k_mag # noqa: E128
# divide by epsilon # divide by epsilon
temp = ifftn(d_xyz, axes=range(3)) # reuses d_xyz if using pyfftw temp = ifftn(d_xyz, axes=range(3)) # reuses d_xyz if using pyfftw
@ -253,7 +254,7 @@ def maxwell_operator(
else: else:
# transform from mn to xyz # transform from mn to xyz
b_xyz = (m * b_m[:, :, :, None] b_xyz = (m * b_m[:, :, :, None]
+ n * b_n[:, :, :, None]) + n * b_n[:, :, :, None]) # noqa: E128
# divide by mu # divide by mu
temp = ifftn(b_xyz, axes=range(3)) temp = ifftn(b_xyz, axes=range(3))
@ -302,7 +303,7 @@ def hmn_2_exyz(
def operator(h: NDArray[numpy.complex128]) -> cfdfield_t: def operator(h: NDArray[numpy.complex128]) -> cfdfield_t:
hin_m, hin_n = [hi.reshape(shape) for hi in numpy.split(h, 2)] hin_m, hin_n = [hi.reshape(shape) for hi in numpy.split(h, 2)]
d_xyz = (n * hin_m d_xyz = (n * hin_m
- m * hin_n) * k_mag - m * hin_n) * k_mag # noqa: E128
# divide by epsilon # divide by epsilon
return numpy.array([ei for ei in numpy.moveaxis(ifftn(d_xyz, axes=range(3)) / epsilon, 3, 0)]) # TODO avoid copy return numpy.array([ei for ei in numpy.moveaxis(ifftn(d_xyz, axes=range(3)) / epsilon, 3, 0)]) # TODO avoid copy
@ -340,7 +341,7 @@ def hmn_2_hxyz(
def operator(h: NDArray[numpy.complex128]) -> cfdfield_t: def operator(h: NDArray[numpy.complex128]) -> cfdfield_t:
hin_m, hin_n = [hi.reshape(shape) for hi in numpy.split(h, 2)] hin_m, hin_n = [hi.reshape(shape) for hi in numpy.split(h, 2)]
h_xyz = (m * hin_m h_xyz = (m * hin_m
+ n * hin_n) + n * hin_n) # noqa: E128
return numpy.array([ifftn(hi) for hi in numpy.moveaxis(h_xyz, 3, 0)]) return numpy.array([ifftn(hi) for hi in numpy.moveaxis(h_xyz, 3, 0)])
return operator return operator
@ -350,7 +351,7 @@ def inverse_maxwell_operator_approx(
k0: ArrayLike, k0: ArrayLike,
G_matrix: ArrayLike, G_matrix: ArrayLike,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]: ) -> Callable[[NDArray[numpy.complex128]], NDArray[numpy.complex128]]:
""" """
Generate an approximate inverse of the Maxwell operator, Generate an approximate inverse of the Maxwell operator,
@ -400,7 +401,7 @@ def inverse_maxwell_operator_approx(
else: else:
# transform from mn to xyz # transform from mn to xyz
h_xyz = (m * hin_m[:, :, :, None] h_xyz = (m * hin_m[:, :, :, None]
+ n * hin_n[:, :, :, None]) + n * hin_n[:, :, :, None]) # noqa: E128
# multiply by mu # multiply by mu
temp = ifftn(h_xyz, axes=range(3)) temp = ifftn(h_xyz, axes=range(3))
@ -413,7 +414,7 @@ def inverse_maxwell_operator_approx(
# cross product and transform into xyz basis # cross product and transform into xyz basis
e_xyz = (n * b_m e_xyz = (n * b_m
- m * b_n) / k_mag - m * b_n) / k_mag # noqa: E128
# multiply by epsilon # multiply by epsilon
temp = ifftn(e_xyz, axes=range(3)) temp = ifftn(e_xyz, axes=range(3))
@ -436,13 +437,13 @@ def find_k(
direction: ArrayLike, direction: ArrayLike,
G_matrix: ArrayLike, G_matrix: ArrayLike,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
band: int = 0, band: int = 0,
k_bounds: Tuple[float, float] = (0, 0.5), k_bounds: tuple[float, float] = (0, 0.5),
k_guess: Optional[float] = None, k_guess: float | None = None,
solve_callback: Optional[Callable[..., None]] = None, solve_callback: Callable[..., None] | None = None,
iter_callback: Optional[Callable[..., None]] = None, iter_callback: Callable[..., None] | None = None,
) -> Tuple[float, float, NDArray[numpy.complex128], NDArray[numpy.complex128]]: ) -> tuple[float, float, NDArray[numpy.complex128], NDArray[numpy.complex128]]:
""" """
Search for a bloch vector that has a given frequency. Search for a bloch vector that has a given frequency.
@ -503,13 +504,13 @@ def eigsolve(
k0: ArrayLike, k0: ArrayLike,
G_matrix: ArrayLike, G_matrix: ArrayLike,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
tolerance: float = 1e-20, tolerance: float = 1e-20,
max_iters: int = 10000, max_iters: int = 10000,
reset_iters: int = 100, reset_iters: int = 100,
y0: Optional[ArrayLike] = None, y0: ArrayLike | None = None,
callback: Optional[Callable[..., None]] = None, callback: Callable[..., None] | None = None,
) -> Tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]: ) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
""" """
Find the first (lowest-frequency) num_modes eigenmodes with Bloch wavevector Find the first (lowest-frequency) num_modes eigenmodes with Bloch wavevector
k0 of the specified structure. k0 of the specified structure.
@ -555,6 +556,7 @@ def eigsolve(
#prev_theta = 0.5 #prev_theta = 0.5
D = numpy.zeros(shape=y_shape, dtype=complex) D = numpy.zeros(shape=y_shape, dtype=complex)
Z: NDArray[numpy.complex128]
if y0 is None: if y0 is None:
Z = numpy.random.rand(*y_shape) + 1j * numpy.random.rand(*y_shape) Z = numpy.random.rand(*y_shape) + 1j * numpy.random.rand(*y_shape)
else: else:
@ -589,11 +591,12 @@ def eigsolve(
E_signed = real(trace(ZtAZU)) E_signed = real(trace(ZtAZU))
sgn = numpy.sign(E_signed) sgn = numpy.sign(E_signed)
E = numpy.abs(E_signed) E = numpy.abs(E_signed)
G = (AZ @ U - Z @ U @ ZtAZU) * sgn # G = AZU projected onto the space orthonormal to Z G = (AZ @ U - Z @ U @ ZtAZU) * sgn # G = AZU projected onto the space orthonormal to Z
# via (1 - ZUZt) # via (1 - ZUZt)
if i > 0 and abs(E - prev_E) < tolerance * 0.5 * (E + prev_E + 1e-7): if i > 0 and abs(E - prev_E) < tolerance * 0.5 * (E + prev_E + 1e-7):
logger.info('Optimization succeded: ' logger.info(
'Optimization succeded: '
f'[change in trace] {abs(E - prev_E)} - 5e-8 ' f'[change in trace] {abs(E - prev_E)} - 5e-8 '
f'< {tolerance} [tolerance] * {(E + prev_E) / 2} [value of trace]' f'< {tolerance} [tolerance] * {(E + prev_E) / 2} [value of trace]'
) )
@ -635,7 +638,7 @@ def eigsolve(
symZtD = _symmetrize(Zt @ D) symZtD = _symmetrize(Zt @ D)
symZtAD = _symmetrize(Zt @ AD) symZtAD = _symmetrize(Zt @ AD)
Qi_memo: List[Optional[float]] = [None, None] Qi_memo: list[float | None] = [None, None]
def Qi_func(theta: float) -> float: def Qi_func(theta: float) -> float:
nonlocal Qi_memo nonlocal Qi_memo
@ -659,8 +662,8 @@ def eigsolve(
else: else:
raise Exception('Inexplicable singularity in trace_func') raise Exception('Inexplicable singularity in trace_func')
Qi_memo[0] = theta Qi_memo[0] = theta
Qi_memo[1] = Qi Qi_memo[1] = cast(float, Qi)
return Qi return cast(float, Qi)
def trace_func(theta: float) -> float: def trace_func(theta: float) -> float:
c = numpy.cos(theta) c = numpy.cos(theta)
@ -772,7 +775,7 @@ def linmin(x_guess, f0, df0, x_max, f_tol=0.1, df_tol=min(tolerance, 1e-6), x_to
def _rtrace_AtB( def _rtrace_AtB(
A: NDArray[numpy.complex128], A: NDArray[numpy.complex128],
B: Union[NDArray[numpy.complex128], float], B: NDArray[numpy.complex128] | float,
) -> float: ) -> float:
return real(numpy.sum(A.conj() * B)) return real(numpy.sum(A.conj() * B))

@ -1,7 +1,7 @@
""" """
Functions for performing near-to-farfield transformation (and the reverse). Functions for performing near-to-farfield transformation (and the reverse).
""" """
from typing import Dict, List, Any, Union, Sequence from typing import Any, Sequence, cast
import numpy import numpy
from numpy.fft import fft2, fftshift, fftfreq, ifft2, ifftshift from numpy.fft import fft2, fftshift, fftfreq, ifft2, ifftshift
from numpy import pi from numpy import pi
@ -14,8 +14,8 @@ def near_to_farfield(
H_near: cfdfield_t, H_near: cfdfield_t,
dx: float, dx: float,
dy: float, dy: float,
padded_size: Union[List[int], int, None] = None padded_size: list[int] | int | None = None
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Compute the farfield, i.e. the distribution of the fields after propagation Compute the farfield, i.e. the distribution of the fields after propagation
through several wavelengths of uniform medium. through several wavelengths of uniform medium.
@ -62,14 +62,15 @@ def near_to_farfield(
padded_size = (2**numpy.ceil(numpy.log2(s))).astype(int) padded_size = (2**numpy.ceil(numpy.log2(s))).astype(int)
if not hasattr(padded_size, '__len__'): if not hasattr(padded_size, '__len__'):
padded_size = (padded_size, padded_size) # type: ignore # checked if sequence padded_size = (padded_size, padded_size) # type: ignore # checked if sequence
padded_shape = cast(Sequence[int], padded_size)
En_fft = [fftshift(fft2(fftshift(Eni), s=padded_size)) for Eni in E_near] En_fft = [fftshift(fft2(fftshift(Eni), s=padded_shape)) for Eni in E_near]
Hn_fft = [fftshift(fft2(fftshift(Hni), s=padded_size)) for Hni in H_near] Hn_fft = [fftshift(fft2(fftshift(Hni), s=padded_shape)) for Hni in H_near]
# Propagation vectors kx, ky # Propagation vectors kx, ky
k = 2 * pi k = 2 * pi
kxx = 2 * pi * fftshift(fftfreq(padded_size[0], dx)) kxx = 2 * pi * fftshift(fftfreq(padded_shape[0], dx))
kyy = 2 * pi * fftshift(fftfreq(padded_size[1], dy)) kyy = 2 * pi * fftshift(fftfreq(padded_shape[1], dy))
kx, ky = numpy.meshgrid(kxx, kyy, indexing='ij') kx, ky = numpy.meshgrid(kxx, kyy, indexing='ij')
kxy2 = kx * kx + ky * ky kxy2 = kx * kx + ky * ky
@ -85,14 +86,14 @@ def near_to_farfield(
# Normalized vector potentials N, L # Normalized vector potentials N, L
N = [-Hn_fft[1] * cos_phi * cos_th + Hn_fft[0] * cos_phi * sin_th, N = [-Hn_fft[1] * cos_phi * cos_th + Hn_fft[0] * cos_phi * sin_th,
Hn_fft[1] * sin_th + Hn_fft[0] * cos_th] Hn_fft[1] * sin_th + Hn_fft[0] * cos_th] # noqa: E127
L = [ En_fft[1] * cos_phi * cos_th - En_fft[0] * cos_phi * sin_th, L = [ En_fft[1] * cos_phi * cos_th - En_fft[0] * cos_phi * sin_th,
-En_fft[1] * sin_th - En_fft[0] * cos_th] -En_fft[1] * sin_th - En_fft[0] * cos_th] # noqa: E128
E_far = [-L[1] - N[0], E_far = [-L[1] - N[0],
L[0] - N[1]] L[0] - N[1]] # noqa: E127
H_far = [-E_far[1], H_far = [-E_far[1],
E_far[0]] E_far[0]] # noqa: E127
theta = numpy.arctan2(ky, kx) theta = numpy.arctan2(ky, kx)
phi = numpy.arccos(cos_phi) phi = numpy.arccos(cos_phi)
@ -126,8 +127,8 @@ def far_to_nearfield(
H_far: cfdfield_t, H_far: cfdfield_t,
dkx: float, dkx: float,
dky: float, dky: float,
padded_size: Union[List[int], int, None] = None padded_size: list[int] | int | None = None
) -> Dict[str, Any]: ) -> dict[str, Any]:
""" """
Compute the farfield, i.e. the distribution of the fields after propagation Compute the farfield, i.e. the distribution of the fields after propagation
through several wavelengths of uniform medium. through several wavelengths of uniform medium.
@ -170,6 +171,7 @@ def far_to_nearfield(
padded_size = (2 ** numpy.ceil(numpy.log2(s))).astype(int) padded_size = (2 ** numpy.ceil(numpy.log2(s))).astype(int)
if not hasattr(padded_size, '__len__'): if not hasattr(padded_size, '__len__'):
padded_size = (padded_size, padded_size) # type: ignore # checked if sequence padded_size = (padded_size, padded_size) # type: ignore # checked if sequence
padded_shape = cast(Sequence[int], padded_size)
k = 2 * pi k = 2 * pi
kxs = fftshift(fftfreq(s[0], 1 / (s[0] * dkx))) kxs = fftshift(fftfreq(s[0], 1 / (s[0] * dkx)))
@ -203,9 +205,9 @@ def far_to_nearfield(
# Normalized vector potentials N, L # Normalized vector potentials N, L
L = [0.5 * E_far[1], L = [0.5 * E_far[1],
-0.5 * E_far[0]] -0.5 * E_far[0]] # noqa: E128
N = [L[1], N = [L[1],
-L[0]] -L[0]] # noqa: E128
En_fft = [-( L[0] * sin_th + L[1] * cos_phi * cos_th) / cos_phi, En_fft = [-( L[0] * sin_th + L[1] * cos_phi * cos_th) / cos_phi,
-(-L[0] * cos_th + L[1] * cos_phi * sin_th) / cos_phi] -(-L[0] * cos_th + L[1] * cos_phi * sin_th) / cos_phi]
@ -217,8 +219,8 @@ def far_to_nearfield(
En_fft[i][cos_phi == 0] = 0 En_fft[i][cos_phi == 0] = 0
Hn_fft[i][cos_phi == 0] = 0 Hn_fft[i][cos_phi == 0] = 0
E_near = [ifftshift(ifft2(ifftshift(Ei), s=padded_size)) for Ei in En_fft] E_near = [ifftshift(ifft2(ifftshift(Ei), s=padded_shape)) for Ei in En_fft]
H_near = [ifftshift(ifft2(ifftshift(Hi), s=padded_size)) for Hi in Hn_fft] H_near = [ifftshift(ifft2(ifftshift(Hi), s=padded_shape)) for Hi in Hn_fft]
dx = 2 * pi / (s[0] * dkx) dx = 2 * pi / (s[0] * dkx)
dy = 2 * pi / (s[0] * dky) dy = 2 * pi / (s[0] * dky)

@ -5,7 +5,7 @@ Functional versions of many FDFD operators. These can be useful for performing
The functions generated here expect `cfdfield_t` inputs with shape (3, X, Y, Z), The functions generated here expect `cfdfield_t` inputs with shape (3, X, Y, Z),
e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z) e.g. E = [E_x, E_y, E_z] where each (complex) component has shape (X, Y, Z)
""" """
from typing import Callable, Tuple, Optional from typing import Callable
import numpy import numpy
from ..fdmath import dx_lists_t, fdfield_t, cfdfield_t, cfdfield_updater_t from ..fdmath import dx_lists_t, fdfield_t, cfdfield_t, cfdfield_updater_t
@ -19,7 +19,7 @@ def e_full(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> cfdfield_updater_t: ) -> cfdfield_updater_t:
""" """
Wave operator for use with E-field. See `operators.e_full` for details. Wave operator for use with E-field. See `operators.e_full` for details.
@ -55,8 +55,8 @@ def eh_full(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> Callable[[cfdfield_t, cfdfield_t], Tuple[cfdfield_t, cfdfield_t]]: ) -> Callable[[cfdfield_t, cfdfield_t], tuple[cfdfield_t, cfdfield_t]]:
""" """
Wave operator for full (both E and H) field representation. Wave operator for full (both E and H) field representation.
See `operators.eh_full`. See `operators.eh_full`.
@ -74,11 +74,11 @@ def eh_full(
ch = curl_back(dxes[1]) ch = curl_back(dxes[1])
ce = curl_forward(dxes[0]) ce = curl_forward(dxes[0])
def op_1(e: cfdfield_t, h: cfdfield_t) -> Tuple[cfdfield_t, cfdfield_t]: def op_1(e: cfdfield_t, h: cfdfield_t) -> tuple[cfdfield_t, cfdfield_t]:
return (ch(h) - 1j * omega * epsilon * e, return (ch(h) - 1j * omega * epsilon * e,
ce(e) + 1j * omega * h) ce(e) + 1j * omega * h)
def op_mu(e: cfdfield_t, h: cfdfield_t) -> Tuple[cfdfield_t, cfdfield_t]: def op_mu(e: cfdfield_t, h: cfdfield_t) -> tuple[cfdfield_t, cfdfield_t]:
return (ch(h) - 1j * omega * epsilon * e, return (ch(h) - 1j * omega * epsilon * e,
ce(e) + 1j * omega * mu * h) # type: ignore # mu=None ok ce(e) + 1j * omega * mu * h) # type: ignore # mu=None ok
@ -91,7 +91,7 @@ def eh_full(
def e2h( def e2h(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> cfdfield_updater_t: ) -> cfdfield_updater_t:
""" """
Utility operator for converting the `E` field into the `H` field. Utility operator for converting the `E` field into the `H` field.
@ -123,7 +123,7 @@ def e2h(
def m2j( def m2j(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> cfdfield_updater_t: ) -> cfdfield_updater_t:
""" """
Utility operator for converting magnetic current `M` distribution Utility operator for converting magnetic current `M` distribution
@ -160,7 +160,7 @@ def e_tfsf_source(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> cfdfield_updater_t: ) -> cfdfield_updater_t:
""" """
Operator that turns an E-field distribution into a total-field/scattered-field Operator that turns an E-field distribution into a total-field/scattered-field

@ -27,7 +27,6 @@ The following operators are included:
- Cross product matrices - Cross product matrices
""" """
from typing import Tuple, Optional
import numpy import numpy
import scipy.sparse as sparse # type: ignore import scipy.sparse as sparse # type: ignore
@ -42,9 +41,9 @@ def e_full(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
pec: Optional[vfdfield_t] = None, pec: vfdfield_t | None = None,
pmc: Optional[vfdfield_t] = None, pmc: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Wave operator Wave operator
@ -99,7 +98,7 @@ def e_full(
def e_full_preconditioners( def e_full_preconditioners(
dxes: dx_lists_t, dxes: dx_lists_t,
) -> Tuple[sparse.spmatrix, sparse.spmatrix]: ) -> tuple[sparse.spmatrix, sparse.spmatrix]:
""" """
Left and right preconditioners `(Pl, Pr)` for symmetrizing the `e_full` wave operator. Left and right preconditioners `(Pl, Pr)` for symmetrizing the `e_full` wave operator.
@ -128,9 +127,9 @@ def h_full(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
pec: Optional[vfdfield_t] = None, pec: vfdfield_t | None = None,
pmc: Optional[vfdfield_t] = None, pmc: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Wave operator Wave operator
@ -185,9 +184,9 @@ def eh_full(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
pec: Optional[vfdfield_t] = None, pec: vfdfield_t | None = None,
pmc: Optional[vfdfield_t] = None, pmc: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Wave operator for `[E, H]` field representation. This operator implements Maxwell's Wave operator for `[E, H]` field representation. This operator implements Maxwell's
@ -254,8 +253,8 @@ def eh_full(
def e2h( def e2h(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
pmc: Optional[vfdfield_t] = None, pmc: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Utility operator for converting the E field into the H field. Utility operator for converting the E field into the H field.
@ -286,7 +285,7 @@ def e2h(
def m2j( def m2j(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Operator for converting a magnetic current M into an electric current J. Operator for converting a magnetic current M into an electric current J.
@ -368,7 +367,7 @@ def e_tfsf_source(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Operator that turns a desired E-field distribution into a Operator that turns a desired E-field distribution into a
@ -399,7 +398,7 @@ def e_boundary_source(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
periodic_mask_edges: bool = False, periodic_mask_edges: bool = False,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """

@ -2,10 +2,10 @@
Functions for creating stretched coordinate perfectly matched layer (PML) absorbers. Functions for creating stretched coordinate perfectly matched layer (PML) absorbers.
""" """
from typing import Sequence, Union, Callable, Optional, List from typing import Sequence, Callable
import numpy import numpy
from numpy.typing import ArrayLike, NDArray from numpy.typing import NDArray
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'
@ -43,8 +43,8 @@ def uniform_grid_scpml(
thicknesses: Sequence[int], thicknesses: Sequence[int],
omega: float, omega: float,
epsilon_effective: float = 1.0, epsilon_effective: float = 1.0,
s_function: Optional[s_function_t] = None, s_function: s_function_t | None = None,
) -> List[List[NDArray[numpy.float64]]]: ) -> list[list[NDArray[numpy.float64]]]:
""" """
Create dx arrays for a uniform grid with a cell width of 1 and a pml. Create dx arrays for a uniform grid with a cell width of 1 and a pml.
@ -86,7 +86,7 @@ def uniform_grid_scpml(
for k, th in enumerate(thicknesses): for k, th in enumerate(thicknesses):
s = shape[k] s = shape[k]
if th > 0: if th > 0:
sr = numpy.arange(s) sr = numpy.arange(s, dtype=numpy.float64)
dx_a[k] = 1 + 1j * s_function(ll(sr, s, th)) / s_correction dx_a[k] = 1 + 1j * s_function(ll(sr, s, th)) / s_correction
dx_b[k] = 1 + 1j * s_function(ll(sr + 0.5, s, th)) / s_correction dx_b[k] = 1 + 1j * s_function(ll(sr + 0.5, s, th)) / s_correction
else: else:
@ -96,14 +96,14 @@ def uniform_grid_scpml(
def stretch_with_scpml( def stretch_with_scpml(
dxes: List[List[NDArray[numpy.float64]]], dxes: list[list[NDArray[numpy.float64]]],
axis: int, axis: int,
polarity: int, polarity: int,
omega: float, omega: float,
epsilon_effective: float = 1.0, epsilon_effective: float = 1.0,
thickness: int = 10, thickness: int = 10,
s_function: Optional[s_function_t] = None, s_function: s_function_t | None = None,
) -> List[List[NDArray[numpy.float64]]]: ) -> list[list[NDArray[numpy.float64]]]:
""" """
Stretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis. Stretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis.

@ -178,7 +178,7 @@ to account for numerical dispersion if the result is introduced into a space wit
""" """
# TODO update module docs # TODO update module docs
from typing import List, Tuple, Optional, Any from typing import Any
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray, ArrayLike
from numpy.linalg import norm from numpy.linalg import norm
@ -196,7 +196,7 @@ def operator_e(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Waveguide operator of the form Waveguide operator of the form
@ -263,7 +263,7 @@ def operator_h(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Waveguide operator of the form Waveguide operator of the form
@ -333,9 +333,9 @@ def normalized_fields_e(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
prop_phase: float = 0, prop_phase: float = 0,
) -> Tuple[vcfdfield_t, vcfdfield_t]: ) -> tuple[vcfdfield_t, vcfdfield_t]:
""" """
Given a vector `e_xy` containing the vectorized E_x and E_y fields, Given a vector `e_xy` containing the vectorized E_x and E_y fields,
returns normalized, vectorized E and H fields for the system. returns normalized, vectorized E and H fields for the system.
@ -368,9 +368,9 @@ def normalized_fields_h(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
prop_phase: float = 0, prop_phase: float = 0,
) -> Tuple[vcfdfield_t, vcfdfield_t]: ) -> tuple[vcfdfield_t, vcfdfield_t]:
""" """
Given a vector `h_xy` containing the vectorized H_x and H_y fields, Given a vector `h_xy` containing the vectorized H_x and H_y fields,
returns normalized, vectorized E and H fields for the system. returns normalized, vectorized E and H fields for the system.
@ -403,9 +403,9 @@ def _normalized_fields(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
prop_phase: float = 0, prop_phase: float = 0,
) -> Tuple[vcfdfield_t, vcfdfield_t]: ) -> tuple[vcfdfield_t, vcfdfield_t]:
# TODO documentation # TODO documentation
shape = [s.size for s in dxes[0]] 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)] dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
@ -445,7 +445,7 @@ def exy2h(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None mu: vfdfield_t | None = None
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Operator which transforms the vector `e_xy` containing the vectorized E_x and E_y fields, Operator which transforms the vector `e_xy` containing the vectorized E_x and E_y fields,
@ -471,7 +471,7 @@ def hxy2e(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None mu: vfdfield_t | None = None
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields, Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields,
@ -495,7 +495,7 @@ def hxy2e(
def hxy2h( def hxy2h(
wavenumber: complex, wavenumber: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[vfdfield_t] = None mu: vfdfield_t | None = None
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields, Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields,
@ -564,7 +564,7 @@ def e2h(
wavenumber: complex, wavenumber: complex,
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
mu: Optional[vfdfield_t] = None mu: vfdfield_t | None = None
) -> sparse.spmatrix: ) -> sparse.spmatrix:
""" """
Returns an operator which, when applied to a vectorized E eigenfield, produces Returns an operator which, when applied to a vectorized E eigenfield, produces
@ -654,7 +654,7 @@ def h_err(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None mu: vfdfield_t | None = None
) -> float: ) -> float:
""" """
Calculates the relative error in the H field Calculates the relative error in the H field
@ -680,7 +680,7 @@ def h_err(
else: else:
op = ce @ eps_inv @ ch @ h - omega ** 2 * (mu * h) op = ce @ eps_inv @ ch @ h - omega ** 2 * (mu * h)
return norm(op) / norm(h) return float(norm(op) / norm(h))
def e_err( def e_err(
@ -689,7 +689,7 @@ def e_err(
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
) -> float: ) -> float:
""" """
Calculates the relative error in the E field Calculates the relative error in the E field
@ -714,17 +714,17 @@ def e_err(
mu_inv = sparse.diags(1 / mu) mu_inv = sparse.diags(1 / mu)
op = ch @ mu_inv @ ce @ e - omega ** 2 * (epsilon * e) op = ch @ mu_inv @ ce @ e - omega ** 2 * (epsilon * e)
return norm(op) / norm(e) return float(norm(op) / norm(e))
def solve_modes( def solve_modes(
mode_numbers: List[int], mode_numbers: list[int],
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
mu: Optional[vfdfield_t] = None, mu: vfdfield_t | None = None,
mode_margin: int = 2, mode_margin: int = 2,
) -> Tuple[NDArray[numpy.float64], NDArray[numpy.complex128]]: ) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]:
""" """
Given a 2D region, attempts to solve for the eigenmode with the specified mode numbers. Given a 2D region, attempts to solve for the eigenmode with the specified mode numbers.
@ -772,7 +772,7 @@ def solve_mode(
mode_number: int, mode_number: int,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Any,
) -> Tuple[vcfdfield_t, complex]: ) -> tuple[vcfdfield_t, complex]:
""" """
Wrapper around `solve_modes()` that solves for a single mode. Wrapper around `solve_modes()` that solves for a single mode.

@ -4,7 +4,7 @@ Tools for working with waveguide modes in 3D domains.
This module relies heavily on `waveguide_2d` and mostly just transforms This module relies heavily on `waveguide_2d` and mostly just transforms
its parameters into 2D equivalents and expands the results back into 3D. its parameters into 2D equivalents and expands the results back into 3D.
""" """
from typing import Dict, Optional, Sequence, Union, Any from typing import Sequence, Any
import numpy import numpy
from numpy.typing import NDArray from numpy.typing import NDArray
@ -20,8 +20,8 @@ def solve_mode(
polarity: int, polarity: int,
slices: Sequence[slice], slices: Sequence[slice],
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> Dict[str, Union[complex, NDArray[numpy.float_]]]: ) -> dict[str, complex | NDArray[numpy.float_]]:
""" """
Given a 3D grid, selects a slice from the grid and attempts to Given a 3D grid, selects a slice from the grid and attempts to
solve for an eigenmode propagating through that slice. solve for an eigenmode propagating through that slice.
@ -40,8 +40,8 @@ def solve_mode(
Returns: Returns:
``` ```
{ {
'E': List[NDArray[numpy.float_]], 'E': list[NDArray[numpy.float_]],
'H': List[NDArray[numpy.float_]], 'H': list[NDArray[numpy.float_]],
'wavenumber': complex, 'wavenumber': complex,
} }
``` ```
@ -63,7 +63,7 @@ def solve_mode(
dx_prop = 0.5 * dxab_forward.sum() dx_prop = 0.5 * dxab_forward.sum()
# Reduce to 2D and solve the 2D problem # Reduce to 2D and solve the 2D problem
args_2d: Dict[str, Any] = { args_2d: dict[str, Any] = {
'omega': omega, 'omega': omega,
'dxes': [[dx[i][slices[i]] for i in order[:2]] for dx in dxes], 'dxes': [[dx[i][slices[i]] for i in order[:2]] for dx in dxes],
'epsilon': vec([epsilon[i][slices].transpose(order) for i in order]), 'epsilon': vec([epsilon[i][slices].transpose(order) for i in order]),
@ -114,7 +114,7 @@ def compute_source(
polarity: int, polarity: int,
slices: Sequence[slice], slices: Sequence[slice],
epsilon: fdfield_t, epsilon: fdfield_t,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
) -> cfdfield_t: ) -> cfdfield_t:
""" """
Given an eigenmode obtained by `solve_mode`, returns the current source distribution Given an eigenmode obtained by `solve_mode`, returns the current source distribution

@ -8,7 +8,6 @@ As the z-dependence is known, all the functions in this file assume a 2D grid
""" """
# TODO update module docs # TODO update module docs
from typing import Dict, Union
import numpy import numpy
import scipy.sparse as sparse # type: ignore import scipy.sparse as sparse # type: ignore
@ -85,7 +84,7 @@ def solve_mode(
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfdfield_t, epsilon: vfdfield_t,
r0: float, r0: float,
) -> Dict[str, Union[complex, cfdfield_t]]: ) -> dict[str, complex | cfdfield_t]:
""" """
TODO: fixup TODO: fixup
Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode
@ -103,8 +102,8 @@ def solve_mode(
Returns: Returns:
``` ```
{ {
'E': List[NDArray[numpy.complex_]], 'E': list[NDArray[numpy.complex_]],
'H': List[NDArray[numpy.complex_]], 'H': list[NDArray[numpy.complex_]],
'wavenumber': complex, 'wavenumber': complex,
} }
``` ```

@ -8,7 +8,7 @@ Fields, Functions, and Operators
Discrete fields are stored in one of two forms: Discrete fields are stored in one of two forms:
- The `fdfield_t` form is a multidimensional `numpy.ndarray` - The `fdfield_t` form is a multidimensional `numpy.NDArray`
+ For a scalar field, this is just `U[m, n, p]`, where `m`, `n`, and `p` are + For a scalar field, this is just `U[m, n, p]`, where `m`, `n`, and `p` are
discrete indices referring to positions on the x, y, and z axes respectively. discrete indices referring to positions on the x, y, and z axes respectively.
+ For a vector field, the first index specifies which vector component is accessed: + For a vector field, the first index specifies which vector component is accessed:

@ -3,7 +3,7 @@ Math functions for finite difference simulations
Basic discrete calculus etc. Basic discrete calculus etc.
""" """
from typing import Sequence, Tuple, Optional, Callable from typing import Sequence, Callable
import numpy import numpy
from numpy.typing import NDArray from numpy.typing import NDArray
@ -12,8 +12,8 @@ from .types import fdfield_t, fdfield_updater_t
def deriv_forward( def deriv_forward(
dx_e: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_e: Sequence[NDArray[numpy.float_]] | None = None,
) -> Tuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]: ) -> tuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]:
""" """
Utility operators for taking discretized derivatives (backward variant). Utility operators for taking discretized derivatives (backward variant).
@ -36,8 +36,8 @@ def deriv_forward(
def deriv_back( def deriv_back(
dx_h: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_h: Sequence[NDArray[numpy.float_]] | None = None,
) -> Tuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]: ) -> tuple[fdfield_updater_t, fdfield_updater_t, fdfield_updater_t]:
""" """
Utility operators for taking discretized derivatives (forward variant). Utility operators for taking discretized derivatives (forward variant).
@ -60,7 +60,7 @@ def deriv_back(
def curl_forward( def curl_forward(
dx_e: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_e: Sequence[NDArray[numpy.float_]] | None = None,
) -> fdfield_updater_t: ) -> fdfield_updater_t:
""" """
Curl operator for use with the E field. Curl operator for use with the E field.
@ -89,7 +89,7 @@ def curl_forward(
def curl_back( def curl_back(
dx_h: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_h: Sequence[NDArray[numpy.float_]] | None = None,
) -> fdfield_updater_t: ) -> fdfield_updater_t:
""" """
Create a function which takes the backward curl of a field. Create a function which takes the backward curl of a field.
@ -118,11 +118,11 @@ def curl_back(
def curl_forward_parts( def curl_forward_parts(
dx_e: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_e: Sequence[NDArray[numpy.float_]] | None = None,
) -> Callable: ) -> Callable:
Dx, Dy, Dz = deriv_forward(dx_e) Dx, Dy, Dz = deriv_forward(dx_e)
def mkparts_fwd(e: fdfield_t) -> Tuple[Tuple[fdfield_t, fdfield_t], ...]: def mkparts_fwd(e: fdfield_t) -> tuple[tuple[fdfield_t, fdfield_t], ...]:
return ((-Dz(e[1]), Dy(e[2])), return ((-Dz(e[1]), Dy(e[2])),
( Dz(e[0]), -Dx(e[2])), ( Dz(e[0]), -Dx(e[2])),
(-Dy(e[0]), Dx(e[1]))) (-Dy(e[0]), Dx(e[1])))
@ -131,11 +131,11 @@ def curl_forward_parts(
def curl_back_parts( def curl_back_parts(
dx_h: Optional[Sequence[NDArray[numpy.float_]]] = None, dx_h: Sequence[NDArray[numpy.float_]] | None = None,
) -> Callable: ) -> Callable:
Dx, Dy, Dz = deriv_back(dx_h) Dx, Dy, Dz = deriv_back(dx_h)
def mkparts_back(h: fdfield_t) -> Tuple[Tuple[fdfield_t, fdfield_t], ...]: def mkparts_back(h: fdfield_t) -> tuple[tuple[fdfield_t, fdfield_t], ...]:
return ((-Dz(h[1]), Dy(h[2])), return ((-Dz(h[1]), Dy(h[2])),
( Dz(h[0]), -Dx(h[2])), ( Dz(h[0]), -Dx(h[2])),
(-Dy(h[0]), Dx(h[1]))) (-Dy(h[0]), Dx(h[1])))

@ -3,7 +3,7 @@ Matrix operators for finite difference simulations
Basic discrete calculus etc. Basic discrete calculus etc.
""" """
from typing import Sequence, List from typing import Sequence
import numpy import numpy
from numpy.typing import NDArray from numpy.typing import NDArray
import scipy.sparse as sparse # type: ignore import scipy.sparse as sparse # type: ignore
@ -98,7 +98,7 @@ def shift_with_mirror(
def deriv_forward( def deriv_forward(
dx_e: Sequence[NDArray[numpy.float_]], dx_e: Sequence[NDArray[numpy.float_]],
) -> List[sparse.spmatrix]: ) -> list[sparse.spmatrix]:
""" """
Utility operators for taking discretized derivatives (forward variant). Utility operators for taking discretized derivatives (forward variant).
@ -125,7 +125,7 @@ def deriv_forward(
def deriv_back( def deriv_back(
dx_h: Sequence[NDArray[numpy.float_]], dx_h: Sequence[NDArray[numpy.float_]],
) -> List[sparse.spmatrix]: ) -> list[sparse.spmatrix]:
""" """
Utility operators for taking discretized derivatives (backward variant). Utility operators for taking discretized derivatives (backward variant).

@ -4,7 +4,7 @@ and a 1D array representation of that field `[f_x0, f_x1, f_x2,... f_y0,... f_z0
Vectorized versions of the field use row-major (ie., C-style) ordering. Vectorized versions of the field use row-major (ie., C-style) ordering.
""" """
from typing import Optional, overload, Union, Sequence from typing import overload, Sequence
import numpy import numpy
from numpy.typing import ArrayLike from numpy.typing import ArrayLike
@ -24,10 +24,10 @@ def vec(f: cfdfield_t) -> vcfdfield_t:
pass pass
@overload @overload
def vec(f: ArrayLike) -> Union[vfdfield_t, vcfdfield_t]: def vec(f: ArrayLike) -> vfdfield_t | vcfdfield_t:
pass pass
def vec(f: Union[fdfield_t, cfdfield_t, ArrayLike, None]) -> Union[vfdfield_t, vcfdfield_t, None]: def vec(f: fdfield_t | cfdfield_t | ArrayLike | None) -> vfdfield_t | vcfdfield_t | None:
""" """
Create a 1D ndarray from a 3D vector field which spans a 1-3D region. Create a 1D ndarray from a 3D vector field which spans a 1-3D region.
@ -57,7 +57,7 @@ def unvec(v: vfdfield_t, shape: Sequence[int]) -> fdfield_t:
def unvec(v: vcfdfield_t, shape: Sequence[int]) -> cfdfield_t: def unvec(v: vcfdfield_t, shape: Sequence[int]) -> cfdfield_t:
pass pass
def unvec(v: Union[vfdfield_t, vcfdfield_t, None], shape: Sequence[int]) -> Union[fdfield_t, cfdfield_t, None]: def unvec(v: vfdfield_t | vcfdfield_t | None, shape: Sequence[int]) -> fdfield_t | cfdfield_t | None:
""" """
Perform the inverse of vec(): take a 1D ndarray and output a 3D field Perform the inverse of vec(): take a 1D ndarray and output a 3D field
of form `[f_x, f_y, f_z]` where each of `f_*` is a len(shape)-dimensional of form `[f_x, f_y, f_z]` where each of `f_*` is a len(shape)-dimensional

@ -3,8 +3,6 @@ Basic FDTD field updates
""" """
from typing import Union, Optional
from ..fdmath import dx_lists_t, fdfield_t, fdfield_updater_t from ..fdmath import dx_lists_t, fdfield_t, fdfield_updater_t
from ..fdmath.functional import curl_forward, curl_back from ..fdmath.functional import curl_forward, curl_back
@ -14,7 +12,7 @@ __author__ = 'Jan Petykiewicz'
def maxwell_e( def maxwell_e(
dt: float, dt: float,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_updater_t: ) -> fdfield_updater_t:
""" """
Build a function which performs a portion the time-domain E-field update, Build a function which performs a portion the time-domain E-field update,
@ -49,7 +47,7 @@ def maxwell_e(
else: else:
curl_h_fun = curl_back() curl_h_fun = curl_back()
def me_fun(e: fdfield_t, h: fdfield_t, epsilon: Union[fdfield_t, float]) -> fdfield_t: def me_fun(e: fdfield_t, h: fdfield_t, epsilon: fdfield_t | float) -> fdfield_t:
""" """
Update the E-field. Update the E-field.
@ -69,7 +67,7 @@ def maxwell_e(
def maxwell_h( def maxwell_h(
dt: float, dt: float,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_updater_t: ) -> fdfield_updater_t:
""" """
Build a function which performs part of the time-domain H-field update, Build a function which performs part of the time-domain H-field update,
@ -105,7 +103,7 @@ def maxwell_h(
else: else:
curl_e_fun = curl_forward() curl_e_fun = curl_forward()
def mh_fun(e: fdfield_t, h: fdfield_t, mu: Union[fdfield_t, float, None] = None) -> fdfield_t: def mh_fun(e: fdfield_t, h: fdfield_t, mu: fdfield_t | float | None = None) -> fdfield_t:
""" """
Update the H-field. Update the H-field.

@ -4,7 +4,7 @@ Boundary conditions
#TODO conducting boundary documentation #TODO conducting boundary documentation
""" """
from typing import Tuple, Any, List from typing import Any
from ..fdmath import fdfield_t, fdfield_updater_t from ..fdmath import fdfield_t, fdfield_updater_t
@ -12,7 +12,7 @@ from ..fdmath import fdfield_t, fdfield_updater_t
def conducting_boundary( def conducting_boundary(
direction: int, direction: int,
polarity: int polarity: int
) -> Tuple[fdfield_updater_t, fdfield_updater_t]: ) -> tuple[fdfield_updater_t, fdfield_updater_t]:
dirs = [0, 1, 2] dirs = [0, 1, 2]
if direction not in dirs: if direction not in dirs:
raise Exception('Invalid direction: {}'.format(direction)) raise Exception('Invalid direction: {}'.format(direction))
@ -20,8 +20,8 @@ def conducting_boundary(
u, v = dirs u, v = dirs
if polarity < 0: if polarity < 0:
boundary_slice = [slice(None)] * 3 # type: List[Any] boundary_slice = [slice(None)] * 3 # type: list[Any]
shifted1_slice = [slice(None)] * 3 # type: List[Any] shifted1_slice = [slice(None)] * 3 # type: list[Any]
boundary_slice[direction] = 0 boundary_slice[direction] = 0
shifted1_slice[direction] = 1 shifted1_slice[direction] = 1
@ -42,7 +42,7 @@ def conducting_boundary(
if polarity > 0: if polarity > 0:
boundary_slice = [slice(None)] * 3 boundary_slice = [slice(None)] * 3
shifted1_slice = [slice(None)] * 3 shifted1_slice = [slice(None)] * 3
shifted2_slice = [slice(None)] * 3 # type: List[Any] shifted2_slice = [slice(None)] * 3 # type: list[Any]
boundary_slice[direction] = -1 boundary_slice[direction] = -1
shifted1_slice[direction] = -2 shifted1_slice[direction] = -2
shifted2_slice[direction] = -3 shifted2_slice[direction] = -3

@ -1,4 +1,3 @@
from typing import Optional, Union
import numpy import numpy
from ..fdmath import dx_lists_t, fdfield_t from ..fdmath import dx_lists_t, fdfield_t
@ -11,7 +10,7 @@ from ..fdmath.functional import deriv_back
def poynting( def poynting(
e: fdfield_t, e: fdfield_t,
h: fdfield_t, h: fdfield_t,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Calculate the poynting vector `S` ($S$). Calculate the poynting vector `S` ($S$).
@ -89,11 +88,11 @@ def poynting(
def poynting_divergence( def poynting_divergence(
s: Optional[fdfield_t] = None, s: fdfield_t | None = None,
*, *,
e: Optional[fdfield_t] = None, e: fdfield_t | None = None,
h: Optional[fdfield_t] = None, h: fdfield_t | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Calculate the divergence of the poynting vector. Calculate the divergence of the poynting vector.
@ -116,9 +115,9 @@ def poynting_divergence(
energy cell. energy cell.
""" """
if s is None: if s is None:
assert(e is not None) assert e is not None
assert(h is not None) assert h is not None
assert(dxes is not None) assert dxes is not None
s = poynting(e, h, dxes=dxes) s = poynting(e, h, dxes=dxes)
Dx, Dy, Dz = deriv_back() Dx, Dy, Dz = deriv_back()
@ -130,9 +129,9 @@ def energy_hstep(
e0: fdfield_t, e0: fdfield_t,
h1: fdfield_t, h1: fdfield_t,
e2: fdfield_t, e2: fdfield_t,
epsilon: Optional[fdfield_t] = None, epsilon: fdfield_t | None = None,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Calculate energy `U` at the time of the provided H-field `h1`. Calculate energy `U` at the time of the provided H-field `h1`.
@ -158,9 +157,9 @@ def energy_estep(
h0: fdfield_t, h0: fdfield_t,
e1: fdfield_t, e1: fdfield_t,
h2: fdfield_t, h2: fdfield_t,
epsilon: Optional[fdfield_t] = None, epsilon: fdfield_t | None = None,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Calculate energy `U` at the time of the provided E-field `e1`. Calculate energy `U` at the time of the provided E-field `e1`.
@ -188,9 +187,9 @@ def delta_energy_h2e(
h1: fdfield_t, h1: fdfield_t,
e2: fdfield_t, e2: fdfield_t,
h3: fdfield_t, h3: fdfield_t,
epsilon: Optional[fdfield_t] = None, epsilon: fdfield_t | None = None,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Change in energy during the half-step from `h1` to `e2`. Change in energy during the half-step from `h1` to `e2`.
@ -221,9 +220,9 @@ def delta_energy_e2h(
e1: fdfield_t, e1: fdfield_t,
h2: fdfield_t, h2: fdfield_t,
e3: fdfield_t, e3: fdfield_t,
epsilon: Optional[fdfield_t] = None, epsilon: fdfield_t | None = None,
mu: Optional[fdfield_t] = None, mu: fdfield_t | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Change in energy during the half-step from `e1` to `h2`. Change in energy during the half-step from `e1` to `h2`.
@ -251,7 +250,7 @@ def delta_energy_e2h(
def delta_energy_j( def delta_energy_j(
j0: fdfield_t, j0: fdfield_t,
e1: fdfield_t, e1: fdfield_t,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
""" """
Calculate Calculate
@ -274,9 +273,9 @@ def delta_energy_j(
def dxmul( def dxmul(
ee: fdfield_t, ee: fdfield_t,
hh: fdfield_t, hh: fdfield_t,
epsilon: Optional[Union[fdfield_t, float]] = None, epsilon: fdfield_t | float | None = None,
mu: Optional[Union[fdfield_t, float]] = None, mu: fdfield_t | float | None = None,
dxes: Optional[dx_lists_t] = None, dxes: dx_lists_t | None = None,
) -> fdfield_t: ) -> fdfield_t:
if epsilon is None: if epsilon is None:
epsilon = 1 epsilon = 1

@ -7,7 +7,7 @@ PML implementations
""" """
# TODO retest pmls! # TODO retest pmls!
from typing import List, Callable, Tuple, Dict, Sequence, Any, Optional from typing import Callable, Sequence, Any
from copy import deepcopy from copy import deepcopy
import numpy import numpy
from numpy.typing import NDArray, DTypeLike from numpy.typing import NDArray, DTypeLike
@ -30,7 +30,7 @@ def cpml_params(
m: float = 3.5, m: float = 3.5,
ma: float = 1, ma: float = 1,
cfs_alpha: float = 0, cfs_alpha: float = 0,
) -> Dict[str, Any]: ) -> dict[str, Any]:
if axis not in range(3): if axis not in range(3):
raise Exception('Invalid axis: {}'.format(axis)) raise Exception('Invalid axis: {}'.format(axis))
@ -59,11 +59,11 @@ def cpml_params(
else: else:
raise Exception('Bad polarity!') raise Exception('Bad polarity!')
expand_slice_l: List[Any] = [None, None, None] expand_slice_l: list[Any] = [None, None, None]
expand_slice_l[axis] = slice(None) expand_slice_l[axis] = slice(None)
expand_slice = tuple(expand_slice_l) expand_slice = tuple(expand_slice_l)
def par(x: NDArray[numpy.float64]) -> Tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]: def par(x: NDArray[numpy.float64]) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]:
scaling = (x / thickness) ** m scaling = (x / thickness) ** m
sigma = scaling * sigma_max sigma = scaling * sigma_max
kappa = 1 + scaling * (kappa_max - 1) kappa = 1 + scaling * (kappa_max - 1)
@ -93,23 +93,22 @@ def cpml_params(
def updates_with_cpml( def updates_with_cpml(
cpml_params: Sequence[Sequence[Optional[Dict[str, Any]]]], cpml_params: Sequence[Sequence[dict[str, Any] | None]],
dt: float, dt: float,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: fdfield_t, epsilon: fdfield_t,
*, *,
dtype: DTypeLike = numpy.float32, dtype: DTypeLike = numpy.float32,
) -> Tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], ) -> tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None],
Callable[[fdfield_t, fdfield_t, fdfield_t], None]]: Callable[[fdfield_t, fdfield_t, fdfield_t], None]]:
Dfx, Dfy, Dfz = deriv_forward(dxes[1]) Dfx, Dfy, Dfz = deriv_forward(dxes[1])
Dbx, Dby, Dbz = deriv_back(dxes[1]) Dbx, Dby, Dbz = deriv_back(dxes[1])
psi_E: list[list[tuple[Any, Any]]] = [[(None, None) for _ in range(2)] for _ in range(3)]
psi_E: List[List[Tuple[Any, Any]]] = [[(None, None) for _ in range(2)] for _ in range(3)] psi_H: list[list[tuple[Any, Any]]] = deepcopy(psi_E)
psi_H: List[List[Tuple[Any, Any]]] = deepcopy(psi_E) params_E: list[list[tuple[Any, Any, Any, Any]]] = [[(None, None, None, None) for _ in range(2)] for _ in range(3)]
params_E: List[List[Tuple[Any, Any, Any, Any]]] = [[(None, None, None, None) for _ in range(2)] for _ in range(3)] params_H: list[list[tuple[Any, Any, Any, Any]]] = deepcopy(params_E)
params_H: List[List[Tuple[Any, Any, Any, Any]]] = deepcopy(params_E)
for axis in range(3): for axis in range(3):
for pp, polarity in enumerate((-1, 1)): for pp, polarity in enumerate((-1, 1)):
@ -133,7 +132,6 @@ def updates_with_cpml(
params_E[axis][pp] = cpml_param['param_e'] + (region,) params_E[axis][pp] = cpml_param['param_e'] + (region,)
params_H[axis][pp] = cpml_param['param_h'] + (region,) params_H[axis][pp] = cpml_param['param_h'] + (region,)
pE = numpy.empty_like(epsilon, dtype=dtype) pE = numpy.empty_like(epsilon, dtype=dtype)
pH = numpy.empty_like(epsilon, dtype=dtype) pH = numpy.empty_like(epsilon, dtype=dtype)
@ -183,7 +181,6 @@ def updates_with_cpml(
e[1] += dt / epsilon[1] * (dzHx - dxHz + pE[1]) e[1] += dt / epsilon[1] * (dzHx - dxHz + pE[1])
e[2] += dt / epsilon[2] * (dxHy - dyHx + pE[2]) e[2] += dt / epsilon[2] * (dxHy - dyHx + pE[2])
def update_H( def update_H(
e: fdfield_t, e: fdfield_t,
h: fdfield_t, h: fdfield_t,

@ -3,9 +3,9 @@
Test fixtures Test fixtures
""" """
from typing import Tuple, Iterable, List, Any from typing import Iterable, Any
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray
import pytest # type: ignore import pytest # type: ignore
from .utils import PRNG from .utils import PRNG
@ -20,7 +20,7 @@ FixtureRequest = Any
(5, 5, 5), (5, 5, 5),
# (7, 7, 7), # (7, 7, 7),
]) ])
def shape(request: FixtureRequest) -> Iterable[Tuple[int, ...]]: def shape(request: FixtureRequest) -> Iterable[tuple[int, ...]]:
yield (3, *request.param) yield (3, *request.param)
@ -37,7 +37,7 @@ def epsilon_fg(request: FixtureRequest) -> Iterable[float]:
@pytest.fixture(scope='module', params=['center', '000', 'random']) @pytest.fixture(scope='module', params=['center', '000', 'random'])
def epsilon( def epsilon(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon_bg: float, epsilon_bg: float,
epsilon_fg: float, epsilon_fg: float,
) -> Iterable[NDArray[numpy.float64]]: ) -> Iterable[NDArray[numpy.float64]]:
@ -76,9 +76,9 @@ def dx(request: FixtureRequest) -> Iterable[float]:
@pytest.fixture(scope='module', params=['uniform', 'centerbig']) @pytest.fixture(scope='module', params=['uniform', 'centerbig'])
def dxes( def dxes(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
dx: float, dx: float,
) -> Iterable[List[List[NDArray[numpy.float64]]]]: ) -> Iterable[list[list[NDArray[numpy.float64]]]]:
if request.param == 'uniform': if request.param == 'uniform':
dxes = [[numpy.full(s, dx) for s in shape[1:]] for _ in range(2)] dxes = [[numpy.full(s, dx) for s in shape[1:]] for _ in range(2)]
elif request.param == 'centerbig': elif request.param == 'centerbig':

@ -1,8 +1,8 @@
from typing import List, Tuple, Iterable, Optional from typing import Iterable
import dataclasses import dataclasses
import pytest # type: ignore import pytest # type: ignore
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray
#from numpy.testing import assert_allclose, assert_array_equal #from numpy.testing import assert_allclose, assert_array_equal
from .. import fdfd from .. import fdfd
@ -60,12 +60,12 @@ def omega(request: FixtureRequest) -> Iterable[float]:
@pytest.fixture(params=[None]) @pytest.fixture(params=[None])
def pec(request: FixtureRequest) -> Iterable[Optional[NDArray[numpy.float64]]]: def pec(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
yield request.param yield request.param
@pytest.fixture(params=[None]) @pytest.fixture(params=[None])
def pmc(request: FixtureRequest) -> Iterable[Optional[NDArray[numpy.float64]]]: def pmc(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
yield request.param yield request.param
@ -78,7 +78,7 @@ def pmc(request: FixtureRequest) -> Iterable[Optional[NDArray[numpy.float64]]]:
@pytest.fixture(params=['diag']) # 'center' @pytest.fixture(params=['diag']) # 'center'
def j_distribution( def j_distribution(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
j_mag: float, j_mag: float,
) -> Iterable[NDArray[numpy.float64]]: ) -> Iterable[NDArray[numpy.float64]]:
j = numpy.zeros(shape, dtype=complex) j = numpy.zeros(shape, dtype=complex)
@ -95,26 +95,26 @@ def j_distribution(
@dataclasses.dataclass() @dataclasses.dataclass()
class FDResult: class FDResult:
shape: Tuple[int, ...] shape: tuple[int, ...]
dxes: List[List[NDArray[numpy.float64]]] dxes: list[list[NDArray[numpy.float64]]]
epsilon: NDArray[numpy.float64] epsilon: NDArray[numpy.float64]
omega: complex omega: complex
j: NDArray[numpy.complex128] j: NDArray[numpy.complex128]
e: NDArray[numpy.complex128] e: NDArray[numpy.complex128]
pmc: Optional[NDArray[numpy.float64]] pmc: NDArray[numpy.float64] | None
pec: Optional[NDArray[numpy.float64]] pec: NDArray[numpy.float64] | None
@pytest.fixture() @pytest.fixture()
def sim( def sim(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon: NDArray[numpy.float64], epsilon: NDArray[numpy.float64],
dxes: List[List[NDArray[numpy.float64]]], dxes: list[list[NDArray[numpy.float64]]],
j_distribution: NDArray[numpy.complex128], j_distribution: NDArray[numpy.complex128],
omega: float, omega: float,
pec: Optional[NDArray[numpy.float64]], pec: NDArray[numpy.float64] | None,
pmc: Optional[NDArray[numpy.float64]], pmc: NDArray[numpy.float64] | None,
) -> FDResult: ) -> FDResult:
""" """
Build simulation from parts Build simulation from parts

@ -1,7 +1,7 @@
from typing import Optional, Tuple, Iterable, List from typing import Iterable
import pytest # type: ignore import pytest # type: ignore
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray
from numpy.testing import assert_allclose from numpy.testing import assert_allclose
from .. import fdfd from .. import fdfd
@ -49,19 +49,19 @@ def omega(request: FixtureRequest) -> Iterable[float]:
@pytest.fixture(params=[None]) @pytest.fixture(params=[None])
def pec(request: FixtureRequest) -> Iterable[Optional[NDArray[numpy.float64]]]: def pec(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
yield request.param yield request.param
@pytest.fixture(params=[None]) @pytest.fixture(params=[None])
def pmc(request: FixtureRequest) -> Iterable[Optional[NDArray[numpy.float64]]]: def pmc(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
yield request.param yield request.param
@pytest.fixture(params=[(30, 1, 1), @pytest.fixture(params=[(30, 1, 1),
(1, 30, 1), (1, 30, 1),
(1, 1, 30)]) (1, 1, 30)])
def shape(request: FixtureRequest) -> Iterable[Tuple[int, ...]]: def shape(request: FixtureRequest) -> Iterable[tuple[int, ...]]:
yield (3, *request.param) yield (3, *request.param)
@ -73,7 +73,7 @@ def src_polarity(request: FixtureRequest) -> Iterable[int]:
@pytest.fixture() @pytest.fixture()
def j_distribution( def j_distribution(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon: NDArray[numpy.float64], epsilon: NDArray[numpy.float64],
dxes: dx_lists_mut, dxes: dx_lists_mut,
omega: float, omega: float,
@ -86,7 +86,7 @@ def j_distribution(
other_dims.remove(dim) other_dims.remove(dim)
dx_prop = (dxes[0][dim][shape[dim + 1] // 2] dx_prop = (dxes[0][dim][shape[dim + 1] // 2]
+ dxes[1][dim][shape[dim + 1] // 2]) / 2 # TODO is this right for nonuniform dxes? + dxes[1][dim][shape[dim + 1] // 2]) / 2 # noqa: E128 # TODO is this right for nonuniform dxes?
# Mask only contains components orthogonal to propagation direction # Mask only contains components orthogonal to propagation direction
center_mask = numpy.zeros(shape, dtype=bool) center_mask = numpy.zeros(shape, dtype=bool)
@ -112,7 +112,7 @@ def j_distribution(
@pytest.fixture() @pytest.fixture()
def epsilon( def epsilon(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon_bg: float, epsilon_bg: float,
epsilon_fg: float, epsilon_fg: float,
) -> Iterable[NDArray[numpy.float64]]: ) -> Iterable[NDArray[numpy.float64]]:
@ -123,11 +123,11 @@ def epsilon(
@pytest.fixture(params=['uniform']) @pytest.fixture(params=['uniform'])
def dxes( def dxes(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
dx: float, dx: float,
omega: float, omega: float,
epsilon_fg: float, epsilon_fg: float,
) -> Iterable[List[List[NDArray[numpy.float64]]]]: ) -> Iterable[list[list[NDArray[numpy.float64]]]]:
if request.param == 'uniform': if request.param == 'uniform':
dxes = [[numpy.full(s, dx) for s in shape[1:]] for _ in range(2)] dxes = [[numpy.full(s, dx) for s in shape[1:]] for _ in range(2)]
dim = numpy.where(numpy.array(shape[1:]) > 1)[0][0] # Propagation axis dim = numpy.where(numpy.array(shape[1:]) > 1)[0][0] # Propagation axis
@ -147,13 +147,13 @@ def dxes(
@pytest.fixture() @pytest.fixture()
def sim( def sim(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon: NDArray[numpy.float64], epsilon: NDArray[numpy.float64],
dxes: dx_lists_mut, dxes: dx_lists_mut,
j_distribution: NDArray[numpy.complex128], j_distribution: NDArray[numpy.complex128],
omega: float, omega: float,
pec: Optional[NDArray[numpy.float64]], pec: NDArray[numpy.float64] | None,
pmc: Optional[NDArray[numpy.float64]], pmc: NDArray[numpy.float64] | None,
) -> FDResult: ) -> FDResult:
j_vec = vec(j_distribution) j_vec = vec(j_distribution)
eps_vec = vec(epsilon) eps_vec = vec(epsilon)

@ -1,8 +1,8 @@
from typing import List, Tuple, Iterable, Any, Dict from typing import Iterable, Any
import dataclasses import dataclasses
import pytest # type: ignore import pytest # type: ignore
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray
#from numpy.testing import assert_allclose, assert_array_equal #from numpy.testing import assert_allclose, assert_array_equal
from .. import fdtd from .. import fdtd
@ -33,7 +33,7 @@ def test_initial_energy(sim: 'TDResult') -> None:
dV = numpy.prod(numpy.meshgrid(*sim.dxes[0], indexing='ij'), axis=0) dV = numpy.prod(numpy.meshgrid(*sim.dxes[0], indexing='ij'), axis=0)
u0 = (j0 * j0.conj() / sim.epsilon * dV).sum(axis=0) u0 = (j0 * j0.conj() / sim.epsilon * dV).sum(axis=0)
args: Dict[str, Any] = { args: dict[str, Any] = {
'dxes': sim.dxes, 'dxes': sim.dxes,
'epsilon': sim.epsilon, 'epsilon': sim.epsilon,
} }
@ -52,7 +52,7 @@ def test_energy_conservation(sim: 'TDResult') -> None:
e0 = sim.es[0] e0 = sim.es[0]
j0 = sim.js[0] j0 = sim.js[0]
u = fdtd.delta_energy_j(j0=j0, e1=e0, dxes=sim.dxes).sum() u = fdtd.delta_energy_j(j0=j0, e1=e0, dxes=sim.dxes).sum()
args: Dict[str, Any] = { args: dict[str, Any] = {
'dxes': sim.dxes, 'dxes': sim.dxes,
'epsilon': sim.epsilon, 'epsilon': sim.epsilon,
} }
@ -70,7 +70,7 @@ def test_energy_conservation(sim: 'TDResult') -> None:
def test_poynting_divergence(sim: 'TDResult') -> None: def test_poynting_divergence(sim: 'TDResult') -> None:
args: Dict[str, Any] = { args: dict[str, Any] = {
'dxes': sim.dxes, 'dxes': sim.dxes,
'epsilon': sim.epsilon, 'epsilon': sim.epsilon,
} }
@ -103,7 +103,7 @@ def test_poynting_planes(sim: 'TDResult') -> None:
if mask.sum() > 1: if mask.sum() > 1:
pytest.skip('test_poynting_planes can only test single point sources, got {}'.format(mask.sum())) pytest.skip('test_poynting_planes can only test single point sources, got {}'.format(mask.sum()))
args: Dict[str, Any] = { args: dict[str, Any] = {
'dxes': sim.dxes, 'dxes': sim.dxes,
'epsilon': sim.epsilon, 'epsilon': sim.epsilon,
} }
@ -156,26 +156,26 @@ def dt(request: FixtureRequest) -> Iterable[float]:
@dataclasses.dataclass() @dataclasses.dataclass()
class TDResult: class TDResult:
shape: Tuple[int, ...] shape: tuple[int, ...]
dt: float dt: float
dxes: List[List[NDArray[numpy.float64]]] dxes: list[list[NDArray[numpy.float64]]]
epsilon: NDArray[numpy.float64] epsilon: NDArray[numpy.float64]
j_distribution: NDArray[numpy.float64] j_distribution: NDArray[numpy.float64]
j_steps: Tuple[int, ...] j_steps: tuple[int, ...]
es: List[NDArray[numpy.float64]] = dataclasses.field(default_factory=list) es: list[NDArray[numpy.float64]] = dataclasses.field(default_factory=list)
hs: List[NDArray[numpy.float64]] = dataclasses.field(default_factory=list) hs: list[NDArray[numpy.float64]] = dataclasses.field(default_factory=list)
js: List[NDArray[numpy.float64]] = dataclasses.field(default_factory=list) js: list[NDArray[numpy.float64]] = dataclasses.field(default_factory=list)
@pytest.fixture(params=[(0, 4, 8)]) # (0,) @pytest.fixture(params=[(0, 4, 8)]) # (0,)
def j_steps(request: FixtureRequest) -> Iterable[Tuple[int, ...]]: def j_steps(request: FixtureRequest) -> Iterable[tuple[int, ...]]:
yield request.param yield request.param
@pytest.fixture(params=['center', 'random']) @pytest.fixture(params=['center', 'random'])
def j_distribution( def j_distribution(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
j_mag: float, j_mag: float,
) -> Iterable[NDArray[numpy.float64]]: ) -> Iterable[NDArray[numpy.float64]]:
j = numpy.zeros(shape) j = numpy.zeros(shape)
@ -191,12 +191,12 @@ def j_distribution(
@pytest.fixture() @pytest.fixture()
def sim( def sim(
request: FixtureRequest, request: FixtureRequest,
shape: Tuple[int, ...], shape: tuple[int, ...],
epsilon: NDArray[numpy.float64], epsilon: NDArray[numpy.float64],
dxes: List[List[NDArray[numpy.float64]]], dxes: list[list[NDArray[numpy.float64]]],
dt: float, dt: float,
j_distribution: NDArray[numpy.float64], j_distribution: NDArray[numpy.float64],
j_steps: Tuple[int, ...], j_steps: tuple[int, ...],
) -> TDResult: ) -> TDResult:
is3d = (numpy.array(shape) == 1).sum() == 0 is3d = (numpy.array(shape) == 1).sum() == 0
if is3d: if is3d:

@ -1,7 +1,7 @@
from typing import Any from typing import Any
import numpy import numpy
from numpy.typing import ArrayLike, NDArray from numpy.typing import NDArray
PRNG = numpy.random.RandomState(12345) PRNG = numpy.random.RandomState(12345)
@ -14,7 +14,7 @@ def assert_fields_close(
**kwargs: Any, **kwargs: Any,
) -> None: ) -> None:
numpy.testing.assert_allclose( numpy.testing.assert_allclose(
x, y, verbose=False, x, y, verbose=False, # type: ignore
err_msg='Fields did not match:\n{}\n{}'.format(numpy.moveaxis(x, -1, 0), err_msg='Fields did not match:\n{}\n{}'.format(numpy.moveaxis(x, -1, 0),
numpy.moveaxis(y, -1, 0)), numpy.moveaxis(y, -1, 0)),
*args, *args,

Loading…
Cancel
Save