move stuff under fdmath

This commit is contained in:
Jan Petykiewicz 2019-11-27 22:59:52 -08:00
commit a956323b94
23 changed files with 304 additions and 272 deletions

View file

@ -83,7 +83,7 @@ import scipy.optimize
from scipy.linalg import norm
import scipy.sparse.linalg as spalg
from .. import field_t
from ..fdmath import fdfield_t
logger = logging.getLogger(__name__)
@ -154,8 +154,8 @@ def generate_kmn(k0: numpy.ndarray,
def maxwell_operator(k0: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t,
mu: field_t = None
epsilon: fdfield_t,
mu: fdfield_t = None
) -> Callable[[numpy.ndarray], numpy.ndarray]:
"""
Generate the Maxwell operator
@ -227,8 +227,8 @@ def maxwell_operator(k0: numpy.ndarray,
def hmn_2_exyz(k0: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t,
) -> Callable[[numpy.ndarray], field_t]:
epsilon: fdfield_t,
) -> Callable[[numpy.ndarray], fdfield_t]:
"""
Generate an operator which converts a vectorized spatial-frequency-space
h_mn into an E-field distribution, i.e.
@ -249,7 +249,7 @@ def hmn_2_exyz(k0: numpy.ndarray,
k_mag, m, n = generate_kmn(k0, G_matrix, shape)
def operator(h: numpy.ndarray) -> field_t:
def operator(h: numpy.ndarray) -> fdfield_t:
hin_m, hin_n = [hi.reshape(shape) for hi in numpy.split(h, 2)]
d_xyz = (n * hin_m -
m * hin_n) * k_mag
@ -262,8 +262,8 @@ def hmn_2_exyz(k0: numpy.ndarray,
def hmn_2_hxyz(k0: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t
) -> Callable[[numpy.ndarray], field_t]:
epsilon: fdfield_t
) -> Callable[[numpy.ndarray], fdfield_t]:
"""
Generate an operator which converts a vectorized spatial-frequency-space
h_mn into an H-field distribution, i.e.
@ -293,8 +293,8 @@ def hmn_2_hxyz(k0: numpy.ndarray,
def inverse_maxwell_operator_approx(k0: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t,
mu: field_t = None
epsilon: fdfield_t,
mu: fdfield_t = None
) -> Callable[[numpy.ndarray], numpy.ndarray]:
"""
Generate an approximate inverse of the Maxwell operator,
@ -366,8 +366,8 @@ def find_k(frequency: float,
tolerance: float,
direction: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t,
mu: field_t = None,
epsilon: fdfield_t,
mu: fdfield_t = None,
band: int = 0,
k_min: float = 0,
k_max: float = 0.5,
@ -409,8 +409,8 @@ def find_k(frequency: float,
def eigsolve(num_modes: int,
k0: numpy.ndarray,
G_matrix: numpy.ndarray,
epsilon: field_t,
mu: field_t = None,
epsilon: fdfield_t,
mu: fdfield_t = None,
tolerance: float = 1e-20,
max_iters: int = 10000,
reset_iters: int = 100,

View file

@ -6,11 +6,11 @@ import numpy
from numpy.fft import fft2, fftshift, fftfreq, ifft2, ifftshift
from numpy import pi
from .. import field_t
from .. import fdfield_t
def near_to_farfield(E_near: field_t,
H_near: field_t,
def near_to_farfield(E_near: fdfield_t,
H_near: fdfield_t,
dx: float,
dy: float,
padded_size: List[int] = None
@ -117,8 +117,8 @@ def near_to_farfield(E_near: field_t,
def far_to_nearfield(E_far: field_t,
H_far: field_t,
def far_to_nearfield(E_far: fdfield_t,
H_far: fdfield_t,
dkx: float,
dky: float,
padded_size: List[int] = None

View file

@ -2,32 +2,30 @@
Functional versions of many FDFD operators. These can be useful for performing
FDFD calculations without needing to construct large matrices in memory.
The functions generated here expect `field_t` inputs with shape (3, X, Y, Z),
The functions generated here expect `fdfield_t` inputs with shape (3, X, Y, Z),
e.g. E = [E_x, E_y, E_z] where each component has shape (X, Y, Z)
"""
from typing import List, Callable, Tuple
import numpy
from .. import dx_lists_t, field_t
from ..fdmath import dx_lists_t, fdfield_t, fdfield_updater_t
from ..fdmath.functional import curl_forward, curl_back
__author__ = 'Jan Petykiewicz'
field_transform_t = Callable[[field_t], field_t]
def e_full(omega: complex,
dxes: dx_lists_t,
epsilon: field_t,
mu: field_t = None
) -> field_transform_t:
epsilon: fdfield_t,
mu: fdfield_t = None
) -> fdfield_updater_t:
"""
Wave operator for use with E-field. See `operators.e_full` for details.
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters [dx_e, dx_h] as described in meanas.types
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Dielectric constant
mu: Magnetic permeability (default 1 everywhere)
@ -54,16 +52,16 @@ def e_full(omega: complex,
def eh_full(omega: complex,
dxes: dx_lists_t,
epsilon: field_t,
mu: field_t = None
) -> Callable[[field_t, field_t], Tuple[field_t, field_t]]:
epsilon: fdfield_t,
mu: fdfield_t = None
) -> Callable[[fdfield_t, fdfield_t], Tuple[fdfield_t, fdfield_t]]:
"""
Wave operator for full (both E and H) field representation.
See `operators.eh_full`.
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters [dx_e, dx_h] as described in meanas.types
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Dielectric constant
mu: Magnetic permeability (default 1 everywhere)
@ -90,15 +88,15 @@ def eh_full(omega: complex,
def e2h(omega: complex,
dxes: dx_lists_t,
mu: field_t = None,
) -> field_transform_t:
mu: fdfield_t = None,
) -> fdfield_updater_t:
"""
Utility operator for converting the `E` field into the `H` field.
For use with `e_full` -- assumes that there is no magnetic current `M`.
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
mu: Magnetic permeability (default 1 everywhere)
Return:
@ -121,8 +119,8 @@ def e2h(omega: complex,
def m2j(omega: complex,
dxes: dx_lists_t,
mu: field_t = None,
) -> field_transform_t:
mu: fdfield_t = None,
) -> fdfield_updater_t:
"""
Utility operator for converting magnetic current `M` distribution
into equivalent electric current distribution `J`.
@ -130,7 +128,7 @@ def m2j(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
mu: Magnetic permeability (default 1 everywhere)
Returns:
@ -153,12 +151,12 @@ def m2j(omega: complex,
return m2j_mu
def e_tfsf_source(TF_region: field_t,
def e_tfsf_source(TF_region: fdfield_t,
omega: complex,
dxes: dx_lists_t,
epsilon: field_t,
mu: field_t = None,
) -> field_transform_t:
epsilon: fdfield_t,
mu: fdfield_t = None,
) -> fdfield_updater_t:
"""
Operator that turns an E-field distribution into a total-field/scattered-field
(TFSF) source.
@ -168,7 +166,7 @@ def e_tfsf_source(TF_region: field_t,
(i.e. in the scattered-field region).
Should have the same shape as the simulation grid, e.g. `epsilon[0].shape`.
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Dielectric constant distribution
mu: Magnetic permeability (default 1 everywhere)
@ -184,7 +182,7 @@ def e_tfsf_source(TF_region: field_t,
return neg_iwj / (-1j * omega)
def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[field_t, field_t], field_t]:
def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[fdfield_t, fdfield_t], fdfield_t]:
"""
Generates a function that takes the single-frequency `E` and `H` fields
and calculates the cross product `E` x `H` = \\( E \\times H \\) as required
@ -201,12 +199,12 @@ def poynting_e_cross_h(dxes: dx_lists_t) -> Callable[[field_t, field_t], field_t
instead.
Args:
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
Returns:
Function `f` that returns E x H as required for the poynting vector.
"""
def exh(e: field_t, h: field_t):
def exh(e: fdfield_t, h: fdfield_t):
s = numpy.empty_like(e)
ex = e[0] * dxes[0][0][:, None, None]
ey = e[1] * dxes[0][1][None, :, None]

View file

@ -9,7 +9,7 @@ E- and H-field values are defined on a Yee cell; `epsilon` values should be calc
cells centered at each E component (`mu` at each H component).
Many of these functions require a `dxes` parameter, of type `dx_lists_t`; see
the `meanas.types` submodule for details.
the `meanas.fdmath.types` submodule for details.
The following operators are included:
@ -31,7 +31,7 @@ from typing import List, Tuple
import numpy
import scipy.sparse as sparse
from .. import vec, dx_lists_t, vfield_t
from ..fdmath import vec, dx_lists_t, vfdfield_t
from ..fdmath.operators import shift_with_mirror, rotation, curl_forward, curl_back
@ -40,10 +40,10 @@ __author__ = 'Jan Petykiewicz'
def e_full(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
pec: vfield_t = None,
pmc: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
pec: vfdfield_t = None,
pmc: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Wave operator
@ -60,7 +60,7 @@ def e_full(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Vectorized dielectric constant
mu: Vectorized magnetic permeability (default 1 everywhere).
pec: Vectorized mask specifying PEC cells. Any cells where `pec != 0` are interpreted
@ -107,7 +107,7 @@ def e_full_preconditioners(dxes: dx_lists_t
The preconditioner matrices are diagonal and complex, with `Pr = 1 / Pl`
Args:
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
Returns:
Preconditioner matrices `(Pl, Pr)`.
@ -124,10 +124,10 @@ def e_full_preconditioners(dxes: dx_lists_t
def h_full(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
pec: vfield_t = None,
pmc: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
pec: vfdfield_t = None,
pmc: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Wave operator
@ -142,7 +142,7 @@ def h_full(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Vectorized dielectric constant
mu: Vectorized magnetic permeability (default 1 everywhere)
pec: Vectorized mask specifying PEC cells. Any cells where `pec != 0` are interpreted
@ -180,10 +180,10 @@ def h_full(omega: complex,
def eh_full(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
pec: vfield_t = None,
pmc: vfield_t = None
epsilon: vfdfield_t,
mu: vfdfield_t = None,
pec: vfdfield_t = None,
pmc: vfdfield_t = None
) -> sparse.spmatrix:
"""
Wave operator for `[E, H]` field representation. This operator implements Maxwell's
@ -210,7 +210,7 @@ def eh_full(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Vectorized dielectric constant
mu: Vectorized magnetic permeability (default 1 everywhere)
pec: Vectorized mask specifying PEC cells. Any cells where `pec != 0` are interpreted
@ -249,8 +249,8 @@ def eh_full(omega: complex,
def e2h(omega: complex,
dxes: dx_lists_t,
mu: vfield_t = None,
pmc: vfield_t = None,
mu: vfdfield_t = None,
pmc: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Utility operator for converting the E field into the H field.
@ -258,7 +258,7 @@ def e2h(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
mu: Vectorized magnetic permeability (default 1 everywhere)
pmc: Vectorized mask specifying PMC cells. Any cells where `pmc != 0` are interpreted
as containing a perfect magnetic conductor (PMC).
@ -280,7 +280,7 @@ def e2h(omega: complex,
def m2j(omega: complex,
dxes: dx_lists_t,
mu: vfield_t = None
mu: vfdfield_t = None
) -> sparse.spmatrix:
"""
Operator for converting a magnetic current M into an electric current J.
@ -288,7 +288,7 @@ def m2j(omega: complex,
Args:
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
mu: Vectorized magnetic permeability (default 1 everywhere)
Returns:
@ -302,14 +302,14 @@ def m2j(omega: complex,
return op
def poynting_e_cross(e: vfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
def poynting_e_cross(e: vfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
"""
Operator for computing the Poynting vector, containing the
(E x) portion of the Poynting vector.
Args:
e: Vectorized E-field for the ExH cross product
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
Returns:
Sparse matrix containing (E x) portion of Poynting cross product.
@ -331,13 +331,13 @@ def poynting_e_cross(e: vfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
return P
def poynting_h_cross(h: vfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
def poynting_h_cross(h: vfdfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
"""
Operator for computing the Poynting vector, containing the (H x) portion of the Poynting vector.
Args:
h: Vectorized H-field for the HxE cross product
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
Returns:
Sparse matrix containing (H x) portion of Poynting cross product.
@ -358,11 +358,11 @@ def poynting_h_cross(h: vfield_t, dxes: dx_lists_t) -> sparse.spmatrix:
return P
def e_tfsf_source(TF_region: vfield_t,
def e_tfsf_source(TF_region: vfdfield_t,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Operator that turns a desired E-field distribution into a
@ -374,7 +374,7 @@ def e_tfsf_source(TF_region: vfield_t,
TF_region: Mask, which is set to 1 inside the total-field region and 0 in the
scattered-field region
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Vectorized dielectric constant
mu: Vectorized magnetic permeability (default 1 everywhere).
@ -388,11 +388,11 @@ def e_tfsf_source(TF_region: vfield_t,
return (A @ Q - Q @ A) / (-1j * omega)
def e_boundary_source(mask: vfield_t,
def e_boundary_source(mask: vfdfield_t,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
periodic_mask_edges: bool = False,
) -> sparse.spmatrix:
"""
@ -405,7 +405,7 @@ def e_boundary_source(mask: vfield_t,
i.e. any points where shifting the mask by one cell in any direction
would change its value.
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Vectorized dielectric constant
mu: Vectorized magnetic permeability (default 1 everywhere).

View file

@ -5,14 +5,14 @@ Functions for creating stretched coordinate perfectly matched layer (PML) absorb
from typing import List, Callable
import numpy
from .. import dx_lists_t
from ..fdmath import dx_lists_t
__author__ = 'Jan Petykiewicz'
s_function_t = Callable[[float], float]
"""Typedef for s-functions"""
"""Typedef for s-functions, see `prepare_s_function()`"""
def prepare_s_function(ln_R: float = -16,
@ -63,7 +63,7 @@ def uniform_grid_scpml(shape: numpy.ndarray or List[int],
Default uses `prepare_s_function()` with no parameters.
Returns:
Complex cell widths (dx_lists_t) as discussed in `meanas.types`.
Complex cell widths (dx_lists_t) as discussed in `meanas.fdmath.types`.
"""
if s_function is None:
s_function = prepare_s_function()
@ -102,7 +102,7 @@ def stretch_with_scpml(dxes: dx_lists_t,
Stretch dxes to contain a stretched-coordinate PML (SCPML) in one direction along one axis.
Args:
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
axis: axis to stretch (0=x, 1=y, 2=z)
polarity: direction to stretch (-1 for -ve, +1 for +ve)
omega: Angular frequency for the simulation
@ -113,7 +113,7 @@ def stretch_with_scpml(dxes: dx_lists_t,
of pml parameters. Default uses `prepare_s_function()` with no parameters.
Returns:
Complex cell widths (dx_lists_t) as discussed in `meanas.types`.
Complex cell widths (dx_lists_t) as discussed in `meanas.fdmath.types`.
Multiple calls to this function may be necessary if multiple absorpbing boundaries are needed.
"""
if s_function is None:

View file

@ -9,6 +9,7 @@ import numpy
from numpy.linalg import norm
import scipy.sparse.linalg
from ..fdmath import dx_lists_t, vfdfield_t
from . import operators
@ -60,16 +61,16 @@ def _scipy_qmr(A: scipy.sparse.csr_matrix,
def generic(omega: complex,
dxes: List[List[numpy.ndarray]],
J: numpy.ndarray,
epsilon: numpy.ndarray,
mu: numpy.ndarray = None,
pec: numpy.ndarray = None,
pmc: numpy.ndarray = None,
dxes: dx_lists_t,
J: vfdfield_t,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
pec: vfdfield_t = None,
pmc: vfdfield_t = None,
adjoint: bool = False,
matrix_solver: Callable[..., numpy.ndarray] = _scipy_qmr,
matrix_solver_opts: Dict[str, Any] = None,
) -> numpy.ndarray:
) -> vfdfield_t:
"""
Conjugate gradient FDFD solver using CSR sparse matrices.
@ -78,7 +79,7 @@ def generic(omega: complex,
Args:
omega: Complex frequency to solve at.
dxes: `[[dx_e, dy_e, dz_e], [dx_h, dy_h, dz_h]]` (complex cell sizes) as
discussed in `meanas.types`
discussed in `meanas.fdmath.types`
J: Electric current distribution (at E-field locations)
epsilon: Dielectric constant distribution (at E-field locations)
mu: Magnetic permeability distribution (at H-field locations)

View file

@ -14,7 +14,8 @@ import numpy
from numpy.linalg import norm
import scipy.sparse as sparse
from .. import vec, unvec, dx_lists_t, field_t, vfield_t
from ..fdmath.operators import deriv_forward, deriv_back, curl_forward, curl_back, cross
from ..fdmath import vec, unvec, dx_lists_t, fdfield_t, vfdfield_t
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
from . import operators
@ -24,8 +25,8 @@ __author__ = 'Jan Petykiewicz'
def operator_e(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Waveguide operator of the form
@ -66,7 +67,7 @@ def operator_e(omega: complex,
Args:
omega: The angular frequency of the system.
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -76,8 +77,8 @@ def operator_e(omega: complex,
if numpy.any(numpy.equal(mu, None)):
mu = numpy.ones_like(epsilon)
Dfx, Dfy = operators.deriv_forward(dxes[0])
Dbx, Dby = operators.deriv_back(dxes[1])
Dfx, Dfy = deriv_forward(dxes[0])
Dbx, Dby = deriv_back(dxes[1])
eps_parts = numpy.split(epsilon, 3)
eps_xy = sparse.diags(numpy.hstack((eps_parts[0], eps_parts[1])))
@ -95,8 +96,8 @@ def operator_e(omega: complex,
def operator_h(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
) -> sparse.spmatrix:
"""
Waveguide operator of the form
@ -137,7 +138,7 @@ def operator_h(omega: complex,
Args:
omega: The angular frequency of the system.
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -169,10 +170,10 @@ def normalized_fields_e(e_xy: numpy.ndarray,
wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
prop_phase: float = 0,
) -> Tuple[vfield_t, vfield_t]:
) -> Tuple[vfdfield_t, vfdfield_t]:
"""
Given a vector `e_xy` containing the vectorized E_x and E_y fields,
returns normalized, vectorized E and H fields for the system.
@ -182,7 +183,7 @@ def normalized_fields_e(e_xy: numpy.ndarray,
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`.
It should satisfy `operator_e() @ e_xy == wavenumber**2 * e_xy`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
prop_phase: Phase shift `(dz * corrected_wavenumber)` over 1 cell in propagation direction.
@ -203,10 +204,10 @@ def normalized_fields_h(h_xy: numpy.ndarray,
wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
prop_phase: float = 0,
) -> Tuple[vfield_t, vfield_t]:
) -> Tuple[vfdfield_t, vfdfield_t]:
"""
Given a vector `h_xy` containing the vectorized H_x and H_y fields,
returns normalized, vectorized E and H fields for the system.
@ -216,7 +217,7 @@ def normalized_fields_h(h_xy: numpy.ndarray,
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`.
It should satisfy `operator_h() @ h_xy == wavenumber**2 * h_xy`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
prop_phase: Phase shift `(dz * corrected_wavenumber)` over 1 cell in propagation direction.
@ -237,10 +238,10 @@ def _normalized_fields(e: numpy.ndarray,
h: numpy.ndarray,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
prop_phase: float = 0,
) -> Tuple[vfield_t, vfield_t]:
) -> Tuple[vfdfield_t, vfdfield_t]:
# TODO documentation
shape = [s.size for s in dxes[0]]
dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
@ -276,8 +277,8 @@ def _normalized_fields(e: numpy.ndarray,
def exy2h(wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None
epsilon: vfdfield_t,
mu: vfdfield_t = None
) -> sparse.spmatrix:
"""
Operator which transforms the vector `e_xy` containing the vectorized E_x and E_y fields,
@ -287,7 +288,7 @@ def exy2h(wavenumber: complex,
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`.
It should satisfy `operator_e() @ e_xy == wavenumber**2 * e_xy`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -301,8 +302,8 @@ def exy2h(wavenumber: complex,
def hxy2e(wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None
epsilon: vfdfield_t,
mu: vfdfield_t = None
) -> sparse.spmatrix:
"""
Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields,
@ -312,7 +313,7 @@ def hxy2e(wavenumber: complex,
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`.
It should satisfy `operator_h() @ h_xy == wavenumber**2 * h_xy`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -325,7 +326,7 @@ def hxy2e(wavenumber: complex,
def hxy2h(wavenumber: complex,
dxes: dx_lists_t,
mu: vfield_t = None
mu: vfdfield_t = None
) -> sparse.spmatrix:
"""
Operator which transforms the vector `h_xy` containing the vectorized H_x and H_y fields,
@ -334,13 +335,13 @@ def hxy2h(wavenumber: complex,
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`.
It should satisfy `operator_h() @ h_xy == wavenumber**2 * h_xy`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
mu: Vectorized magnetic permeability grid (default 1 everywhere)
Returns:
Sparse matrix representing the operator.
"""
Dfx, Dfy = operators.deriv_forward(dxes[0])
Dfx, Dfy = deriv_forward(dxes[0])
hxy2hz = sparse.hstack((Dfx, Dfy)) / (1j * wavenumber)
if not numpy.any(numpy.equal(mu, None)):
@ -358,7 +359,7 @@ def hxy2h(wavenumber: complex,
def exy2e(wavenumber: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
epsilon: vfdfield_t,
) -> sparse.spmatrix:
"""
Operator which transforms the vector `e_xy` containing the vectorized E_x and E_y fields,
@ -367,13 +368,13 @@ def exy2e(wavenumber: complex,
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
It should satisfy `operator_e() @ e_xy == wavenumber**2 * e_xy`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
Returns:
Sparse matrix representing the operator.
"""
Dbx, Dby = operators.deriv_back(dxes[1])
Dbx, Dby = deriv_back(dxes[1])
exy2ez = sparse.hstack((Dbx, Dby)) / (1j * wavenumber)
if not numpy.any(numpy.equal(epsilon, None)):
@ -392,7 +393,7 @@ def exy2e(wavenumber: complex,
def e2h(wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
mu: vfield_t = None
mu: vfdfield_t = None
) -> sparse.spmatrix:
"""
Returns an operator which, when applied to a vectorized E eigenfield, produces
@ -401,7 +402,7 @@ def e2h(wavenumber: complex,
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
mu: Vectorized magnetic permeability grid (default 1 everywhere)
Returns:
@ -416,7 +417,7 @@ def e2h(wavenumber: complex,
def h2e(wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t
epsilon: vfdfield_t
) -> sparse.spmatrix:
"""
Returns an operator which, when applied to a vectorized H eigenfield, produces
@ -425,7 +426,7 @@ def h2e(wavenumber: complex,
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
Returns:
@ -441,7 +442,7 @@ def curl_e(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
Return:
Sparse matrix representation of the operator.
@ -450,9 +451,10 @@ def curl_e(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
for d in dxes[0]:
n *= len(d)
print(wavenumber, n)
Bz = -1j * wavenumber * sparse.eye(n)
Dfx, Dfy = operators.deriv_forward(dxes[0])
return operators.cross([Dfx, Dfy, Bz])
Dfx, Dfy = deriv_forward(dxes[0])
return cross([Dfx, Dfy, Bz])
def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
@ -461,7 +463,7 @@ def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
Args:
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
Return:
Sparse matrix representation of the operator.
@ -471,16 +473,16 @@ def curl_h(wavenumber: complex, dxes: dx_lists_t) -> sparse.spmatrix:
n *= len(d)
Bz = -1j * wavenumber * sparse.eye(n)
Dbx, Dby = operators.deriv_back(dxes[1])
return operators.cross([Dbx, Dby, Bz])
Dbx, Dby = deriv_back(dxes[1])
return cross([Dbx, Dby, Bz])
def h_err(h: vfield_t,
def h_err(h: vfdfield_t,
wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None
epsilon: vfdfield_t,
mu: vfdfield_t = None
) -> float:
"""
Calculates the relative error in the H field
@ -489,7 +491,7 @@ def h_err(h: vfield_t,
h: Vectorized H field
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -509,12 +511,12 @@ def h_err(h: vfield_t,
return norm(op) / norm(h)
def e_err(e: vfield_t,
def e_err(e: vfdfield_t,
wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None
epsilon: vfdfield_t,
mu: vfdfield_t = None
) -> float:
"""
Calculates the relative error in the E field
@ -523,7 +525,7 @@ def e_err(e: vfield_t,
e: Vectorized E field
wavenumber: Wavenumber assuming fields have z-dependence of `exp(-i * wavenumber * z)`
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
mu: Vectorized magnetic permeability grid (default 1 everywhere)
@ -545,17 +547,17 @@ def e_err(e: vfield_t,
def solve_modes(mode_numbers: List[int],
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
mu: vfield_t = None,
epsilon: vfdfield_t,
mu: vfdfield_t = None,
mode_margin: int = 2,
) -> Tuple[List[vfield_t], List[complex]]:
) -> Tuple[List[vfdfield_t], List[complex]]:
"""
Given a 2D region, attempts to solve for the eigenmode with the specified mode numbers.
Args:
mode_numbers: List of 0-indexed mode numbers to solve for
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
epsilon: Dielectric constant
mu: Magnetic permeability (default 1 everywhere)
mode_margin: The eigensolver will actually solve for `(max(mode_number) + mode_margin)`
@ -570,17 +572,18 @@ def solve_modes(mode_numbers: List[int],
Solve for the largest-magnitude eigenvalue of the real operator
'''
dxes_real = [[numpy.real(dx) for dx in dxi] for dxi in dxes]
A_r = waveguide.operator_e(numpy.real(omega), dxes_real, numpy.real(epsilon), numpy.real(mu))
A_r = operator_e(numpy.real(omega), dxes_real, numpy.real(epsilon), numpy.real(mu))
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_number) + mode_margin)
e_xys = eigvecs[:, -(numpy.array(mode_number) + 1)]
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_numbers) + mode_margin)
e_xys = eigvecs[:, -(numpy.array(mode_numbers) + 1)]
'''
Now solve for the eigenvector of the full operator, using the real operator's
eigenvector as an initial guess for Rayleigh quotient iteration.
'''
A = waveguide.operator_e(omega, dxes, epsilon, mu)
eigvals, e_xys = rayleigh_quotient_iteration(A, e_xys)
A = operator_e(omega, dxes, epsilon, mu)
for nn in range(len(mode_numbers)):
eigvals[nn], e_xys[:, nn] = rayleigh_quotient_iteration(A, e_xys[:, nn])
# Calculate the wave-vector (force the real part to be positive)
wavenumbers = numpy.sqrt(eigvals)
@ -592,7 +595,7 @@ def solve_modes(mode_numbers: List[int],
def solve_mode(mode_number: int,
*args,
**kwargs
) -> Tuple[vfield_t, complex]:
) -> Tuple[vfdfield_t, complex]:
"""
Wrapper around `solve_modes()` that solves for a single mode.
@ -604,4 +607,5 @@ def solve_mode(mode_number: int,
Returns:
(e_xy, wavenumber)
"""
return solve_modes(mode_numbers=[mode_number], *args, **kwargs)
e_xys, wavenumbers = solve_modes(mode_numbers=[mode_number], *args, **kwargs)
return e_xys[:, 0], wavenumbers[0]

View file

@ -8,7 +8,7 @@ from typing import Dict, List, Tuple
import numpy
import scipy.sparse as sparse
from .. import vec, unvec, dx_lists_t, vfield_t, field_t
from ..fdmath import vec, unvec, dx_lists_t, vfdfield_t, fdfield_t
from . import operators, waveguide_2d, functional
@ -18,8 +18,8 @@ def solve_mode(mode_number: int,
axis: int,
polarity: int,
slices: List[slice],
epsilon: field_t,
mu: field_t = None,
epsilon: fdfield_t,
mu: fdfield_t = None,
) -> Dict[str, complex or numpy.ndarray]:
"""
Given a 3D grid, selects a slice from the grid and attempts to
@ -28,7 +28,7 @@ def solve_mode(mode_number: int,
Args:
mode_number: Number of the mode, 0-indexed
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
axis: Propagation axis (0=x, 1=y, 2=z)
polarity: Propagation direction (+1 for +ve, -1 for -ve)
slices: `epsilon[tuple(slices)]` is used to select the portion of the grid to use
@ -71,7 +71,7 @@ def solve_mode(mode_number: int,
wavenumber = 2/dx_prop * numpy.arcsin(wavenumber_2d * dx_prop/2)
shape = [d.size for d in args_2d['dxes'][0]]
ve, vh = waveguide.normalized_fields_e(e_xy, wavenumber=wavenumber_2d, **args_2d, prop_phase=dx_prop * wavenumber)
ve, vh = waveguide_2d.normalized_fields_e(e_xy, wavenumber=wavenumber_2d, **args_2d, prop_phase=dx_prop * wavenumber)
e = unvec(ve, shape)
h = unvec(vh, shape)
@ -98,16 +98,16 @@ def solve_mode(mode_number: int,
return results
def compute_source(E: field_t,
def compute_source(E: fdfield_t,
wavenumber: complex,
omega: complex,
dxes: dx_lists_t,
axis: int,
polarity: int,
slices: List[slice],
epsilon: field_t,
mu: field_t = None,
) -> field_t:
epsilon: fdfield_t,
mu: fdfield_t = None,
) -> fdfield_t:
"""
Given an eigenmode obtained by `solve_mode`, returns the current source distribution
necessary to position a unidirectional source at the slice location.
@ -116,7 +116,7 @@ def compute_source(E: field_t,
E: E-field of the mode
wavenumber: Wavenumber of the mode
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
axis: Propagation axis (0=x, 1=y, 2=z)
polarity: Propagation direction (+1 for +ve, -1 for -ve)
slices: `epsilon[tuple(slices)]` is used to select the portion of the grid to use
@ -143,13 +143,13 @@ def compute_source(E: field_t,
return J
def compute_overlap_e(E: field_t,
def compute_overlap_e(E: fdfield_t,
wavenumber: complex,
dxes: dx_lists_t,
axis: int,
polarity: int,
slices: List[slice],
) -> field_t: # TODO DOCS
) -> fdfield_t: # TODO DOCS
"""
Given an eigenmode obtained by `solve_mode`, calculates an overlap_e for the
mode orthogonality relation Integrate(((E x H_mode) + (E_mode x H)) dot dn)
@ -160,7 +160,7 @@ def compute_overlap_e(E: field_t,
H: H-field of the mode (advanced by half of a Yee cell from E)
wavenumber: Wavenumber of the mode
omega: Angular frequency of the simulation
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
axis: Propagation axis (0=x, 1=y, 2=z)
polarity: Propagation direction (+1 for +ve, -1 for -ve)
slices: `epsilon[tuple(slices)]` is used to select the portion of the grid to use
@ -188,13 +188,13 @@ def compute_overlap_e(E: field_t,
return Etgt
def expand_e(E: field_t,
def expand_e(E: fdfield_t,
wavenumber: complex,
dxes: dx_lists_t,
axis: int,
polarity: int,
slices: List[slice],
) -> field_t:
) -> fdfield_t:
"""
Given an eigenmode obtained by `solve_mode`, expands the E-field from the 2D
slice where the mode was calculated to the entire domain (along the propagation
@ -205,7 +205,7 @@ def expand_e(E: field_t,
Args:
E: E-field of the mode
wavenumber: Wavenumber of the mode
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types`
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types`
axis: Propagation axis (0=x, 1=y, 2=z)
polarity: Propagation direction (+1 for +ve, -1 for -ve)
slices: `epsilon[tuple(slices)]` is used to select the portion of the grid to use

View file

@ -13,7 +13,7 @@ import numpy
from numpy.linalg import norm
import scipy.sparse as sparse
from .. import vec, unvec, dx_lists_t, field_t, vfield_t
from ..fdmath import vec, unvec, dx_lists_t, fdfield_t, vfdfield_t
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
from . import operators
@ -23,7 +23,7 @@ __author__ = 'Jan Petykiewicz'
def cylindrical_operator(omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
epsilon: vfdfield_t,
r0: float,
) -> sparse.spmatrix:
"""
@ -41,7 +41,7 @@ def cylindrical_operator(omega: complex,
Args:
omega: The angular frequency of the system
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.types` (2D)
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
epsilon: Vectorized dielectric constant grid
r0: Radius of curvature for the simulation. This should be the minimum value of
r within the simulation domain.
@ -83,9 +83,9 @@ def cylindrical_operator(omega: complex,
def solve_mode(mode_number: int,
omega: complex,
dxes: dx_lists_t,
epsilon: vfield_t,
epsilon: vfdfield_t,
r0: float,
) -> Dict[str, complex or field_t]:
) -> Dict[str, complex or fdfield_t]:
"""
TODO: fixup
Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode
@ -94,7 +94,7 @@ def solve_mode(mode_number: int,
Args:
mode_number: Number of the mode, 0-indexed
omega: Angular frequency of the simulation
dxes: Grid parameters [dx_e, dx_h] as described in meanas.types.
dxes: Grid parameters [dx_e, dx_h] as described in meanas.fdmath.types.
The first coordinate is assumed to be r, the second is y.
epsilon: Dielectric constant
r0: Radius of curvature for the simulation. This should be the minimum value of