Compare commits
No commits in common. "master" and "v0.11" have entirely different histories.
36 changed files with 172 additions and 837 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ toolbox overview and API derivations.
|
|||
|
||||
import pathlib
|
||||
|
||||
__version__ = '0.12'
|
||||
__version__ = '0.11'
|
||||
__author__ = 'Jan Petykiewicz'
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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])))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import numpy
|
||||
|
||||
from ..fdmath import functional as fd_functional
|
||||
from ..fdtd import base
|
||||
from ._test_builders import real_ramp
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
104
uv.lock
generated
104
uv.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue