You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
6.8 KiB
Python
230 lines
6.8 KiB
Python
"""
|
|
PML implementations
|
|
|
|
#TODO discussion of PMLs
|
|
#TODO cpml documentation
|
|
|
|
"""
|
|
# TODO retest pmls!
|
|
|
|
from typing import Callable, Sequence, Any
|
|
from copy import deepcopy
|
|
import numpy
|
|
from numpy.typing import NDArray, DTypeLike
|
|
|
|
from ..fdmath import fdfield_t, dx_lists_t
|
|
from ..fdmath.functional import deriv_forward, deriv_back
|
|
|
|
|
|
__author__ = 'Jan Petykiewicz'
|
|
|
|
|
|
def cpml_params(
|
|
axis: int,
|
|
polarity: int,
|
|
dt: float,
|
|
thickness: int = 8,
|
|
ln_R_per_layer: float = -1.6,
|
|
epsilon_eff: float = 1,
|
|
mu_eff: float = 1,
|
|
m: float = 3.5,
|
|
ma: float = 1,
|
|
cfs_alpha: float = 0,
|
|
) -> dict[str, Any]:
|
|
|
|
if axis not in range(3):
|
|
raise Exception('Invalid axis: {}'.format(axis))
|
|
|
|
if polarity not in (-1, 1):
|
|
raise Exception('Invalid polarity: {}'.format(polarity))
|
|
|
|
if thickness <= 2:
|
|
raise Exception('It would be wise to have a pml with 4+ cells of thickness')
|
|
|
|
if epsilon_eff <= 0:
|
|
raise Exception('epsilon_eff must be positive')
|
|
|
|
sigma_max = -ln_R_per_layer / 2 * (m + 1)
|
|
kappa_max = numpy.sqrt(epsilon_eff * mu_eff)
|
|
alpha_max = cfs_alpha
|
|
|
|
xe = numpy.arange(1, thickness + 1, dtype=float) # TODO: pass in dtype?
|
|
xh = numpy.arange(1, thickness + 1, dtype=float)
|
|
if polarity > 0:
|
|
xe -= 0.5
|
|
elif polarity < 0:
|
|
xh -= 0.5
|
|
xe = xe[::-1]
|
|
xh = xh[::-1]
|
|
else:
|
|
raise Exception('Bad polarity!')
|
|
|
|
expand_slice_l: list[Any] = [None, None, None]
|
|
expand_slice_l[axis] = slice(None)
|
|
expand_slice = tuple(expand_slice_l)
|
|
|
|
def par(x: NDArray[numpy.float64]) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64], NDArray[numpy.float64]]:
|
|
scaling = (x / thickness) ** m
|
|
sigma = scaling * sigma_max
|
|
kappa = 1 + scaling * (kappa_max - 1)
|
|
alpha = ((1 - x / thickness) ** ma) * alpha_max
|
|
p0 = numpy.exp(-(sigma / kappa + alpha) * dt)
|
|
p1 = sigma / (sigma + kappa * alpha) * (p0 - 1)
|
|
p2 = 1 / kappa
|
|
return p0[expand_slice], p1[expand_slice], p2[expand_slice]
|
|
|
|
p0e, p1e, p2e = par(xe)
|
|
p0h, p1h, p2h = par(xh)
|
|
|
|
region_list = [slice(None), slice(None), slice(None)]
|
|
if polarity < 0:
|
|
region_list[axis] = slice(None, thickness)
|
|
elif polarity > 0:
|
|
region_list[axis] = slice(-thickness, None)
|
|
else:
|
|
raise Exception('Bad polarity!')
|
|
region = tuple(region_list)
|
|
|
|
return {
|
|
'param_e': (p0e, p1e, p2e),
|
|
'param_h': (p0h, p1h, p2h),
|
|
'region': region,
|
|
}
|
|
|
|
|
|
def updates_with_cpml(
|
|
cpml_params: Sequence[Sequence[dict[str, Any] | None]],
|
|
dt: float,
|
|
dxes: dx_lists_t,
|
|
epsilon: fdfield_t,
|
|
*,
|
|
dtype: DTypeLike = numpy.float32,
|
|
) -> tuple[Callable[[fdfield_t, fdfield_t, fdfield_t], None],
|
|
Callable[[fdfield_t, fdfield_t, fdfield_t], None]]:
|
|
|
|
Dfx, Dfy, Dfz = deriv_forward(dxes[1])
|
|
Dbx, Dby, Dbz = deriv_back(dxes[1])
|
|
|
|
psi_E: list[list[tuple[Any, Any]]] = [[(None, None) for _ in range(2)] for _ in range(3)]
|
|
psi_H: list[list[tuple[Any, Any]]] = deepcopy(psi_E)
|
|
params_E: list[list[tuple[Any, Any, Any, Any]]] = [[(None, None, None, None) for _ in range(2)] for _ in range(3)]
|
|
params_H: list[list[tuple[Any, Any, Any, Any]]] = deepcopy(params_E)
|
|
|
|
for axis in range(3):
|
|
for pp, polarity in enumerate((-1, 1)):
|
|
cpml_param = cpml_params[axis][pp]
|
|
if cpml_param is None:
|
|
psi_E[axis][pp] = (None, None)
|
|
psi_H[axis][pp] = (None, None)
|
|
continue
|
|
|
|
region = cpml_param['region']
|
|
region_shape = epsilon[0][region].shape
|
|
|
|
psi_E[axis][pp] = (
|
|
numpy.zeros(region_shape, dtype=dtype),
|
|
numpy.zeros(region_shape, dtype=dtype),
|
|
)
|
|
psi_H[axis][pp] = (
|
|
numpy.zeros(region_shape, dtype=dtype),
|
|
numpy.zeros(region_shape, dtype=dtype),
|
|
)
|
|
params_E[axis][pp] = cpml_param['param_e'] + (region,)
|
|
params_H[axis][pp] = cpml_param['param_h'] + (region,)
|
|
|
|
pE = numpy.empty_like(epsilon, dtype=dtype)
|
|
pH = numpy.empty_like(epsilon, dtype=dtype)
|
|
|
|
def update_E(
|
|
e: fdfield_t,
|
|
h: fdfield_t,
|
|
epsilon: fdfield_t,
|
|
) -> None:
|
|
dyHx = Dby(h[0])
|
|
dzHx = Dbz(h[0])
|
|
dxHy = Dbx(h[1])
|
|
dzHy = Dbz(h[1])
|
|
dxHz = Dbx(h[2])
|
|
dyHz = Dby(h[2])
|
|
|
|
dH = ((dxHy, dxHz),
|
|
(dyHx, dyHz),
|
|
(dzHx, dzHy))
|
|
|
|
pE.fill(0)
|
|
|
|
for axis in range(3):
|
|
se = (-1, 1, -1)[axis]
|
|
transverse = numpy.delete(range(3), axis)
|
|
u, v = transverse
|
|
dHu, dHv = dH[axis]
|
|
|
|
for pp in range(2):
|
|
psi_Eu, psi_Ev = psi_E[axis][pp]
|
|
|
|
if psi_Eu is None:
|
|
# No pml in this direction
|
|
continue
|
|
|
|
p0e, p1e, p2e, region = params_E[axis][pp]
|
|
|
|
dHu[region] *= p2e
|
|
dHv[region] *= p2e
|
|
psi_Eu *= p0e
|
|
psi_Ev *= p0e
|
|
psi_Eu += p1e * dHv[region] # note reversed u,v mapping
|
|
psi_Ev += p1e * dHu[region]
|
|
pE[u][region] += +se * psi_Eu
|
|
pE[v][region] += -se * psi_Ev
|
|
|
|
e[0] += dt / epsilon[0] * (dyHz - dzHy + pE[0])
|
|
e[1] += dt / epsilon[1] * (dzHx - dxHz + pE[1])
|
|
e[2] += dt / epsilon[2] * (dxHy - dyHx + pE[2])
|
|
|
|
def update_H(
|
|
e: fdfield_t,
|
|
h: fdfield_t,
|
|
mu: fdfield_t = numpy.ones(3),
|
|
) -> None:
|
|
dyEx = Dfy(e[0])
|
|
dzEx = Dfz(e[0])
|
|
dxEy = Dfx(e[1])
|
|
dzEy = Dfz(e[1])
|
|
dxEz = Dfx(e[2])
|
|
dyEz = Dfy(e[2])
|
|
|
|
dE = ((dxEy, dxEz),
|
|
(dyEx, dyEz),
|
|
(dzEx, dzEy))
|
|
|
|
pH.fill(0)
|
|
|
|
for axis in range(3):
|
|
se = (-1, 1, -1)[axis]
|
|
transverse = numpy.delete(range(3), axis)
|
|
u, v = transverse
|
|
dEu, dEv = dE[axis]
|
|
|
|
for pp in range(2):
|
|
psi_Hu, psi_Hv = psi_H[axis][pp]
|
|
|
|
if psi_Hu is None:
|
|
# No pml here
|
|
continue
|
|
|
|
p0h, p1h, p2h, region = params_H[axis][pp]
|
|
|
|
dEu[region] *= p2h # modifies d_E_
|
|
dEv[region] *= p2h
|
|
psi_Hu *= p0h
|
|
psi_Hv *= p0h
|
|
psi_Hu += p1h * dEv[region] # note reversed u,v mapping
|
|
psi_Hv += p1h * dEu[region]
|
|
pH[u][region] += +se * psi_Hu
|
|
pH[v][region] += -se * psi_Hv
|
|
|
|
h[0] -= dt / mu[0] * (dyEz - dzEy + pH[0])
|
|
h[1] -= dt / mu[1] * (dzEx - dxEz + pH[1])
|
|
h[2] -= dt / mu[2] * (dxEy - dyEx + pH[2])
|
|
return update_E, update_H
|