diff --git a/README.md b/README.md index 73d48b5..b400796 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,9 @@ source my_venv/bin/activate # Install in-place (-e, editable) from ./meanas, including development dependencies ([dev]) pip3 install --user -e './meanas[dev]' -# Fast local iteration: excludes slower 3D/integration/example-smoke checks +# Run tests cd meanas -python3 -m pytest -q -m "not complete" - -# Complete pre-commit confidence run: includes the slower integration tests and -# tracked example smoke tests -python3 -m pytest -q | tee test_results.txt +python3 -m pytest -rsxX | tee test_results.txt ``` #### See also: diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index bee91a4..090b5e7 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -13,31 +13,19 @@ } [data-md-color-scheme="slate"] { - --md-default-bg-color: #000000; - --md-default-bg-color--light: #050505; - --md-default-bg-color--lighter: #0a0a0a; - --md-default-bg-color--lightest: #111111; + --md-default-bg-color: #0f141c; --md-default-fg-color: #e8eef7; --md-default-fg-color--light: #b3bfd1; --md-default-fg-color--lighter: #7f8ba0; --md-default-fg-color--lightest: #5d6880; - --md-code-bg-color: #050505; + --md-code-bg-color: #111923; --md-code-fg-color: #e4edf8; --md-accent-fg-color: #7dd3fc; } [data-md-color-scheme="slate"] .md-header, [data-md-color-scheme="slate"] .md-tabs { - background: #000000; -} - -[data-md-color-scheme="slate"] .md-main, -[data-md-color-scheme="slate"] .md-main__inner, -[data-md-color-scheme="slate"] .md-content, -[data-md-color-scheme="slate"] .md-content__inner, -[data-md-color-scheme="slate"] .md-sidebar, -[data-md-color-scheme="slate"] .md-sidebar__scrollwrap { - background: #000000; + background: linear-gradient(90deg, #111923 0%, #162235 100%); } [data-md-color-scheme="slate"] .md-typeset pre > code, @@ -46,7 +34,7 @@ } [data-md-color-scheme="slate"] .md-typeset table:not([class]) { - background: #050505; + background: rgba(255, 255, 255, 0.015); } [data-md-color-scheme="slate"] .md-typeset table:not([class]) th { @@ -55,7 +43,7 @@ [data-md-color-scheme="slate"] .md-typeset .admonition, [data-md-color-scheme="slate"] .md-typeset details { - background: #050505; + background: rgba(255, 255, 255, 0.02); border-color: rgba(125, 211, 252, 0.2); } diff --git a/examples/eme.py b/examples/eme.py index 3a26dc8..6215cbc 100644 --- a/examples/eme.py +++ b/examples/eme.py @@ -14,7 +14,6 @@ simple straight interface: from __future__ import annotations import importlib -from typing import TYPE_CHECKING import numpy from numpy import pi @@ -25,9 +24,6 @@ from gridlock import Extent from meanas.fdfd import eme, waveguide_2d from meanas.fdmath import unvec -if TYPE_CHECKING: - from types import ModuleType - WL = 1310.0 DX = 40.0 @@ -39,7 +35,7 @@ EPS_OX = 1.453 ** 2 MODE_NUMBERS = numpy.array([0]) -def require_optional(name: str, package_name: str | None = None) -> ModuleType: +def require_optional(name: str, package_name: str | None = None): package_name = package_name or name try: return importlib.import_module(name) @@ -163,7 +159,7 @@ def print_summary(ss: numpy.ndarray, wavenumbers_left: numpy.ndarray, wavenumber def plot_results( *, - pyplot: ModuleType, + pyplot, ss: numpy.ndarray, left_mode: tuple[numpy.ndarray, numpy.ndarray], right_mode: tuple[numpy.ndarray, numpy.ndarray], diff --git a/examples/eme_bend.py b/examples/eme_bend.py index e5eaebd..caff4df 100644 --- a/examples/eme_bend.py +++ b/examples/eme_bend.py @@ -15,7 +15,6 @@ This example demonstrates a cylindrical-waveguide EME workflow: from __future__ import annotations import importlib -from typing import TYPE_CHECKING import numpy from numpy import pi @@ -27,9 +26,6 @@ from gridlock import Extent from meanas.fdfd import eme, waveguide_2d, waveguide_cyl from meanas.fdmath import unvec -if TYPE_CHECKING: - from types import ModuleType - WL = 1310.0 DX = 40.0 @@ -44,7 +40,7 @@ STRAIGHT_SECTION_LENGTH = 12e3 BEND_ANGLE = pi / 2 -def require_optional(name: str, package_name: str | None = None) -> ModuleType: +def require_optional(name: str, package_name: str | None = None): package_name = package_name or name try: return importlib.import_module(name) @@ -167,7 +163,7 @@ def solve_bend_modes( def build_cascaded_network( - skrf: ModuleType, + skrf, *, interface_s: numpy.ndarray, straight_wavenumbers: numpy.ndarray, @@ -220,7 +216,7 @@ def print_summary( def plot_results( *, - pyplot: ModuleType, + pyplot, interface_s: numpy.ndarray, cascaded_s: numpy.ndarray, straight_mode: tuple[numpy.ndarray, numpy.ndarray], diff --git a/examples/fdtd.py b/examples/fdtd.py index fd6026d..d8cd101 100644 --- a/examples/fdtd.py +++ b/examples/fdtd.py @@ -89,7 +89,7 @@ def perturbed_l3(a: float, radius: float, **kwargs) -> Pattern: return pat -def main() -> None: +def main(): dtype = numpy.float32 max_t = 3600 # number of timesteps @@ -97,6 +97,7 @@ def main() -> None: pml_thickness = 8 # (number of cells) wl = 1550 # Excitation wavelength and fwhm + dwl = 100 # Device design parameters xy_size = numpy.array([10, 10]) diff --git a/examples/waveguide.py b/examples/waveguide.py index f243924..7becd59 100644 --- a/examples/waveguide.py +++ b/examples/waveguide.py @@ -100,7 +100,7 @@ def get_waveguide_mode( # compute_overlap_e() returns the normalized upstream overlap window used to # project another field onto this same guided mode. - e_overlap = waveguide_3d.compute_overlap_e(E=wg_results['E'], wavenumber=wg_results['wavenumber'], **wg_args) + e_overlap = waveguide_3d.compute_overlap_e(E=wg_results['E'], wavenumber=wg_results['wavenumber'], **wg_args, omega=omega) return J, e_overlap diff --git a/meanas/__init__.py b/meanas/__init__.py index 9d1b401..5ea8d42 100644 --- a/meanas/__init__.py +++ b/meanas/__init__.py @@ -7,7 +7,7 @@ toolbox overview and API derivations. import pathlib -__version__ = '0.12' +__version__ = '0.11' __author__ = 'Jan Petykiewicz' diff --git a/meanas/fdfd/bloch.py b/meanas/fdfd/bloch.py index 1e15c3a..5701ed9 100644 --- a/meanas/fdfd/bloch.py +++ b/meanas/fdfd/bloch.py @@ -262,7 +262,7 @@ def maxwell_operator( else: # transform from mn to xyz b_xyz = (m * b_m - + n * b_n) # noqa + + n * b_n) # noqa: E128 # divide by mu temp = ifftn(b_xyz, axes=range(3)) @@ -409,7 +409,7 @@ def inverse_maxwell_operator_approx( else: # transform from mn to xyz h_xyz = (m * hin_m - + n * hin_n) # noqa + + n * hin_n) # noqa: E128 # multiply by mu temp = ifftn(h_xyz, axes=range(3)) @@ -474,7 +474,7 @@ def find_k( `(k, actual_frequency, eigenvalues, eigenvectors)` The found k-vector and its frequency, along with all eigenvalues and eigenvectors. """ - direction = numpy.array(direction) / norm(direction) # type: ignore[operator] + direction = numpy.array(direction) / norm(direction) k_bounds = tuple(sorted(k_bounds)) # type: ignore # we know the length already... assert len(k_bounds) == 2 @@ -504,7 +504,7 @@ def find_k( assert n is not None assert v is not None actual_frequency = get_f(float(res.x), band) - return direction * float(res.x), float(actual_frequency), n, v # type: ignore[operator,return-value] + return direction * float(res.x), float(actual_frequency), n, v def eigsolve( @@ -683,16 +683,7 @@ def eigsolve( return numpy.abs(trace) if False: - def trace_deriv( - theta: float, - sgn: int = sgn, - ZtAZ=ZtAZ, # noqa: ANN001 - DtAD=DtAD, # noqa: ANN001 - symZtD=symZtD, # noqa: ANN001 - symZtAD=symZtAD, # noqa: ANN001 - ZtZ=ZtZ, # noqa: ANN001 - DtD=DtD, # noqa: ANN001 - ) -> float: + def trace_deriv(theta, sgn: int = sgn, ZtAZ=ZtAZ, DtAD=DtAD, symZtD=symZtD, symZtAD=symZtAD, ZtZ=ZtZ, DtD=DtD): # noqa: ANN001 Qi = Qi_func(theta) c2 = numpy.cos(2 * theta) s2 = numpy.sin(2 * theta) diff --git a/meanas/fdfd/eme.py b/meanas/fdfd/eme.py index af745e8..5165ef1 100644 --- a/meanas/fdfd/eme.py +++ b/meanas/fdfd/eme.py @@ -1,24 +1,3 @@ -""" -Low-level mode-matching helpers for waveguide / EME workflows. - -These helpers operate on already-solved and already-normalized port fields. -They do not build geometries or solve modes themselves; downstream users are -expected to supply compatible `(E, H)` modal field pairs from -`waveguide_2d`, `waveguide_3d`, or `waveguide_cyl`. - -The returned matrices follow the usual port ordering: - -- `get_tr(...)` returns `(T, R)` for left-incident modes. -- `get_abcd(...)` returns the 2-port block transfer matrix built from the two - directional `T/R` solves. -- `get_s(...)` returns the full block scattering matrix - `[[R12, T12], [T21, R21]]`. - -This module is intentionally a thin library layer rather than an integrated -simulation suite. It provides the overlap algebra that downstream users can -compose into larger workflows. -""" - from collections.abc import Sequence import numpy from numpy.typing import NDArray @@ -27,70 +6,14 @@ from scipy import sparse from ..fdmath import dx_lists2_t, vcfdfield2 from .waveguide_2d import inner_product -type wavenumber_seq = Sequence[complex] | NDArray[numpy.complexfloating] | NDArray[numpy.floating] - - -def _validate_port_modes( - name: str, - ehs: Sequence[Sequence[vcfdfield2]], - wavenumbers: wavenumber_seq, - ) -> tuple[tuple[int, ...], tuple[int, ...]]: - if len(ehs) != len(wavenumbers): - raise ValueError(f'{name} mode list and wavenumber list must have the same length') - if not ehs: - raise ValueError(f'{name} must contain at least one mode') - - e_shape: tuple[int, ...] | None = None - h_shape: tuple[int, ...] | None = None - for index, mode in enumerate(ehs): - if len(mode) != 2: - raise ValueError(f'{name}[{index}] must be a 2-tuple of (E, H) modal fields') - e_field, h_field = mode - mode_e_shape = numpy.shape(e_field) - mode_h_shape = numpy.shape(h_field) - if mode_e_shape != mode_h_shape: - raise ValueError(f'{name}[{index}] has mismatched E/H field shapes') - if e_shape is None: - e_shape = mode_e_shape - h_shape = mode_h_shape - elif mode_e_shape != e_shape or mode_h_shape != h_shape: - raise ValueError(f'{name} modal fields must all share the same shape') - - assert e_shape is not None - assert h_shape is not None - return e_shape, h_shape - def get_tr( ehLs: Sequence[Sequence[vcfdfield2]], - wavenumbers_L: wavenumber_seq, + wavenumbers_L: Sequence[complex], ehRs: Sequence[Sequence[vcfdfield2]], - wavenumbers_R: wavenumber_seq, + wavenumbers_R: Sequence[complex], dxes: dx_lists2_t, ) -> tuple[NDArray[numpy.complex128], NDArray[numpy.complex128]]: - """ - Compute left-incident transmission and reflection matrices. - - Args: - ehLs: Left-port modes as `(E, H)` field pairs. - wavenumbers_L: Propagation constants for `ehLs`. - ehRs: Right-port modes as `(E, H)` field pairs. - wavenumbers_R: Propagation constants for `ehRs`. - dxes: Two-dimensional Yee-cell edge lengths for the shared port plane. - - Returns: - `(T12, R12)` where columns index left-incident modes and rows index - outgoing right-going / left-going modes respectively. - - Raises: - ValueError: If the port mode lists are empty, malformed, or defined on - incompatible field shapes. - """ - left_e_shape, left_h_shape = _validate_port_modes('ehLs', ehLs, wavenumbers_L) - right_e_shape, right_h_shape = _validate_port_modes('ehRs', ehRs, wavenumbers_R) - if left_e_shape != right_e_shape or left_h_shape != right_h_shape: - raise ValueError('left and right modal fields must share the same E/H shapes') - nL = len(wavenumbers_L) nR = len(wavenumbers_R) A12 = numpy.zeros((nL, nR), dtype=complex) @@ -120,21 +43,11 @@ def get_tr( def get_abcd( ehLs: Sequence[Sequence[vcfdfield2]], - wavenumbers_L: wavenumber_seq, + wavenumbers_L: Sequence[complex], ehRs: Sequence[Sequence[vcfdfield2]], - wavenumbers_R: wavenumber_seq, + wavenumbers_R: Sequence[complex], **kwargs, ) -> sparse.sparray: - """ - Build the 2-port block transfer matrix for an interface. - - The blocks are assembled from the forward and reverse `get_tr(...)` - solutions using the standard - - `[[A, B], [C, D]] = [[T12 - R21 T21^-1 R12, R21 T21^-1], [-T21^-1 R12, T21^-1]]` - - convention. - """ 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) @@ -153,26 +66,13 @@ def get_abcd( def get_s( ehLs: Sequence[Sequence[vcfdfield2]], - wavenumbers_L: wavenumber_seq, + wavenumbers_L: Sequence[complex], ehRs: Sequence[Sequence[vcfdfield2]], - wavenumbers_R: wavenumber_seq, + wavenumbers_R: Sequence[complex], force_nogain: bool = False, force_reciprocal: bool = False, **kwargs, ) -> NDArray[numpy.complex128]: - """ - Build the full block scattering matrix for a two-sided interface. - - The returned matrix is ordered as `[[R12, T12], [T21, R21]]`, where the - first block-row/column corresponds to the left port and the second to the - right port. - - Args: - force_nogain: If `True`, clamp singular values of the assembled - scattering matrix to at most one. - force_reciprocal: If `True`, symmetrize the assembled matrix as - `0.5 * (S + S.T)`. - """ t12, r12 = get_tr(ehLs, wavenumbers_L, ehRs, wavenumbers_R, **kwargs) t21, r21 = get_tr(ehRs, wavenumbers_R, ehLs, wavenumbers_L, **kwargs) diff --git a/meanas/fdfd/farfield.py b/meanas/fdfd/farfield.py index 06f705b..0051cd0 100644 --- a/meanas/fdfd/farfield.py +++ b/meanas/fdfd/farfield.py @@ -1,24 +1,23 @@ """ 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 numpy.typing import NDArray -from numpy import complexfloating -type farfield_slice = NDArray[complexfloating] -type transverse_slice_pair = Sequence[farfield_slice] +from ..fdmath import cfdfield_t + +if TYPE_CHECKING: + from collections.abc import Sequence def near_to_farfield( - E_near: transverse_slice_pair, - H_near: transverse_slice_pair, + E_near: cfdfield_t, + H_near: cfdfield_t, dx: float, dy: float, - padded_size: Sequence[int] | int | None = None + padded_size: list[int] | int | None = None ) -> dict[str, Any]: """ Compute the farfield, i.e. the distribution of the fields after propagation @@ -59,7 +58,7 @@ def near_to_farfield( raise Exception('H_near must be a length-2 list of ndarrays') s = E_near[0].shape - if not all(s == f.shape for f in [*E_near, *H_near]): + if not all(s == f.shape for f in E_near + H_near): raise Exception('All fields must be the same shape!') if padded_size is None: @@ -87,14 +86,14 @@ def near_to_farfield( # Normalized vector potentials N, L 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] # noqa + 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, - -En_fft[1] * sin_th - En_fft[0] * cos_th] # noqa + -En_fft[1] * sin_th - En_fft[0] * cos_th] # noqa: E128 E_far = [-L[1] - N[0], - L[0] - N[1]] # noqa + L[0] - N[1]] # noqa: E127 H_far = [-E_far[1], - E_far[0]] # noqa + E_far[0]] # noqa: E127 theta = numpy.arctan2(ky, kx) phi = numpy.arccos(cos_phi) @@ -124,11 +123,11 @@ def near_to_farfield( def far_to_nearfield( - E_far: transverse_slice_pair, - H_far: transverse_slice_pair, + E_far: cfdfield_t, + H_far: cfdfield_t, dkx: float, dky: float, - padded_size: Sequence[int] | int | None = None + padded_size: list[int] | int | None = None ) -> dict[str, Any]: """ Compute the farfield, i.e. the distribution of the fields after propagation @@ -165,7 +164,7 @@ def far_to_nearfield( raise Exception('H_far must be a length-2 list of ndarrays') s = E_far[0].shape - if not all(s == f.shape for f in [*E_far, *H_far]): + if not all(s == f.shape for f in E_far + H_far): raise Exception('All fields must be the same shape!') if padded_size is None: @@ -203,9 +202,9 @@ def far_to_nearfield( # Normalized vector potentials N, L L = [0.5 * E_far[1], - -0.5 * E_far[0]] # noqa + -0.5 * E_far[0]] # noqa: E128 N = [L[1], - -L[0]] # noqa + -L[0]] # noqa: E128 En_fft = [ numpy.divide( diff --git a/meanas/fdfd/waveguide_2d.py b/meanas/fdfd/waveguide_2d.py index e67160e..1074e2b 100644 --- a/meanas/fdfd/waveguide_2d.py +++ b/meanas/fdfd/waveguide_2d.py @@ -373,9 +373,8 @@ def normalized_fields_e( """ e = exy2e(wavenumber=wavenumber, dxes=dxes, epsilon=epsilon) @ e_xy h = exy2h(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ e_xy - e_norm, h_norm = _normalized_fields( - e=e, h=h, dxes=dxes, epsilon=epsilon, prop_phase=prop_phase, - ) + e_norm, h_norm = _normalized_fields(e=e, h=h, omega=omega, dxes=dxes, epsilon=epsilon, + mu=mu, prop_phase=prop_phase) return e_norm, h_norm @@ -416,17 +415,18 @@ def normalized_fields_h( """ e = hxy2e(wavenumber=wavenumber, omega=omega, dxes=dxes, epsilon=epsilon, mu=mu) @ h_xy h = hxy2h(wavenumber=wavenumber, dxes=dxes, mu=mu) @ h_xy - e_norm, h_norm = _normalized_fields( - e=e, h=h, dxes=dxes, epsilon=epsilon, prop_phase=prop_phase, - ) + e_norm, h_norm = _normalized_fields(e=e, h=h, omega=omega, dxes=dxes, epsilon=epsilon, + mu=mu, prop_phase=prop_phase) return e_norm, h_norm def _normalized_fields( e: vcfdslice, h: vcfdslice, + omega: complex, dxes: dx_lists2_t, epsilon: vfdslice, + mu: vfdslice | None = None, prop_phase: float = 0, ) -> tuple[vcfdslice_t, vcfdslice_t]: r""" diff --git a/meanas/fdfd/waveguide_3d.py b/meanas/fdfd/waveguide_3d.py index c77c8d4..e7dfd22 100644 --- a/meanas/fdfd/waveguide_3d.py +++ b/meanas/fdfd/waveguide_3d.py @@ -19,8 +19,9 @@ The intended workflow is: That same convention controls which side of the selected slice is used for the overlap window and how the expanded field is phased. """ -from typing import Any, TypedDict, cast +from typing import Any, cast import warnings +from typing import Any from collections.abc import Sequence import numpy from numpy.typing import NDArray @@ -30,13 +31,6 @@ from ..fdmath import vec, unvec, dx_lists_t, cfdfield_t, fdfield, cfdfield from . import operators, waveguide_2d -class Waveguide3DMode(TypedDict): - wavenumber: complex - wavenumber_2d: complex - H: NDArray[complexfloating] - E: NDArray[complexfloating] - - def solve_mode( mode_number: int, omega: complex, @@ -46,7 +40,7 @@ def solve_mode( slices: Sequence[slice], epsilon: fdfield, mu: fdfield | None = None, - ) -> Waveguide3DMode: + ) -> dict[str, complex | NDArray[complexfloating]]: r""" Given a 3D grid, selects a slice from the grid and attempts to solve for an eigenmode propagating through that slice. @@ -127,7 +121,7 @@ def solve_mode( E[iii] = e[oo][:, :, None].transpose(reverse_order) H[iii] = h[oo][:, :, None].transpose(reverse_order) - results: Waveguide3DMode = { + results = { 'wavenumber': wavenumber, 'wavenumber_2d': wavenumber_2d, 'H': H, @@ -190,12 +184,13 @@ def compute_source( def compute_overlap_e( - E: cfdfield, + E: cfdfield_t, wavenumber: complex, dxes: dx_lists_t, axis: int, polarity: int, slices: Sequence[slice], + omega: float, ) -> cfdfield_t: r""" Build an overlap field for projecting another 3D electric field onto a mode. @@ -267,7 +262,7 @@ def compute_overlap_e( if clipped_start >= clipped_stop: raise ValueError('Requested overlap window lies outside the domain') if clipped_start != start or clipped_stop != stop: - warnings.warn('Requested overlap window was clipped to fit within the domain', RuntimeWarning, stacklevel=2) + warnings.warn('Requested overlap window was clipped to fit within the domain', RuntimeWarning) slices2_l = list(slices) slices2_l[axis] = slice(clipped_start, clipped_stop) @@ -280,7 +275,7 @@ def compute_overlap_e( norm = (Etgt.conj() * Etgt).sum() if norm == 0: raise ValueError('Requested overlap window contains no overlap field support') - Etgt = Etgt / norm + Etgt /= norm return cfdfield_t(Etgt) diff --git a/meanas/fdfd/waveguide_cyl.py b/meanas/fdfd/waveguide_cyl.py index f2cb5c3..201f709 100644 --- a/meanas/fdfd/waveguide_cyl.py +++ b/meanas/fdfd/waveguide_cyl.py @@ -130,7 +130,7 @@ import numpy from numpy.typing import NDArray, ArrayLike from scipy import sparse -from ..fdmath import vec, unvec, dx_lists2_t, vcfdslice_t, vfdslice, vcfdslice, vcfdfield2 +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 @@ -267,7 +267,7 @@ def solve_mode( mode_number: int, *args: Any, **kwargs: Any, - ) -> tuple[vcfdfield2, complex]: + ) -> tuple[vcfdslice, complex]: """ Wrapper around `solve_modes()` that solves for a single mode. @@ -285,7 +285,7 @@ def solve_mode( def linear_wavenumbers( - e_xys: Sequence[vcfdfield2] | NDArray[numpy.complex128], + e_xys: list[vcfdfield2_t], angular_wavenumbers: ArrayLike, epsilon: vfdslice, dxes: dx_lists2_t, @@ -529,17 +529,19 @@ def normalized_fields_e( """ e = exy2e(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, epsilon=epsilon) @ e_xy h = exy2h(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, epsilon=epsilon, mu=mu) @ e_xy - e_norm, h_norm = _normalized_fields( - e=e, h=h, dxes=dxes, epsilon=epsilon, prop_phase=prop_phase, - ) + e_norm, h_norm = _normalized_fields(e=e, h=h, omega=omega, dxes=dxes, rmin=rmin, epsilon=epsilon, + mu=mu, prop_phase=prop_phase) return e_norm, h_norm def _normalized_fields( e: vcfdslice, h: vcfdslice, + omega: complex, 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[vcfdslice_t, vcfdslice_t]: r""" diff --git a/meanas/fdmath/functional.py b/meanas/fdmath/functional.py index 27d368a..034d4ba 100644 --- a/meanas/fdmath/functional.py +++ b/meanas/fdmath/functional.py @@ -10,7 +10,7 @@ import numpy from numpy.typing import NDArray from numpy import floating, complexfloating -from .types import fdfield, fdfield_updater_t +from .types import fdfield_t, fdfield_updater_t def deriv_forward( @@ -127,7 +127,7 @@ def curl_forward_parts( ) -> Callable: Dx, Dy, Dz = deriv_forward(dx_e) - def mkparts_fwd(e: fdfield) -> tuple[tuple[fdfield, fdfield], ...]: + def mkparts_fwd(e: fdfield_t) -> tuple[tuple[fdfield_t, fdfield_t], ...]: return ((-Dz(e[1]), Dy(e[2])), ( Dz(e[0]), -Dx(e[2])), (-Dy(e[0]), Dx(e[1]))) @@ -140,7 +140,7 @@ def curl_back_parts( ) -> Callable: Dx, Dy, Dz = deriv_back(dx_h) - def mkparts_back(h: fdfield) -> tuple[tuple[fdfield, fdfield], ...]: + def mkparts_back(h: fdfield_t) -> tuple[tuple[fdfield_t, fdfield_t], ...]: return ((-Dz(h[1]), Dy(h[2])), ( Dz(h[0]), -Dx(h[2])), (-Dy(h[0]), Dx(h[1]))) diff --git a/meanas/fdmath/operators.py b/meanas/fdmath/operators.py index 8b7cabc..0c64ae7 100644 --- a/meanas/fdmath/operators.py +++ b/meanas/fdmath/operators.py @@ -9,7 +9,7 @@ from numpy.typing import NDArray from numpy import floating, complexfloating from scipy import sparse -from .types import vfdfield +from .types import vfdfield_t def shift_circ( @@ -171,7 +171,7 @@ def cross( [-B[1], B[0], zero]]) -def vec_cross(b: vfdfield) -> sparse.sparray: +def vec_cross(b: vfdfield_t) -> sparse.sparray: """ Vector cross product operator diff --git a/meanas/fdmath/types.py b/meanas/fdmath/types.py index b82a5ae..222d18a 100644 --- a/meanas/fdmath/types.py +++ b/meanas/fdmath/types.py @@ -88,8 +88,8 @@ dx_lists2_mut = MutableSequence[MutableSequence[NDArray[floating | complexfloati """Mutable version of `dx_lists2_t`""" -fdfield_updater_t = Callable[..., fdfield] -"""Convenience type for functions which take and return a real `fdfield`""" +fdfield_updater_t = Callable[..., fdfield_t] +"""Convenience type for functions which take and return an fdfield_t""" -cfdfield_updater_t = Callable[..., cfdfield] -"""Convenience type for functions which take and return a complex `cfdfield`""" +cfdfield_updater_t = Callable[..., cfdfield_t] +"""Convenience type for functions which take and return an cfdfield_t""" diff --git a/meanas/fdtd/base.py b/meanas/fdtd/base.py index 480ed87..3891e28 100644 --- a/meanas/fdtd/base.py +++ b/meanas/fdtd/base.py @@ -3,7 +3,7 @@ Basic FDTD field updates """ -from ..fdmath import dx_lists_t, fdfield, fdfield_updater_t +from ..fdmath import dx_lists_t, fdfield_t, fdfield_updater_t from ..fdmath.functional import curl_forward, curl_back @@ -47,7 +47,7 @@ def maxwell_e( else: curl_h_fun = curl_back() - def me_fun(e: fdfield, h: fdfield, epsilon: fdfield | float) -> fdfield: + def me_fun(e: fdfield_t, h: fdfield_t, epsilon: fdfield_t | float) -> fdfield_t: """ Update the E-field. @@ -103,7 +103,7 @@ def maxwell_h( else: curl_e_fun = curl_forward() - def mh_fun(e: fdfield, h: fdfield, mu: fdfield | float | None = None) -> fdfield: + def mh_fun(e: fdfield_t, h: fdfield_t, mu: fdfield_t | float | None = None) -> fdfield_t: """ Update the H-field. diff --git a/meanas/fdtd/boundaries.py b/meanas/fdtd/boundaries.py index ca8940d..aa0bff5 100644 --- a/meanas/fdtd/boundaries.py +++ b/meanas/fdtd/boundaries.py @@ -6,7 +6,7 @@ Boundary conditions from typing import Any -from ..fdmath import fdfield, fdfield_updater_t +from ..fdmath import fdfield_t, fdfield_updater_t def conducting_boundary( @@ -15,7 +15,7 @@ def conducting_boundary( ) -> tuple[fdfield_updater_t, fdfield_updater_t]: dirs = [0, 1, 2] if direction not in dirs: - raise ValueError(f'Invalid direction: {direction}') + raise Exception(f'Invalid direction: {direction}') dirs.remove(direction) u, v = dirs @@ -31,13 +31,13 @@ def conducting_boundary( boundary = tuple(boundary_slice) shifted1 = tuple(shifted1_slice) - def en(e: fdfield) -> fdfield: + def en(e: fdfield_t) -> fdfield_t: e[direction][boundary] = 0 e[u][boundary] = e[u][shifted1] e[v][boundary] = e[v][shifted1] return e - def hn(h: fdfield) -> fdfield: + def hn(h: fdfield_t) -> fdfield_t: h[direction][boundary] = h[direction][shifted1] h[u][boundary] = 0 h[v][boundary] = 0 @@ -56,14 +56,14 @@ def conducting_boundary( shifted1 = tuple(shifted1_slice) shifted2 = tuple(shifted2_slice) - def ep(e: fdfield) -> fdfield: + def ep(e: fdfield_t) -> fdfield_t: e[direction][boundary] = -e[direction][shifted2] e[direction][shifted1] = 0 e[u][boundary] = e[u][shifted1] e[v][boundary] = e[v][shifted1] return e - def hp(h: fdfield) -> fdfield: + def hp(h: fdfield_t) -> fdfield_t: h[direction][boundary] = h[direction][shifted1] h[u][boundary] = -h[u][shifted2] h[u][shifted1] = 0 @@ -73,4 +73,4 @@ def conducting_boundary( return ep, hp - raise ValueError(f'Bad polarity: {polarity}') + raise Exception(f'Bad polarity: {polarity}') diff --git a/meanas/fdtd/misc.py b/meanas/fdtd/misc.py index 585c745..89ccb3d 100644 --- a/meanas/fdtd/misc.py +++ b/meanas/fdtd/misc.py @@ -1,6 +1,5 @@ from collections.abc import Callable import logging -from typing import cast import numpy from numpy.typing import NDArray, ArrayLike @@ -10,14 +9,7 @@ from numpy import pi logger = logging.getLogger(__name__) -type pulse_scalar_t = float | NDArray[numpy.floating] -pulse_fn_t = Callable[[ArrayLike], tuple[pulse_scalar_t, pulse_scalar_t, pulse_scalar_t]] - - -def _scalar_or_array(values: NDArray[numpy.floating]) -> pulse_scalar_t: - if values.ndim == 0: - return float(values) - return cast('NDArray[numpy.floating]', values) +pulse_fn_t = Callable[[int | NDArray], tuple[float, float, float]] def gaussian_packet( @@ -57,9 +49,8 @@ def gaussian_packet( delay = numpy.ceil(delay * freq) / freq # force delay to integer number of periods to maintain phase logger.info(f'src_time {2 * delay / dt}') - def source_phasor(ii: ArrayLike) -> tuple[pulse_scalar_t, pulse_scalar_t, pulse_scalar_t]: - ii_array = numpy.asarray(ii, dtype=float) - t0 = ii_array * dt - delay + def source_phasor(ii: int | NDArray) -> tuple[float, float, float]: + t0 = ii * dt - delay envelope = numpy.sqrt(numpy.sqrt(2 * alpha / pi)) * numpy.exp(-alpha * t0 * t0) if one_sided: @@ -68,7 +59,7 @@ def gaussian_packet( cc = numpy.cos(omega * t0) ss = numpy.sin(omega * t0) - return _scalar_or_array(envelope), _scalar_or_array(cc), _scalar_or_array(ss) + return envelope, cc, ss # nrm = numpy.exp(-omega * omega / alpha) / 2 @@ -114,16 +105,15 @@ def ricker_pulse( delay = delay_results.root delay = numpy.ceil(delay * freq) / freq # force delay to integer number of periods to maintain phase - def source_phasor(ii: ArrayLike) -> tuple[pulse_scalar_t, pulse_scalar_t, pulse_scalar_t]: - ii_array = numpy.asarray(ii, dtype=float) - t0 = ii_array * dt - delay + def source_phasor(ii: int | NDArray) -> tuple[float, float, float]: + t0 = ii * dt - delay rr = omega * t0 / 2 ff = (1 - 2 * rr * rr) * numpy.exp(-rr * rr) cc = numpy.cos(omega * t0) ss = numpy.sin(omega * t0) - return _scalar_or_array(ff), _scalar_or_array(cc), _scalar_or_array(ss) + return ff, cc, ss return source_phasor, delay diff --git a/meanas/fdtd/pml.py b/meanas/fdtd/pml.py index aba9cb7..bf61b4e 100644 --- a/meanas/fdtd/pml.py +++ b/meanas/fdtd/pml.py @@ -23,7 +23,7 @@ from copy import deepcopy import numpy from numpy.typing import NDArray, DTypeLike -from ..fdmath import fdfield, dx_lists_t +from ..fdmath import fdfield, fdfield_t, dx_lists_t from ..fdmath.functional import deriv_forward, deriv_back @@ -67,16 +67,16 @@ def cpml_params( """ if axis not in range(3): - raise ValueError(f'Invalid axis: {axis}') + raise Exception(f'Invalid axis: {axis}') if polarity not in (-1, 1): - raise ValueError(f'Invalid polarity: {polarity}') + raise Exception(f'Invalid polarity: {polarity}') if thickness <= 2: - raise ValueError('It would be wise to have a pml with 4+ cells of thickness') + raise Exception('It would be wise to have a pml with 4+ cells of thickness') if epsilon_eff <= 0: - raise ValueError('epsilon_eff must be positive') + raise Exception('epsilon_eff must be positive') sigma_max = -ln_R_per_layer / 2 * (m + 1) kappa_max = numpy.sqrt(epsilon_eff * mu_eff) @@ -129,7 +129,8 @@ def updates_with_cpml( epsilon: fdfield, *, dtype: DTypeLike = numpy.float32, - ) -> tuple[Callable[..., None], Callable[..., None]]: + ) -> tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None], + Callable[[fdfield_t, fdfield_t, fdfield_t], None]]: """ Build Yee-step update closures augmented with CPML terms. @@ -186,9 +187,9 @@ def updates_with_cpml( pH = numpy.empty_like(epsilon, dtype=dtype) def update_E( - e: fdfield, - h: fdfield, - epsilon: fdfield, + e: fdfield_t, + h: fdfield_t, + epsilon: fdfield_t, ) -> None: dyHx = Dby(h[0]) dzHx = Dbz(h[0]) @@ -232,9 +233,9 @@ def updates_with_cpml( e[2] += dt / epsilon[2] * (dxHy - dyHx + pE[2]) def update_H( - e: fdfield, - h: fdfield, - mu: fdfield | tuple[int, int, int] = (1, 1, 1), + e: fdfield_t, + h: fdfield_t, + mu: fdfield_t | tuple[int, int, int] = (1, 1, 1), ) -> None: dyEx = Dfy(e[0]) dzEx = Dfz(e[0]) diff --git a/meanas/test/test_bloch_interactions.py b/meanas/test/test_bloch_interactions.py index 0628a55..b67d5ce 100644 --- a/meanas/test/test_bloch_interactions.py +++ b/meanas/test/test_bloch_interactions.py @@ -4,7 +4,7 @@ from numpy.testing import assert_allclose from types import SimpleNamespace from ..fdfd import bloch -from ._bloch_case import EPSILON, G_MATRIX, H_SIZE, K0_X, Y0, Y0_TWO_MODE, build_overlap_fixture +from ._bloch_case import EPSILON, G_MATRIX, H_SIZE, K0_X, SHAPE, Y0, Y0_TWO_MODE, build_overlap_fixture from .utils import assert_close diff --git a/meanas/test/test_eme_numerics.py b/meanas/test/test_eme_numerics.py index 3237c1b..8798e0d 100644 --- a/meanas/test/test_eme_numerics.py +++ b/meanas/test/test_eme_numerics.py @@ -1,11 +1,8 @@ -from typing import cast - import numpy -import pytest from scipy import sparse from ..fdmath import vec -from ..fdfd import eme, waveguide_2d, waveguide_cyl +from ..fdfd import eme from ._test_builders import complex_ramp, unit_dxes from .utils import assert_close @@ -14,8 +11,6 @@ SHAPE = (3, 2, 2) DXES = unit_dxes((2, 2)) WAVENUMBERS_L = numpy.array([1.0, 0.8]) WAVENUMBERS_R = numpy.array([0.9, 0.7]) -OMEGA = 1 / 1500 -REAL_DXES = unit_dxes((5, 5)) def _mode(scale: float) -> tuple[numpy.ndarray, numpy.ndarray]: @@ -53,10 +48,6 @@ def _nonsymmetric_tr(left_marker: object): return fake_get_tr -def _dummy_modes() -> tuple[list[tuple[numpy.ndarray, numpy.ndarray]], numpy.ndarray]: - return [_mode(0.0), _mode(0.7)], numpy.array([1.0, 0.5]) - - def test_get_tr_returns_finite_bounded_transfer_matrices() -> None: left_modes, right_modes = _mode_sets() @@ -109,10 +100,9 @@ def test_get_s_plain_matches_block_assembly_from_get_tr() -> None: def test_get_s_force_nogain_caps_singular_values(monkeypatch) -> None: monkeypatch.setattr(eme, 'get_tr', _gain_only_tr) - modes, wavenumbers = _dummy_modes() - plain_s = eme.get_s(modes, wavenumbers, modes, wavenumbers) - clipped_s = eme.get_s(modes, wavenumbers, modes, wavenumbers, force_nogain=True) + plain_s = eme.get_s(None, None, None, None) + clipped_s = eme.get_s(None, None, None, None, force_nogain=True) plain_singular_values = numpy.linalg.svd(plain_s, compute_uv=False) clipped_singular_values = numpy.linalg.svd(clipped_s, compute_uv=False) @@ -123,369 +113,20 @@ def test_get_s_force_nogain_caps_singular_values(monkeypatch) -> None: def test_get_s_force_reciprocal_symmetrizes_output(monkeypatch) -> None: - left = numpy.array([1.0, 0.5]) - right = numpy.array([0.9, 0.4]) - modes, _wavenumbers = _dummy_modes() + left = object() + right = object() monkeypatch.setattr(eme, 'get_tr', _nonsymmetric_tr(left)) - ss = eme.get_s(modes, left, modes, right, force_reciprocal=True) + ss = eme.get_s(None, left, None, right, force_reciprocal=True) assert_close(ss, ss.T) def test_get_s_force_nogain_and_reciprocal_returns_finite_output(monkeypatch) -> None: monkeypatch.setattr(eme, 'get_tr', _gain_and_reflection_tr) - modes, wavenumbers = _dummy_modes() - ss = eme.get_s(modes, wavenumbers, modes, wavenumbers, force_nogain=True, force_reciprocal=True) + ss = eme.get_s(None, None, None, None, force_nogain=True, force_reciprocal=True) assert ss.shape == (4, 4) assert numpy.isfinite(ss).all() assert_close(ss, ss.T) assert (numpy.linalg.svd(ss, compute_uv=False) <= 1.0 + 1e-12).all() - - -def test_get_tr_rejects_length_mismatches() -> None: - left_modes, right_modes = _mode_sets() - - with pytest.raises(ValueError, match='same length'): - eme.get_tr(left_modes[:1], WAVENUMBERS_L, right_modes, WAVENUMBERS_R, dxes=DXES) - - -def test_get_tr_rejects_malformed_mode_tuples() -> None: - bad_modes = cast(list[tuple[numpy.ndarray, numpy.ndarray]], [(numpy.ones(4, dtype=complex),)]) - - with pytest.raises(ValueError, match='2-tuple'): - eme.get_tr(bad_modes, [1.0], bad_modes, [1.0], dxes=DXES) - - -def test_get_tr_rejects_incompatible_field_shapes() -> None: - left_modes = [(numpy.ones(4, dtype=complex), numpy.ones(4, dtype=complex))] - right_modes = [(numpy.ones(6, dtype=complex), numpy.ones(6, dtype=complex))] - - with pytest.raises(ValueError, match='same E/H shapes'): - eme.get_tr(left_modes, [1.0], right_modes, [1.0], dxes=DXES) - - -def _build_real_epsilon() -> numpy.ndarray: - epsilon = numpy.ones((3, 5, 5), dtype=float) - epsilon[:, 2, 1] = 2.0 - return vec(epsilon) - - -def _build_straight_mode() -> tuple[tuple[numpy.ndarray, numpy.ndarray], complex, numpy.ndarray]: - epsilon = _build_real_epsilon() - e_xy, wavenumber = waveguide_2d.solve_mode( - 0, - omega=OMEGA, - dxes=REAL_DXES, - epsilon=epsilon, - ) - e_field, h_field = waveguide_2d.normalized_fields_e( - e_xy, - wavenumber=wavenumber, - omega=OMEGA, - dxes=REAL_DXES, - epsilon=epsilon, - ) - return (e_field, h_field), wavenumber, epsilon - - -def _build_bend_mode() -> tuple[tuple[numpy.ndarray, numpy.ndarray], complex]: - epsilon = vec(numpy.ones((3, 5, 5), dtype=float)) - rmin = 10.0 - e_xy, angular_wavenumber = waveguide_cyl.solve_mode( - 0, - omega=OMEGA, - dxes=REAL_DXES, - epsilon=epsilon, - rmin=rmin, - ) - linear_wavenumber = waveguide_cyl.linear_wavenumbers( - [e_xy], - [angular_wavenumber], - epsilon=epsilon, - dxes=REAL_DXES, - rmin=rmin, - )[0] - e_field, h_field = waveguide_cyl.normalized_fields_e( - e_xy, - angular_wavenumber=angular_wavenumber, - omega=OMEGA, - dxes=REAL_DXES, - epsilon=epsilon, - rmin=rmin, - ) - return (e_field, h_field), linear_wavenumber - - -def _build_uniform_mode(index: float) -> tuple[tuple[numpy.ndarray, numpy.ndarray], complex]: - area = 25.0 - e_field = numpy.zeros((3, 5, 5), dtype=complex) - h_field = numpy.zeros((3, 5, 5), dtype=complex) - e_field[0] = numpy.sqrt(2.0 / (index * area)) - h_field[1] = numpy.sqrt(2.0 * index / area) - return (vec(e_field), vec(h_field)), complex(index * OMEGA) - - -def _interface_s(n_left: float, n_right: float) -> numpy.ndarray: - left_mode, left_beta = _build_uniform_mode(n_left) - right_mode, right_beta = _build_uniform_mode(n_right) - return eme.get_s([left_mode], [left_beta], [right_mode], [right_beta], dxes=REAL_DXES) - - -def _interface_abcd(n_left: float, n_right: float) -> numpy.ndarray: - left_mode, left_beta = _build_uniform_mode(n_left) - right_mode, right_beta = _build_uniform_mode(n_right) - return eme.get_abcd([left_mode], [left_beta], [right_mode], [right_beta], dxes=REAL_DXES).toarray() - - -def _expected_interface_s(n_left: float, n_right: float) -> numpy.ndarray: - reflection = (n_left - n_right) / (n_left + n_right) - transmission = 2 * numpy.sqrt(n_left * n_right) / (n_left + n_right) - return numpy.array( - [ - [reflection, transmission], - [transmission, -reflection], - ], - dtype=complex, - ) - - -def _propagation_abcd(beta: complex, length: float) -> numpy.ndarray: - phase = numpy.exp(-1j * beta * length) - return numpy.array( - [ - [phase, 0.0], - [0.0, phase ** -1], - ], - dtype=complex, - ) - - -def _abcd_to_s(abcd: numpy.ndarray) -> numpy.ndarray: - aa = abcd[0, 0] - bb = abcd[0, 1] - cc = abcd[1, 0] - dd = abcd[1, 1] - t21 = 1.0 / dd - r21 = bb / dd - r12 = -cc / dd - t12 = aa - bb * cc / dd - return numpy.array( - [ - [r12, t12], - [t21, r21], - ], - dtype=complex, - ) - - -def _expected_bragg_reflector_s(n_low: float, n_high: float, periods: int) -> numpy.ndarray: - ratio = n_high / n_low - reflection = (1 - ratio ** (2 * periods)) / (1 + ratio ** (2 * periods)) - transmission = ((-1) ** periods) * 2 * ratio ** periods / (1 + ratio ** (2 * periods)) - return numpy.array( - [ - [reflection, transmission], - [transmission, -reflection], - ], - dtype=complex, - ) - - -def test_get_s_is_near_identity_for_identical_solved_straight_modes() -> None: - mode, wavenumber, _epsilon = _build_straight_mode() - - ss = eme.get_s([mode], [wavenumber], [mode], [wavenumber], dxes=REAL_DXES) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert abs(ss[0, 0]) < 1e-6 - assert abs(ss[1, 1]) < 1e-6 - assert abs(abs(ss[0, 1]) - 1.0) < 1e-6 - assert abs(abs(ss[1, 0]) - 1.0) < 1e-6 - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_get_s_returns_finite_passive_output_for_small_straight_to_bend_fixture() -> None: - straight_mode, straight_wavenumber, _epsilon = _build_straight_mode() - bend_mode, bend_wavenumber = _build_bend_mode() - - ss = eme.get_s([straight_mode], [straight_wavenumber], [bend_mode], [bend_wavenumber], dxes=REAL_DXES) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_get_s_matches_analytic_fresnel_interface_for_uniform_one_mode_ports() -> None: - """ - For power-normalized one-mode ports at normal incidence, the interface matrix is - - r12 = (n_left - n_right) / (n_left + n_right) - r21 = -r12 - t12 = t21 = 2 * sqrt(n_left * n_right) / (n_left + n_right) - - so - - S = [[r12, t12], [t21, r21]]. - """ - ss = _interface_s(1.0, 2.0) - expected = _expected_interface_s(1.0, 2.0) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert_close(ss, expected, atol=1e-6, rtol=1e-6) - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_quarter_wave_matching_layer_is_nearly_reflectionless_at_design_frequency() -> None: - """ - A single quarter-wave matching layer with - - n1 = sqrt(n0 * n2), beta1 * L = pi / 2 - - cancels the two interface reflections at the design wavelength, so the - normal-incidence stack should satisfy `r = 0` and `|t| = 1`. - """ - n0 = 1.0 - n1 = numpy.sqrt(2.0) - n2 = 2.0 - interface_01 = _interface_abcd(n0, n1) - interface_12 = _interface_abcd(n1, n2) - _mode_1, beta_1 = _build_uniform_mode(float(n1)) - quarter_wave_length = numpy.pi / (2 * numpy.real(beta_1)) - - stack_abcd = interface_01 @ _propagation_abcd(beta_1, quarter_wave_length) @ interface_12 - ss = _abcd_to_s(stack_abcd) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert abs(ss[0, 0]) < 1e-12 - assert abs(ss[1, 1]) < 1e-12 - assert abs(abs(ss[0, 1]) - 1.0) < 1e-12 - assert abs(abs(ss[1, 0]) - 1.0) < 1e-12 - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_quarter_wave_ar_layer_reduces_reflection_relative_to_abrupt_interface() -> None: - """ - Compare the abrupt interface `n0 -> n2` against the quarter-wave matching-layer - stack `n0 -> sqrt(n0 n2) -> n2` at the same design wavelength. - - For the canonical `n0 = 1`, `n2 = 2` case, the abrupt interface has - - |r_abrupt| = |(n0 - n2) / (n0 + n2)| = 1 / 3, - - while the quarter-wave matching layer should cancel the interface reflections - so that `|r_ar|` is essentially zero and `|t_ar|` is correspondingly larger. - """ - n0 = 1.0 - n2 = 2.0 - abrupt = _interface_s(n0, n2) - - n1 = numpy.sqrt(n0 * n2) - interface_01 = _interface_abcd(n0, n1) - interface_12 = _interface_abcd(n1, n2) - _mode_1, beta_1 = _build_uniform_mode(float(n1)) - quarter_wave_length = numpy.pi / (2 * numpy.real(beta_1)) - ar_stack = _abcd_to_s(interface_01 @ _propagation_abcd(beta_1, quarter_wave_length) @ interface_12) - - abrupt_reflection = abs(abrupt[0, 0]) - abrupt_transmission = abs(abrupt[1, 0]) - ar_reflection = abs(ar_stack[0, 0]) - ar_transmission = abs(ar_stack[1, 0]) - - assert numpy.linalg.svd(abrupt, compute_uv=False).max() <= 1.0 + 1e-10 - assert numpy.linalg.svd(ar_stack, compute_uv=False).max() <= 1.0 + 1e-10 - assert ar_reflection < abrupt_reflection - assert ar_transmission > abrupt_transmission - assert ar_reflection < 1e-12 - assert abs(abrupt_reflection - (1.0 / 3.0)) < 1e-12 - - -def test_half_wave_uniform_slab_restores_unit_transmission_between_matched_media() -> None: - """ - For matched exterior media `n0 = n2`, a half-wave slab with - - beta1 * L = pi - - contributes only a global phase, so the stack returns to `r = 0` and - `|t| = 1` at the design wavelength. - """ - n0 = 1.0 - n1 = 2.0 - interface_01 = _interface_abcd(n0, n1) - interface_10 = _interface_abcd(n1, n0) - _mode_1, beta_1 = _build_uniform_mode(n1) - half_wave_length = numpy.pi / numpy.real(beta_1) - - stack_abcd = interface_01 @ _propagation_abcd(beta_1, half_wave_length) @ interface_10 - ss = _abcd_to_s(stack_abcd) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert abs(ss[0, 0]) < 1e-12 - assert abs(ss[1, 1]) < 1e-12 - assert abs(abs(ss[0, 1]) - 1.0) < 1e-12 - assert abs(abs(ss[1, 0]) - 1.0) < 1e-12 - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_strong_uniform_index_mismatch_behaves_like_near_termination() -> None: - """ - In the large-index-ratio limit, the same Fresnel formulas approach a hard-wall - reflector: - - |r| -> 1, |t| -> 0 as n_right / n_left -> infinity. - """ - ss = _interface_s(1.0, 100.0) - expected = _expected_interface_s(1.0, 100.0) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert_close(ss, expected, atol=1e-6, rtol=1e-6) - assert abs(ss[0, 0]) > 0.9 - assert abs(ss[1, 0]) < 0.25 - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 - - -def test_quarter_wave_bragg_reflector_matches_closed_form_stopband_response() -> None: - """ - For `N` quarter-wave high/low periods at the Bragg wavelength with identical - low-index incident and exit media (`n0 = ns = n_low`), - - M_pair = diag(-(n_low / n_high), -(n_high / n_low)) - M_stack = M_pair ** N - - which yields the closed-form scattering amplitudes - - r = (1 - (n_high / n_low) ** (2N)) / (1 + (n_high / n_low) ** (2N)) - t = 2 * (n_high / n_low) ** N / (1 + (n_high / n_low) ** (2N)). - - The reflector should therefore sit deep in the stopband with `|r|` near 1 and - `|t|` correspondingly small. - """ - n_low = 1.0 - n_high = 2.0 - periods = 5 - interface_lh = _interface_abcd(n_low, n_high) - interface_hl = _interface_abcd(n_high, n_low) - _mode_h, beta_h = _build_uniform_mode(n_high) - _mode_l, beta_l = _build_uniform_mode(n_low) - quarter_wave_high = numpy.pi / (2 * numpy.real(beta_h)) - quarter_wave_low = numpy.pi / (2 * numpy.real(beta_l)) - - stack_abcd = numpy.eye(2, dtype=complex) - for _ in range(periods): - stack_abcd = stack_abcd @ interface_lh @ _propagation_abcd(beta_h, quarter_wave_high) - stack_abcd = stack_abcd @ interface_hl @ _propagation_abcd(beta_l, quarter_wave_low) - ss = _abcd_to_s(stack_abcd) - expected = _expected_bragg_reflector_s(n_low, n_high, periods) - - assert ss.shape == (2, 2) - assert numpy.isfinite(ss).all() - assert_close(ss, expected, atol=1e-12, rtol=1e-12) - assert abs(ss[0, 0]) > 0.99 - assert abs(ss[1, 0]) < 0.1 - assert numpy.linalg.svd(ss, compute_uv=False).max() <= 1.0 + 1e-10 diff --git a/meanas/test/test_examples_smoke.py b/meanas/test/test_examples_smoke.py deleted file mode 100644 index b21f90b..0000000 --- a/meanas/test/test_examples_smoke.py +++ /dev/null @@ -1,47 +0,0 @@ -from pathlib import Path -import os -import subprocess -import sys - -import pytest - - -pytestmark = pytest.mark.complete - -REPO_ROOT = Path(__file__).resolve().parents[2] - - -def _run_example(example_name: str, tmp_path: Path) -> subprocess.CompletedProcess[str]: - env = os.environ.copy() - env['MPLBACKEND'] = 'Agg' - env['MPLCONFIGDIR'] = str(tmp_path / f'mpl-{example_name}') - return subprocess.run( - [sys.executable, str(REPO_ROOT / 'examples' / example_name)], - cwd=REPO_ROOT, - env=env, - text=True, - capture_output=True, - check=False, - timeout=60, - ) - - -def test_eme_example_smoke_runs(tmp_path: Path) -> None: - pytest.importorskip('matplotlib') - - result = _run_example('eme.py', tmp_path) - - assert result.returncode == 0, result.stdout + result.stderr - assert 'left effective indices:' in result.stdout - assert 'fundamental left-to-right transmission' in result.stdout - - -def test_eme_bend_example_smoke_runs(tmp_path: Path) -> None: - pytest.importorskip('matplotlib') - pytest.importorskip('skrf') - - result = _run_example('eme_bend.py', tmp_path) - - assert result.returncode == 0, result.stdout + result.stderr - assert 'straight effective indices:' in result.stdout - assert 'cascaded bend through power' in result.stdout diff --git a/meanas/test/test_fdfd_pml.py b/meanas/test/test_fdfd_pml.py index 1a8d66c..540a3a0 100644 --- a/meanas/test/test_fdfd_pml.py +++ b/meanas/test/test_fdfd_pml.py @@ -85,10 +85,8 @@ def j_distribution( other_dims = [0, 1, 2] other_dims.remove(dim) - dx_prop = ( - dxes[0][dim][shape[dim + 1] // 2] - + dxes[1][dim][shape[dim + 1] // 2] - ) / 2 # TODO is this right for nonuniform dxes? + dx_prop = (dxes[0][dim][shape[dim + 1] // 2] + + 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 center_mask = numpy.zeros(shape, dtype=bool) diff --git a/meanas/test/test_fdfd_solvers.py b/meanas/test/test_fdfd_solvers.py index de39d70..b841dc9 100644 --- a/meanas/test/test_fdfd_solvers.py +++ b/meanas/test/test_fdfd_solvers.py @@ -1,5 +1,3 @@ -from typing import cast - import numpy from ..fdfd import solvers @@ -43,7 +41,7 @@ def test_scipy_qmr_installs_logging_callback_when_missing(monkeypatch) -> None: def test_generic_forward_preconditions_system_and_guess(monkeypatch) -> None: case = solver_plumbing_case() - captured: dict[str, numpy.ndarray | float | object] = {} + captured: dict[str, object] = {} monkeypatch.setattr(solvers.operators, 'e_full', lambda *args, **kwargs: case.a0) monkeypatch.setattr(solvers.operators, 'e_full_preconditioners', lambda dxes: (case.pl, case.pr)) @@ -65,16 +63,16 @@ def test_generic_forward_preconditions_system_and_guess(monkeypatch) -> None: E_guess=case.guess, ) - assert_close(cast(object, captured['a']).toarray(), (case.pl @ case.a0 @ case.pr).toarray()) # type: ignore[attr-defined] - assert_close(cast(numpy.ndarray, captured['b']), case.pl @ (-1j * case.omega * case.j)) - assert_close(cast(numpy.ndarray, captured['x0']), case.pl @ case.guess) + assert_close(captured['a'].toarray(), (case.pl @ case.a0 @ case.pr).toarray()) + assert_close(captured['b'], case.pl @ (-1j * case.omega * case.j)) + assert_close(captured['x0'], case.pl @ case.guess) assert captured['atol'] == 1e-12 assert_close(result, case.pr @ case.solver_result) def test_generic_adjoint_preconditions_system_and_guess(monkeypatch) -> None: case = solver_plumbing_case() - captured: dict[str, numpy.ndarray | float | object] = {} + captured: dict[str, object] = {} monkeypatch.setattr(solvers.operators, 'e_full', lambda *args, **kwargs: case.a0) monkeypatch.setattr(solvers.operators, 'e_full_preconditioners', lambda dxes: (case.pl, case.pr)) @@ -98,9 +96,9 @@ def test_generic_adjoint_preconditions_system_and_guess(monkeypatch) -> None: ) expected_matrix = (case.pl @ case.a0 @ case.pr).T.conjugate() - assert_close(cast(object, captured['a']).toarray(), expected_matrix.toarray()) # type: ignore[attr-defined] - assert_close(cast(numpy.ndarray, captured['b']), case.pr.T.conjugate() @ (-1j * case.omega * case.j)) - assert_close(cast(numpy.ndarray, captured['x0']), case.pr.T.conjugate() @ case.guess) + assert_close(captured['a'].toarray(), expected_matrix.toarray()) + assert_close(captured['b'], case.pr.T.conjugate() @ (-1j * case.omega * case.j)) + assert_close(captured['x0'], case.pr.T.conjugate() @ case.guess) assert captured['rtol'] == 1e-9 assert_close(result, case.pl.T.conjugate() @ case.solver_result) @@ -124,5 +122,5 @@ def test_generic_without_guess_does_not_inject_x0(monkeypatch) -> None: matrix_solver=fake_solver, ) - assert 'x0' not in cast(dict[str, object], captured['kwargs']) + assert 'x0' not in captured['kwargs'] assert_close(result, case.pr @ numpy.array([1.0, -1.0])) diff --git a/meanas/test/test_fdtd_base.py b/meanas/test/test_fdtd_base.py index c8246d5..bc1f514 100644 --- a/meanas/test/test_fdtd_base.py +++ b/meanas/test/test_fdtd_base.py @@ -1,3 +1,5 @@ +import numpy + from ..fdmath import functional as fd_functional from ..fdtd import base from ._test_builders import real_ramp diff --git a/meanas/test/test_fdtd_boundaries.py b/meanas/test/test_fdtd_boundaries.py index d60ca7a..d7ba186 100644 --- a/meanas/test/test_fdtd_boundaries.py +++ b/meanas/test/test_fdtd_boundaries.py @@ -58,5 +58,5 @@ def test_conducting_boundary_updates_expected_faces(direction: int, polarity: in [(-1, 1), (3, 1), (0, 0)], ) def test_conducting_boundary_rejects_invalid_arguments(direction: int, polarity: int) -> None: - with pytest.raises(ValueError, match='Invalid direction|Bad polarity'): + with pytest.raises(Exception): conducting_boundary(direction, polarity) diff --git a/meanas/test/test_fdtd_misc.py b/meanas/test/test_fdtd_misc.py index 3688c6c..65dc713 100644 --- a/meanas/test/test_fdtd_misc.py +++ b/meanas/test/test_fdtd_misc.py @@ -10,9 +10,6 @@ def test_gaussian_packet_accepts_array_input(one_sided: bool) -> None: source, delay = gaussian_packet(1.55, 0.1, dt, one_sided=one_sided) steps = numpy.array([0, int(numpy.ceil(delay / dt)) + 5]) envelope, cc, ss = source(steps) - assert isinstance(envelope, numpy.ndarray) - assert isinstance(cc, numpy.ndarray) - assert isinstance(ss, numpy.ndarray) assert envelope.shape == (2,) assert numpy.isfinite(envelope).all() diff --git a/meanas/test/test_fdtd_phasor.py b/meanas/test/test_fdtd_phasor.py index 9d28ee3..7e1126b 100644 --- a/meanas/test/test_fdtd_phasor.py +++ b/meanas/test/test_fdtd_phasor.py @@ -371,7 +371,7 @@ def _real_pulse_case() -> RealPulseCase: source_phasor, _delay = gaussian_packet(wl=wavelength, dwl=1.0, dt=dt, turn_on=1e-5) aa, cc, ss = source_phasor(numpy.arange(total_steps) + 0.5) - waveform = numpy.asarray(aa * (cc + 1j * ss), dtype=complex) + waveform = aa * (cc + 1j * ss) scale = fdtd.real_injection_scale(waveform, omega, dt, offset_steps=0.5)[0] j_accumulator = numpy.zeros((1, *full_shape), dtype=complex) diff --git a/meanas/test/test_fdtd_pml.py b/meanas/test/test_fdtd_pml.py index 319260f..9d8aef8 100644 --- a/meanas/test/test_fdtd_pml.py +++ b/meanas/test/test_fdtd_pml.py @@ -1,5 +1,3 @@ -from typing import Any - import numpy import pytest @@ -14,7 +12,7 @@ from .utils import assert_close [(3, 1, 4, 1.0), (0, 0, 4, 1.0), (0, 1, 2, 1.0), (0, 1, 4, 0.0)], ) def test_cpml_params_reject_invalid_arguments(axis: int, polarity: int, thickness: int, epsilon_eff: float) -> None: - with pytest.raises(ValueError, match='Invalid axis|Invalid polarity|wise to have a pml|epsilon_eff must be positive'): + with pytest.raises(Exception): cpml_params(axis=axis, polarity=polarity, dt=0.1, thickness=thickness, epsilon_eff=epsilon_eff) @@ -38,7 +36,7 @@ def test_updates_with_cpml_keeps_zero_fields_zero() -> None: e = numpy.zeros(shape, dtype=float) h = numpy.zeros(shape, dtype=float) dxes = [[numpy.ones(4), numpy.ones(4), numpy.ones(4)] for _ in range(2)] - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] params[0][0] = cpml_params(axis=0, polarity=-1, dt=0.1, thickness=3) update_e, update_h = updates_with_cpml(params, dt=0.1, dxes=dxes, epsilon=epsilon) @@ -71,7 +69,7 @@ def test_updates_with_cpml_matches_base_updates_when_all_faces_disabled() -> Non e = _real_field(shape, 10.0) h = _real_field(shape, 100.0) dxes = _unit_dxes(shape) - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] update_e_cpml, update_h_cpml = updates_with_cpml(params, dt=0.1, dxes=dxes, epsilon=epsilon) update_e_base = maxwell_e(dt=0.1, dxes=dxes) @@ -98,7 +96,7 @@ def test_updates_with_cpml_matches_base_updates_with_complex_dtype_when_all_face e = _complex_field(shape, 10.0) h = _complex_field(shape, 100.0) dxes = _unit_dxes(shape) - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] update_e_cpml, update_h_cpml = updates_with_cpml(params, dt=0.1, dxes=dxes, epsilon=epsilon, dtype=complex) update_e_base = maxwell_e(dt=0.1, dxes=dxes) @@ -127,7 +125,7 @@ def test_updates_with_cpml_only_changes_the_configured_face_region() -> None: dxes = _unit_dxes(shape) thickness = 3 - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] params[0][0] = cpml_params(axis=0, polarity=-1, dt=0.1, thickness=thickness) update_e_cpml, update_h_cpml = updates_with_cpml(params, dt=0.1, dxes=dxes, epsilon=epsilon) @@ -168,7 +166,7 @@ def test_cpml_plane_wave_phasor_decays_monotonically_through_outgoing_pml() -> N epsilon = numpy.ones(shape, dtype=float) dxes = _unit_dxes(shape) - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] for polarity_index, polarity in enumerate((-1, 1)): params[0][polarity_index] = cpml_params(axis=0, polarity=polarity, dt=dt, thickness=thickness) @@ -200,7 +198,6 @@ def test_cpml_plane_wave_phasor_decays_monotonically_through_outgoing_pml() -> N assert numpy.all(numpy.diff(right_pml) <= interior_level * 1e-3) -@pytest.mark.complete def test_cpml_point_source_total_energy_reaches_late_time_plateau() -> None: dt = 0.3 period_steps = 24 @@ -214,7 +211,7 @@ def test_cpml_point_source_total_energy_reaches_late_time_plateau() -> None: epsilon = numpy.ones(shape, dtype=float) dxes = _unit_dxes(shape) - params: list[list[dict[str, Any] | None]] = [[None, None] for _ in range(3)] + params = [[None, None] for _ in range(3)] for axis in range(3): for polarity_index, polarity in enumerate((-1, 1)): params[axis][polarity_index] = cpml_params(axis=axis, polarity=polarity, dt=dt, thickness=thickness) diff --git a/meanas/test/test_import_fallbacks.py b/meanas/test/test_import_fallbacks.py index e332d1b..d1ecca9 100644 --- a/meanas/test/test_import_fallbacks.py +++ b/meanas/test/test_import_fallbacks.py @@ -1,28 +1,25 @@ import builtins import importlib import pathlib -from types import ModuleType -from typing import Any -import pytest import meanas from ..fdfd import bloch +from .utils import assert_close -def _reload(module: ModuleType) -> ModuleType: +def _reload(module): return importlib.reload(module) -def _restore_reloaded(monkeypatch: pytest.MonkeyPatch, module: ModuleType) -> ModuleType: +def _restore_reloaded(monkeypatch, module): monkeypatch.undo() return _reload(module) -def test_meanas_import_survives_readme_open_failure(monkeypatch: pytest.MonkeyPatch) -> None: - expected_version = meanas.__version__ +def test_meanas_import_survives_readme_open_failure(monkeypatch) -> None: # type: ignore[no-untyped-def] original_open = pathlib.Path.open - def failing_open(self: pathlib.Path, *args: Any, **kwargs: Any) -> Any: + def failing_open(self: pathlib.Path, *args, **kwargs): # type: ignore[no-untyped-def] if self.name == 'README.md': raise FileNotFoundError('forced README failure') return original_open(self, *args, **kwargs) @@ -30,23 +27,17 @@ def test_meanas_import_survives_readme_open_failure(monkeypatch: pytest.MonkeyPa monkeypatch.setattr(pathlib.Path, 'open', failing_open) reloaded = _reload(meanas) - assert reloaded.__version__ == expected_version + assert reloaded.__version__ == '0.10' assert reloaded.__author__ == 'Jan Petykiewicz' assert reloaded.__doc__ is not None _restore_reloaded(monkeypatch, meanas) -def test_bloch_reloads_with_numpy_fft_when_pyfftw_is_unavailable(monkeypatch: pytest.MonkeyPatch) -> None: +def test_bloch_reloads_with_numpy_fft_when_pyfftw_is_unavailable(monkeypatch) -> None: # type: ignore[no-untyped-def] original_import = builtins.__import__ - def fake_import( - name: str, - globals: dict[str, Any] | None = None, - locals: dict[str, Any] | None = None, - fromlist: tuple[str, ...] = (), - level: int = 0, - ) -> Any: + def fake_import(name: str, globals=None, locals=None, fromlist=(), level: int = 0): # type: ignore[no-untyped-def] if name.startswith('pyfftw'): raise ImportError('forced pyfftw failure') return original_import(name, globals, locals, fromlist, level) diff --git a/meanas/test/test_waveguide_fdtd_fdfd.py b/meanas/test/test_waveguide_fdtd_fdfd.py index 0488197..167b91e 100644 --- a/meanas/test/test_waveguide_fdtd_fdfd.py +++ b/meanas/test/test_waveguide_fdtd_fdfd.py @@ -2,7 +2,6 @@ import dataclasses from functools import lru_cache import numpy -import pytest from .. import fdfd, fdtd from ..fdtd.misc import gaussian_packet @@ -10,9 +9,6 @@ from ..fdmath import vec, unvec from ..fdfd import functional, scpml, waveguide_3d -pytestmark = pytest.mark.complete - - DT = 0.25 PERIOD_STEPS = 64 OMEGA = 2 * numpy.pi / (PERIOD_STEPS * DT) @@ -224,7 +220,7 @@ def _build_cpml_params() -> list[list[dict[str, numpy.ndarray | float]]]: def _build_complex_pulse_waveform(total_steps: int) -> tuple[numpy.ndarray, complex]: source_phasor, _delay = gaussian_packet(wl=WAVELENGTH, dwl=PULSE_DWL, dt=DT, turn_on=1e-5) aa, cc, ss = source_phasor(numpy.arange(total_steps) + 0.5) - waveform = numpy.asarray(aa * (cc + 1j * ss), dtype=complex) + waveform = aa * (cc + 1j * ss) scale = fdtd.temporal_phasor_scale(waveform, OMEGA, DT, offset_steps=0.5)[0] return waveform, scale @@ -272,7 +268,7 @@ def _run_real_field_straight_waveguide_case() -> RealFieldWaveguideResult: slices=REAL_FIELD_SOURCE_SLICES, epsilon=epsilon, ) - j_mode = j_mode * numpy.exp(1j * REAL_FIELD_SOURCE_PHASE) + j_mode *= numpy.exp(1j * REAL_FIELD_SOURCE_PHASE) monitor_mode = waveguide_3d.solve_mode( 0, omega=OMEGA, @@ -380,6 +376,7 @@ def _run_straight_waveguide_case(variant: str) -> WaveguideCalibrationResult: axis=0, polarity=1, slices=MONITOR_SLICES, + omega=OMEGA, ) update_e, update_h = fdtd.updates_with_cpml(cpml_params=_build_cpml_params(), dt=DT, dxes=base_dxes, epsilon=epsilon) @@ -424,8 +421,8 @@ def _run_straight_waveguide_case(variant: str) -> WaveguideCalibrationResult: ) h_fdfd = functional.e2h(OMEGA, stretched_dxes)(e_fdfd) - overlap_td = complex(vec(e_ph) @ vec(overlap_e).conj()) - overlap_fd = complex(vec(e_fdfd) @ vec(overlap_e).conj()) + overlap_td = vec(e_ph) @ vec(overlap_e).conj() + overlap_fd = vec(e_fdfd) @ vec(overlap_e).conj() poynting_td = functional.poynting_e_cross_h(stretched_dxes)(e_ph, h_ph.conj()) poynting_fd = functional.poynting_e_cross_h(stretched_dxes)(e_fdfd, h_fdfd.conj()) @@ -487,6 +484,7 @@ def _run_width_step_scattering_case() -> WaveguideScatteringResult: axis=0, polarity=-1, slices=SCATTERING_REFLECT_SLICES, + omega=OMEGA, ) transmitted_mode = waveguide_3d.solve_mode( 0, @@ -504,6 +502,7 @@ def _run_width_step_scattering_case() -> WaveguideScatteringResult: axis=0, polarity=1, slices=SCATTERING_TRANSMIT_SLICES, + omega=OMEGA, ) update_e, update_h = fdtd.updates_with_cpml(cpml_params=_build_cpml_params(), dt=DT, dxes=base_dxes, epsilon=epsilon) @@ -548,10 +547,10 @@ def _run_width_step_scattering_case() -> WaveguideScatteringResult: ) h_fdfd = functional.e2h(OMEGA, stretched_dxes)(e_fdfd) - reflected_td = complex(vec(e_ph) @ vec(reflected_overlap).conj()) - reflected_fd = complex(vec(e_fdfd) @ vec(reflected_overlap).conj()) - transmitted_td = complex(vec(e_ph) @ vec(transmitted_overlap).conj()) - transmitted_fd = complex(vec(e_fdfd) @ vec(transmitted_overlap).conj()) + reflected_td = vec(e_ph) @ vec(reflected_overlap).conj() + reflected_fd = vec(e_fdfd) @ vec(reflected_overlap).conj() + transmitted_td = vec(e_ph) @ vec(transmitted_overlap).conj() + transmitted_fd = vec(e_fdfd) @ vec(transmitted_overlap).conj() poynting_td = functional.poynting_e_cross_h(stretched_dxes)(e_ph, h_ph.conj()) poynting_fd = functional.poynting_e_cross_h(stretched_dxes)(e_fdfd, h_fdfd.conj()) @@ -618,6 +617,7 @@ def _run_pulsed_straight_waveguide_case() -> PulsedWaveguideCalibrationResult: axis=0, polarity=1, slices=MONITOR_SLICES, + omega=OMEGA, ) update_e, update_h = fdtd.updates_with_cpml(cpml_params=_build_cpml_params(), dt=DT, dxes=base_dxes, epsilon=epsilon, dtype=complex) @@ -660,8 +660,8 @@ def _run_pulsed_straight_waveguide_case() -> PulsedWaveguideCalibrationResult: ) h_fdfd = functional.e2h(OMEGA, stretched_dxes)(e_fdfd) - overlap_td = complex(vec(e_ph) @ vec(overlap_e).conj()) - overlap_fd = complex(vec(e_fdfd) @ vec(overlap_e).conj()) + overlap_td = vec(e_ph) @ vec(overlap_e).conj() + overlap_fd = vec(e_fdfd) @ vec(overlap_e).conj() poynting_td = functional.poynting_e_cross_h(stretched_dxes)(e_ph, h_ph.conj()) poynting_fd = functional.poynting_e_cross_h(stretched_dxes)(e_fdfd, h_fdfd.conj()) diff --git a/meanas/test/test_waveguide_mode_helpers.py b/meanas/test/test_waveguide_mode_helpers.py index d3ec7cd..d5d3abf 100644 --- a/meanas/test/test_waveguide_mode_helpers.py +++ b/meanas/test/test_waveguide_mode_helpers.py @@ -16,7 +16,7 @@ def build_waveguide_3d_mode( *, slice_start: int, polarity: int, - ) -> tuple[numpy.ndarray, list[list[numpy.ndarray]], tuple[slice, slice, slice], waveguide_3d.Waveguide3DMode]: + ) -> tuple[numpy.ndarray, list[list[numpy.ndarray]], tuple[slice, slice, slice], dict[str, complex | numpy.ndarray]]: epsilon = numpy.ones((3, 5, 5, 1), dtype=float) dxes = [[numpy.ones(5), numpy.ones(5), numpy.ones(1)] for _ in range(2)] slices = (slice(slice_start, slice_start + 1), slice(None), slice(None)) @@ -100,6 +100,7 @@ def test_waveguide_3d_compute_overlap_e_uses_adjacent_window( axis=0, polarity=polarity, slices=slices, + omega=OMEGA, ) nonzero = numpy.argwhere(numpy.abs(overlap) > 0) @@ -129,6 +130,7 @@ def test_waveguide_3d_compute_overlap_e_warns_when_window_is_clipped( axis=0, polarity=polarity, slices=slices, + omega=OMEGA, ) nonzero = numpy.argwhere(numpy.abs(overlap) > 0) @@ -156,6 +158,7 @@ def test_waveguide_3d_compute_overlap_e_rejects_empty_overlap_window( axis=0, polarity=polarity, slices=slices, + omega=OMEGA, ) @@ -170,6 +173,7 @@ def test_waveguide_3d_compute_overlap_e_rejects_zero_support_window() -> None: axis=0, polarity=1, slices=slices, + omega=OMEGA, ) diff --git a/meanas/test/utils.py b/meanas/test/utils.py index 62afaf0..3bafd49 100644 --- a/meanas/test/utils.py +++ b/meanas/test/utils.py @@ -1,6 +1,5 @@ import numpy from numpy.typing import NDArray -from numpy.typing import ArrayLike def make_prng(seed: int = 12345) -> numpy.random.RandomState: @@ -25,9 +24,9 @@ def assert_fields_close( ) def assert_close( - x: ArrayLike, - y: ArrayLike, + x: NDArray, + y: NDArray, *args, **kwargs, ) -> None: - numpy.testing.assert_allclose(numpy.asarray(x), numpy.asarray(y), *args, **kwargs) + numpy.testing.assert_allclose(x, y, *args, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index 7f1d6b4..013631a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,9 +104,6 @@ lint.ignore = [ "TRY002", # Exception() ] -[tool.ruff.lint.per-file-ignores] -"meanas/test/**/*.py" = ["ANN", "ARG", "TC006"] - [[tool.mypy.overrides]] module = [ @@ -124,9 +121,6 @@ ignore_missing_imports = true [tool.pytest.ini_options] addopts = "-rsXx" testpaths = ["meanas"] -markers = [ - "complete: slower integration and smoke tests intended for full pre-commit confidence runs", -] [tool.coverage.run] source = ["meanas"] diff --git a/uv.lock b/uv.lock index 08bad0c..b696160 100644 --- a/uv.lock +++ b/uv.lock @@ -1,13 +1,5 @@ version = 1 requires-python = ">=3.11" -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] [[package]] name = "babel" @@ -738,8 +730,8 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "coverage" }, + { name = "gridlock" }, { name = "htmlark" }, - { name = "matplotlib" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocs-print-site-plugin" }, @@ -747,7 +739,6 @@ dev = [ { name = "pymdown-extensions" }, { name = "pytest" }, { name = "ruff" }, - { name = "scikit-rf" }, ] docs = [ { name = "htmlark" }, @@ -759,8 +750,8 @@ docs = [ { name = "ruff" }, ] examples = [ + { name = "gridlock" }, { name = "matplotlib" }, - { name = "scikit-rf" }, ] test = [ { name = "coverage" }, @@ -771,10 +762,11 @@ test = [ requires-dist = [ { name = "coverage", marker = "extra == 'dev'" }, { name = "coverage", marker = "extra == 'test'" }, - { name = "gridlock", specifier = ">=2.1" }, + { name = "gridlock" }, + { name = "gridlock", marker = "extra == 'dev'" }, + { name = "gridlock", marker = "extra == 'examples'", specifier = ">=2.1" }, { name = "htmlark", marker = "extra == 'dev'", specifier = ">=1.0" }, { name = "htmlark", marker = "extra == 'docs'", specifier = ">=1.0" }, - { name = "matplotlib", marker = "extra == 'dev'", specifier = ">=3.10.8" }, { name = "matplotlib", marker = "extra == 'examples'", specifier = ">=3.10.8" }, { name = "mkdocs", marker = "extra == 'dev'", specifier = ">=1.6" }, { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.6" }, @@ -791,8 +783,6 @@ requires-dist = [ { name = "pytest", marker = "extra == 'test'" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" }, { name = "ruff", marker = "extra == 'docs'", specifier = ">=0.6" }, - { name = "scikit-rf", marker = "extra == 'dev'", specifier = ">=1.0" }, - { name = "scikit-rf", marker = "extra == 'examples'", specifier = ">=1.0" }, { name = "scipy", specifier = "~=1.14" }, ] @@ -1035,66 +1025,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, ] -[[package]] -name = "pandas" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/35/6411db530c618e0e0005187e35aa02ce60ae4c4c4d206964a2f978217c27/pandas-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0", size = 10326926 }, - { url = "https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c", size = 9926987 }, - { url = "https://files.pythonhosted.org/packages/52/77/9b1c2d6070b5dbe239a7bc889e21bfa58720793fb902d1e070695d87c6d0/pandas-3.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb", size = 10757067 }, - { url = "https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76", size = 11258787 }, - { url = "https://files.pythonhosted.org/packages/90/e3/3f1126d43d3702ca8773871a81c9f15122a1f412342cc56284ffda5b1f70/pandas-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e", size = 11771616 }, - { url = "https://files.pythonhosted.org/packages/2e/cf/0f4e268e1f5062e44a6bda9f925806721cd4c95c2b808a4c82ebe914f96b/pandas-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa", size = 12337623 }, - { url = "https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df", size = 9897372 }, - { url = "https://files.pythonhosted.org/packages/8f/eb/781516b808a99ddf288143cec46b342b3016c3414d137da1fdc3290d8860/pandas-3.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f", size = 9154922 }, - { url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921 }, - { url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127 }, - { url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577 }, - { url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030 }, - { url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468 }, - { url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381 }, - { url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993 }, - { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118 }, - { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105 }, - { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088 }, - { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066 }, - { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780 }, - { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181 }, - { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899 }, - { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574 }, - { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156 }, - { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238 }, - { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520 }, - { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154 }, - { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449 }, - { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475 }, - { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568 }, - { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652 }, - { url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084 }, - { url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146 }, - { url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081 }, - { url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535 }, - { url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992 }, - { url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257 }, - { url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893 }, - { url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644 }, - { url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246 }, - { url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801 }, - { url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643 }, - { url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641 }, - { url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993 }, - { url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274 }, - { url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530 }, - { url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341 }, -] - [[package]] name = "pathspec" version = "1.0.4" @@ -1375,21 +1305,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614 }, ] -[[package]] -name = "scikit-rf" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pandas" }, - { name = "scipy" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/89/bb/36d5d359137435e1776c44aaf5861aa84727ad728ac979ec76a52e3e5b28/scikit_rf-1.11.0.tar.gz", hash = "sha256:ac6c532e327da473abb15864105337424061a9d36429808362de0247eb2906d1", size = 577744 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/53/301380bcb71e136d944363f9172491730ad3f03d0cef598d57a65db38d84/scikit_rf-1.11.0-py3-none-any.whl", hash = "sha256:a8e7c8e3b89630685b1e1ab4c48fe19a6f830bbf31c26cd6f438e90902c2b9c5", size = 627060 }, -] - [[package]] name = "scipy" version = "1.16.3" @@ -1488,15 +1403,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] -[[package]] -name = "tzdata" -version = "2026.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952 }, -] - [[package]] name = "urllib3" version = "2.6.3"