from typing import Iterable import dataclasses import pytest # type: ignore import numpy from numpy.typing import NDArray #from numpy.testing import assert_allclose, assert_array_equal from .. import fdfd from ..fdmath import vec, unvec from .utils import assert_close # , assert_fields_close from .conftest import FixtureRequest def test_residual(sim: 'FDResult') -> None: A = fdfd.operators.e_full(sim.omega, sim.dxes, vec(sim.epsilon)).tocsr() b = -1j * sim.omega * vec(sim.j) residual = A @ vec(sim.e) - b assert numpy.linalg.norm(residual) < 1e-10 def test_poynting_planes(sim: 'FDResult') -> None: mask = (sim.j != 0).any(axis=0) if mask.sum() != 2: pytest.skip(f'test_poynting_planes will only test 2-point sources, got {mask.sum()}') # for dxg in sim.dxes: # for dxa in dxg: # if not (dxa == sim.dxes[0][0][0]).all(): # pytest.skip('test_poynting_planes skips nonuniform dxes') # pick only the second point points = numpy.where(mask) mask[points[0][0], points[1][0], points[2][0]] = 0 mx = numpy.roll(mask, -1, axis=0) my = numpy.roll(mask, -1, axis=1) mz = numpy.roll(mask, -1, axis=2) e2h = fdfd.operators.e2h(omega=sim.omega, dxes=sim.dxes, pmc=sim.pmc) ev = vec(sim.e) hv = e2h @ ev exh = fdfd.operators.poynting_e_cross(e=ev, dxes=sim.dxes) @ hv.conj() s = unvec(exh.real / 2, sim.shape[1:]) planes = [s[0, mask].sum(), -s[0, mx].sum(), s[1, mask].sum(), -s[1, my].sum(), s[2, mask].sum(), -s[2, mz].sum()] e_dot_j = sim.e * sim.j.conj() dv = (sim.dxes[0][0][:, None, None] * sim.dxes[0][1][None, :, None] * sim.dxes[0][2][None, None, :] ) src_energy = -(e_dot_j.real * dv)[:, mask] / 2 assert_close(sum(planes), src_energy.sum(), rtol=1e-6) # TODO improve energy calculation accuracy? ##################################### # Test fixtures ##################################### # Also see conftest.py @pytest.fixture(params=[1 / 1500]) def omega(request: FixtureRequest) -> Iterable[float]: yield request.param @pytest.fixture(params=[None]) def pec(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]: yield request.param @pytest.fixture(params=[None]) def pmc(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]: yield request.param #@pytest.fixture(scope='module', # params=[(25, 5, 5)]) #def shape(request): # yield (3, *request.param) @pytest.fixture(params=['diag']) # 'center' def j_distribution( request: FixtureRequest, shape: tuple[int, ...], j_mag: float, ) -> Iterable[NDArray[numpy.float64]]: j = numpy.zeros(shape, dtype=complex) center_mask = numpy.zeros(shape, dtype=bool) center_mask[:, shape[1] // 2, shape[2] // 2, shape[3] // 2] = True if request.param == 'center': j[center_mask] = j_mag elif request.param == 'diag': j[numpy.roll(center_mask, [1, 1, 1], axis=(1, 2, 3))] = (1 + 1j) * j_mag j[numpy.roll(center_mask, [-1, -1, -1], axis=(1, 2, 3))] = (1 - 1j) * j_mag yield j @dataclasses.dataclass() class FDResult: shape: tuple[int, ...] dxes: list[list[NDArray[numpy.float64]]] epsilon: NDArray[numpy.float64] omega: complex j: NDArray[numpy.complex128] e: NDArray[numpy.complex128] pmc: NDArray[numpy.float64] | None pec: NDArray[numpy.float64] | None @pytest.fixture() def sim( request: FixtureRequest, shape: tuple[int, ...], epsilon: NDArray[numpy.float64], dxes: list[list[NDArray[numpy.float64]]], j_distribution: NDArray[numpy.complex128], omega: float, pec: NDArray[numpy.float64] | None, pmc: NDArray[numpy.float64] | None, ) -> FDResult: """ Build simulation from parts """ # is3d = (numpy.array(shape) == 1).sum() == 0 # if is3d: # pytest.skip('Skipping dt != 0.3 because test is 3D (for speed)') # # If no edge currents, add pmls # src_mask = j_distribution.any(axis=0) # th = 10 # #if src_mask.sum() - src_mask[th:-th, th:-th, th:-th].sum() == 0: # if src_mask.sum() - src_mask[th:-th, :, :].sum() == 0: # for axis in (0,): # for polarity in (-1, 1): # dxes = fdfd.scpml.stretch_with_scpml(dxes, axis=axis, polarity=polarity, j_vec = vec(j_distribution) eps_vec = vec(epsilon) e_vec = fdfd.solvers.generic( J=j_vec, omega=omega, dxes=dxes, epsilon=eps_vec, matrix_solver_opts={'atol': 1e-15, 'tol': 1e-11}, ) e = unvec(e_vec, shape[1:]) sim = FDResult( shape=shape, dxes=dxes, epsilon=epsilon, j=j_distribution, e=e, pec=pec, pmc=pmc, omega=omega, ) return sim