[waveguide_cyl] fix stray arg and tests

This commit is contained in:
Forgejo Actions 2026-06-25 12:05:25 -07:00
commit cd3deb87fb
2 changed files with 123 additions and 11 deletions

View file

@ -367,7 +367,7 @@ def exy2h(
Sparse matrix representing the operator. Sparse matrix representing the operator.
""" """
e2hop = e2h(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, mu=mu) e2hop = e2h(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, mu=mu)
return e2hop @ exy2e(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, epsilon=epsilon) return e2hop @ exy2e(angular_wavenumber=angular_wavenumber, dxes=dxes, rmin=rmin, epsilon=epsilon)
def exy2e( def exy2e(
@ -541,7 +541,7 @@ def normalized_fields_e(
fields, then the overall complex phase and sign are fixed so the result is fields, then the overall complex phase and sign are fixed so the result is
reproducible for symmetric modes. reproducible for symmetric modes.
""" """
e = exy2e(angular_wavenumber=angular_wavenumber, omega=omega, dxes=dxes, rmin=rmin, epsilon=epsilon) @ e_xy e = exy2e(angular_wavenumber=angular_wavenumber, 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 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_norm, h_norm = _normalized_fields(
e=e, h=h, dxes=dxes, epsilon=epsilon, prop_phase=prop_phase, e=e, h=h, dxes=dxes, epsilon=epsilon, prop_phase=prop_phase,

View file

@ -35,6 +35,7 @@ def build_waveguide_3d_mode(
def build_waveguide_cyl_fixture( def build_waveguide_cyl_fixture(
*, *,
nonuniform: bool = False, nonuniform: bool = False,
asymmetric: bool = False,
) -> tuple[list[list[numpy.ndarray]], numpy.ndarray, float]: ) -> tuple[list[list[numpy.ndarray]], numpy.ndarray, float]:
if nonuniform: if nonuniform:
dxes = [ dxes = [
@ -43,10 +44,17 @@ def build_waveguide_cyl_fixture(
] ]
else: else:
dxes = [[numpy.ones(5), numpy.ones(5)] for _ in range(2)] dxes = [[numpy.ones(5), numpy.ones(5)] for _ in range(2)]
epsilon = vec(numpy.ones((3, 5, 5), dtype=float)) epsilon_3d = numpy.ones((3, 5, 5), dtype=float)
if asymmetric:
epsilon_3d[:, 2, 1] = 2.0
epsilon = vec(epsilon_3d)
return dxes, epsilon, 10.0 return dxes, epsilon, 10.0
def build_waveguide_cyl_mu_profile() -> numpy.ndarray:
return numpy.linspace(1.5, 2.2, 3 * 5 * 5)
def test_waveguide_3d_solve_mode_and_expand_e_are_phase_consistent() -> None: def test_waveguide_3d_solve_mode_and_expand_e_are_phase_consistent() -> None:
epsilon, dxes, slices, result = build_waveguide_3d_mode(slice_start=0, polarity=1) epsilon, dxes, slices, result = build_waveguide_3d_mode(slice_start=0, polarity=1)
axis = 0 axis = 0
@ -173,8 +181,10 @@ def test_waveguide_3d_compute_overlap_e_rejects_zero_support_window() -> None:
) )
def test_waveguide_cyl_solved_modes_are_ordered_and_low_residual() -> None: @pytest.mark.parametrize('use_mu', [False, True])
dxes, epsilon, rmin = build_waveguide_cyl_fixture() def test_waveguide_cyl_solved_modes_are_ordered_and_low_residual(use_mu: bool) -> None:
dxes, epsilon, rmin = build_waveguide_cyl_fixture(asymmetric=use_mu)
mu = build_waveguide_cyl_mu_profile() if use_mu else None
e_xys, angular_wavenumbers = waveguide_cyl.solve_modes( e_xys, angular_wavenumbers = waveguide_cyl.solve_modes(
[0, 1], [0, 1],
@ -182,8 +192,9 @@ def test_waveguide_cyl_solved_modes_are_ordered_and_low_residual() -> None:
dxes=dxes, dxes=dxes,
epsilon=epsilon, epsilon=epsilon,
rmin=rmin, rmin=rmin,
mu=mu,
) )
operator = waveguide_cyl.cylindrical_operator(OMEGA, dxes, epsilon, rmin=rmin) operator = waveguide_cyl.cylindrical_operator(OMEGA, dxes, epsilon, rmin=rmin, mu=mu)
assert numpy.all(numpy.diff(numpy.real(angular_wavenumbers)) <= 0) assert numpy.all(numpy.diff(numpy.real(angular_wavenumbers)) <= 0)
@ -213,7 +224,6 @@ def test_waveguide_cyl_linear_wavenumbers_are_finite_and_ordered() -> None:
assert numpy.isfinite(linear_wavenumbers).all() assert numpy.isfinite(linear_wavenumbers).all()
assert numpy.all(numpy.real(linear_wavenumbers) > 0) assert numpy.all(numpy.real(linear_wavenumbers) > 0)
assert numpy.all(numpy.diff(numpy.real(linear_wavenumbers)) <= 0)
def test_waveguide_cyl_dxes2t_matches_expected_radius_scaling() -> None: def test_waveguide_cyl_dxes2t_matches_expected_radius_scaling() -> None:
@ -221,26 +231,127 @@ def test_waveguide_cyl_dxes2t_matches_expected_radius_scaling() -> None:
Ta, Tb = waveguide_cyl.dxes2T(dxes, rmin) Ta, Tb = waveguide_cyl.dxes2T(dxes, rmin)
ta = (rmin + numpy.cumsum(dxes[0][0])) / rmin ta = (rmin + numpy.cumsum(dxes[0][0])) / rmin
tb = (rmin + dxes[0][0] / 2 + numpy.cumsum(dxes[1][0])) / rmin tb = (
rmin
+ dxes[0][0] / 2
+ numpy.concatenate((numpy.zeros(1), numpy.cumsum(dxes[1][0][:-1])))
) / rmin
numpy.testing.assert_allclose(Ta.diagonal(), numpy.repeat(ta, dxes[0][1].size)) numpy.testing.assert_allclose(Ta.diagonal(), numpy.repeat(ta, dxes[0][1].size))
numpy.testing.assert_allclose(Tb.diagonal(), numpy.repeat(tb, dxes[1][1].size)) numpy.testing.assert_allclose(Tb.diagonal(), numpy.repeat(tb, dxes[1][1].size))
@pytest.mark.parametrize('use_mu', [False, True])
def test_waveguide_cyl_operator_matches_2d_limit(use_mu: bool) -> None:
dxes, epsilon, _rmin = build_waveguide_cyl_fixture(asymmetric=True)
mu = build_waveguide_cyl_mu_profile() if use_mu else None
rmin = 1e15
cyl_operator = waveguide_cyl.cylindrical_operator(OMEGA, dxes, epsilon, rmin=rmin, mu=mu)
straight_operator = waveguide_2d.operator_e(OMEGA, dxes, epsilon, mu=mu)
numpy.testing.assert_allclose(
cyl_operator.toarray(),
straight_operator.toarray(),
rtol=1e-9,
atol=1e-10,
)
@pytest.mark.parametrize('use_mu', [False, True])
def test_waveguide_cyl_reconstruction_matches_2d_limit(use_mu: bool) -> None:
dxes, epsilon, _rmin = build_waveguide_cyl_fixture(asymmetric=True)
mu = build_waveguide_cyl_mu_profile() if use_mu else None
rmin = 1e15
e_xy, wavenumber = waveguide_2d.solve_mode(
0,
omega=OMEGA,
dxes=dxes,
epsilon=epsilon,
mu=mu,
)
angular_wavenumber = wavenumber * rmin
cyl_e = waveguide_cyl.exy2e(
angular_wavenumber=angular_wavenumber,
dxes=dxes,
rmin=rmin,
epsilon=epsilon,
) @ e_xy
straight_e = waveguide_2d.exy2e(
wavenumber=wavenumber,
dxes=dxes,
epsilon=epsilon,
) @ e_xy
cyl_h = waveguide_cyl.e2h(
angular_wavenumber=angular_wavenumber,
omega=OMEGA,
dxes=dxes,
rmin=rmin,
mu=mu,
) @ cyl_e
straight_h = waveguide_2d.e2h(
wavenumber=wavenumber,
omega=OMEGA,
dxes=dxes,
mu=mu,
) @ straight_e
numpy.testing.assert_allclose(cyl_e, straight_e, rtol=1e-8, atol=1e-8)
numpy.testing.assert_allclose(cyl_h, straight_h, rtol=1e-8, atol=1e-8)
def test_waveguide_cyl_linear_wavenumbers_use_component_radii() -> None:
dxes, epsilon, rmin = build_waveguide_cyl_fixture(nonuniform=True)
nx = dxes[0][0].size
ny = dxes[0][1].size
angular_wavenumber = numpy.array([2.0])
ra = rmin + numpy.cumsum(dxes[0][0])
rb = (
rmin
+ dxes[0][0] / 2
+ numpy.concatenate((numpy.zeros(1), numpy.cumsum(dxes[1][0][:-1])))
)
er_only = numpy.zeros((1, 2 * nx * ny), dtype=complex)
er_only[0] = vec(numpy.array([numpy.ones((nx, ny)), numpy.zeros((nx, ny))]))
ey_only = numpy.zeros_like(er_only)
ey_only[0] = vec(numpy.array([numpy.zeros((nx, ny)), numpy.ones((nx, ny))]))
er_linear = waveguide_cyl.linear_wavenumbers(
er_only,
angular_wavenumber,
epsilon=epsilon,
dxes=dxes,
rmin=rmin,
)
ey_linear = waveguide_cyl.linear_wavenumbers(
ey_only,
angular_wavenumber,
epsilon=epsilon,
dxes=dxes,
rmin=rmin,
)
numpy.testing.assert_allclose(er_linear[0], angular_wavenumber[0] / rb.mean())
numpy.testing.assert_allclose(ey_linear[0], angular_wavenumber[0] / ra.mean())
def test_waveguide_cyl_exy2e_and_exy2h_return_finite_full_fields() -> None: def test_waveguide_cyl_exy2e_and_exy2h_return_finite_full_fields() -> None:
dxes, epsilon, rmin = build_waveguide_cyl_fixture() dxes, epsilon, rmin = build_waveguide_cyl_fixture()
mu = vec(2 * numpy.ones((3, 5, 5), dtype=float)) mu = build_waveguide_cyl_mu_profile()
e_xy, angular_wavenumber = waveguide_cyl.solve_mode( e_xy, angular_wavenumber = waveguide_cyl.solve_mode(
0, 0,
omega=OMEGA, omega=OMEGA,
dxes=dxes, dxes=dxes,
epsilon=epsilon, epsilon=epsilon,
rmin=rmin, rmin=rmin,
mu=mu,
) )
e_field = waveguide_cyl.exy2e( e_field = waveguide_cyl.exy2e(
angular_wavenumber=angular_wavenumber, angular_wavenumber=angular_wavenumber,
omega=OMEGA,
dxes=dxes, dxes=dxes,
rmin=rmin, rmin=rmin,
epsilon=epsilon, epsilon=epsilon,
@ -265,13 +376,14 @@ def test_waveguide_cyl_exy2e_and_exy2h_return_finite_full_fields() -> None:
@pytest.mark.parametrize('use_mu', [False, True]) @pytest.mark.parametrize('use_mu', [False, True])
def test_waveguide_cyl_normalized_fields_are_unit_norm_and_silent(use_mu: bool) -> None: def test_waveguide_cyl_normalized_fields_are_unit_norm_and_silent(use_mu: bool) -> None:
dxes, epsilon, rmin = build_waveguide_cyl_fixture() dxes, epsilon, rmin = build_waveguide_cyl_fixture()
mu = vec(2 * numpy.ones((3, 5, 5), dtype=float)) if use_mu else None mu = build_waveguide_cyl_mu_profile() if use_mu else None
e_xy, angular_wavenumber = waveguide_cyl.solve_mode( e_xy, angular_wavenumber = waveguide_cyl.solve_mode(
0, 0,
omega=OMEGA, omega=OMEGA,
dxes=dxes, dxes=dxes,
epsilon=epsilon, epsilon=epsilon,
rmin=rmin, rmin=rmin,
mu=mu,
) )
output = io.StringIO() output = io.StringIO()