Compare commits
12 Commits
e54735d9c6
...
71c2bbfada
Author | SHA1 | Date | |
---|---|---|---|
71c2bbfada | |||
6a56921c12 | |||
006833acf2 | |||
155f30068f | |||
7987dc796f | |||
829007c672 | |||
659566750f | |||
76701f593c | |||
4e3a163522 | |||
50f92e1cc8 | |||
b3c2fd391b | |||
c543868c0b |
@ -46,20 +46,24 @@ def test0(solver=generic_solver):
|
|||||||
# #### Create the grid, mask, and draw the device ####
|
# #### Create the grid, mask, and draw the device ####
|
||||||
grid = gridlock.Grid(edge_coords)
|
grid = gridlock.Grid(edge_coords)
|
||||||
epsilon = grid.allocate(n_air**2, dtype=numpy.float32)
|
epsilon = grid.allocate(n_air**2, dtype=numpy.float32)
|
||||||
grid.draw_cylinder(epsilon,
|
grid.draw_cylinder(
|
||||||
surface_normal=2,
|
epsilon,
|
||||||
center=center,
|
surface_normal=2,
|
||||||
radius=max(radii),
|
center=center,
|
||||||
thickness=th,
|
radius=max(radii),
|
||||||
eps=n_ring**2,
|
thickness=th,
|
||||||
num_points=24)
|
foreground=n_ring**2,
|
||||||
grid.draw_cylinder(epsilon,
|
num_points=24,
|
||||||
surface_normal=2,
|
)
|
||||||
center=center,
|
grid.draw_cylinder(
|
||||||
radius=min(radii),
|
epsilon,
|
||||||
thickness=th*1.1,
|
surface_normal=2,
|
||||||
eps=n_air ** 2,
|
center=center,
|
||||||
num_points=24)
|
radius=min(radii),
|
||||||
|
thickness=th*1.1,
|
||||||
|
foreground=n_air ** 2,
|
||||||
|
num_points=24,
|
||||||
|
)
|
||||||
|
|
||||||
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
||||||
for a in (0, 1, 2):
|
for a in (0, 1, 2):
|
||||||
@ -71,9 +75,9 @@ def test0(solver=generic_solver):
|
|||||||
J[1][15, grid.shape[1]//2, grid.shape[2]//2] = 1
|
J[1][15, grid.shape[1]//2, grid.shape[2]//2] = 1
|
||||||
|
|
||||||
|
|
||||||
'''
|
#
|
||||||
Solve!
|
# Solve!
|
||||||
'''
|
#
|
||||||
sim_args = {
|
sim_args = {
|
||||||
'omega': omega,
|
'omega': omega,
|
||||||
'dxes': dxes,
|
'dxes': dxes,
|
||||||
@ -87,9 +91,9 @@ def test0(solver=generic_solver):
|
|||||||
|
|
||||||
E = unvec(x, grid.shape)
|
E = unvec(x, grid.shape)
|
||||||
|
|
||||||
'''
|
#
|
||||||
Plot results
|
# Plot results
|
||||||
'''
|
#
|
||||||
pyplot.figure()
|
pyplot.figure()
|
||||||
pyplot.pcolor(numpy.real(E[1][:, :, grid.shape[2]//2]), cmap='seismic')
|
pyplot.pcolor(numpy.real(E[1][:, :, grid.shape[2]//2]), cmap='seismic')
|
||||||
pyplot.axis('equal')
|
pyplot.axis('equal')
|
||||||
@ -122,7 +126,7 @@ def test1(solver=generic_solver):
|
|||||||
# #### Create the grid and draw the device ####
|
# #### Create the grid and draw the device ####
|
||||||
grid = gridlock.Grid(edge_coords)
|
grid = gridlock.Grid(edge_coords)
|
||||||
epsilon = grid.allocate(n_air**2, dtype=numpy.float32)
|
epsilon = grid.allocate(n_air**2, dtype=numpy.float32)
|
||||||
grid.draw_cuboid(epsilon, center=center, dimensions=[8e3, w, th], eps=n_wg**2)
|
grid.draw_cuboid(epsilon, center=center, dimensions=[8e3, w, th], foreground=n_wg**2)
|
||||||
|
|
||||||
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
||||||
for a in (0, 1, 2):
|
for a in (0, 1, 2):
|
||||||
@ -169,9 +173,9 @@ def test1(solver=generic_solver):
|
|||||||
# pcolor((numpy.abs(J3).sum(axis=2).sum(axis=0) > 0).astype(float).T)
|
# pcolor((numpy.abs(J3).sum(axis=2).sum(axis=0) > 0).astype(float).T)
|
||||||
pyplot.show(block=True)
|
pyplot.show(block=True)
|
||||||
|
|
||||||
'''
|
#
|
||||||
Solve!
|
# Solve!
|
||||||
'''
|
#
|
||||||
sim_args = {
|
sim_args = {
|
||||||
'omega': omega,
|
'omega': omega,
|
||||||
'dxes': dxes,
|
'dxes': dxes,
|
||||||
@ -188,9 +192,9 @@ def test1(solver=generic_solver):
|
|||||||
|
|
||||||
E = unvec(x, grid.shape)
|
E = unvec(x, grid.shape)
|
||||||
|
|
||||||
'''
|
#
|
||||||
Plot results
|
# Plot results
|
||||||
'''
|
#
|
||||||
center = grid.pos2ind([0, 0, 0], None).astype(int)
|
center = grid.pos2ind([0, 0, 0], None).astype(int)
|
||||||
pyplot.figure()
|
pyplot.figure()
|
||||||
pyplot.subplot(2, 2, 1)
|
pyplot.subplot(2, 2, 1)
|
||||||
@ -232,7 +236,7 @@ def test1(solver=generic_solver):
|
|||||||
pyplot.grid(alpha=0.6)
|
pyplot.grid(alpha=0.6)
|
||||||
pyplot.title('Overlap with mode')
|
pyplot.title('Overlap with mode')
|
||||||
pyplot.show()
|
pyplot.show()
|
||||||
print('Average overlap with mode:', sum(q)/len(q))
|
print('Average overlap with mode:', sum(q[8:32])/len(q[8:32]))
|
||||||
|
|
||||||
|
|
||||||
def module_available(name):
|
def module_available(name):
|
||||||
|
@ -414,18 +414,13 @@ def _normalized_fields(
|
|||||||
shape = [s.size for s in dxes[0]]
|
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)]
|
dxes_real = [[numpy.real(d) for d in numpy.meshgrid(*dxes[v], indexing='ij')] for v in (0, 1)]
|
||||||
|
|
||||||
E = unvec(e, shape)
|
|
||||||
H = unvec(h, shape)
|
|
||||||
|
|
||||||
# Find time-averaged Sz and normalize to it
|
# Find time-averaged Sz and normalize to it
|
||||||
# H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
|
# H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
|
||||||
phase = numpy.exp(-1j * -prop_phase / 2)
|
phase = numpy.exp(-1j * -prop_phase / 2)
|
||||||
Sz_a = E[0] * numpy.conj(H[1] * phase) * dxes_real[0][1] * dxes_real[1][0]
|
Sz_tavg = inner_product(e, h, dxes=dxes, prop_phase=prop_phase, conj_h=True).real
|
||||||
Sz_b = E[1] * numpy.conj(H[0] * phase) * dxes_real[0][0] * dxes_real[1][1]
|
|
||||||
Sz_tavg = numpy.real(Sz_a.sum() - Sz_b.sum()) * 0.5 # 0.5 since E, H are assumed to be peak (not RMS) amplitudes
|
|
||||||
assert Sz_tavg > 0, f'Found a mode propagating in the wrong direction! {Sz_tavg=}'
|
assert Sz_tavg > 0, f'Found a mode propagating in the wrong direction! {Sz_tavg=}'
|
||||||
|
|
||||||
energy = epsilon * e.conj() * e
|
energy = numpy.real(epsilon * e.conj() * e)
|
||||||
|
|
||||||
norm_amplitude = 1 / numpy.sqrt(Sz_tavg)
|
norm_amplitude = 1 / numpy.sqrt(Sz_tavg)
|
||||||
norm_angle = -numpy.angle(e[energy.argmax()]) # Will randomly add a negative sign when mode is symmetric
|
norm_angle = -numpy.angle(e[energy.argmax()]) # Will randomly add a negative sign when mode is symmetric
|
||||||
@ -435,6 +430,7 @@ def _normalized_fields(
|
|||||||
sign = numpy.sign(E_weighted[:,
|
sign = numpy.sign(E_weighted[:,
|
||||||
:max(shape[0] // 2, 1),
|
:max(shape[0] // 2, 1),
|
||||||
:max(shape[1] // 2, 1)].real.sum())
|
:max(shape[1] // 2, 1)].real.sum())
|
||||||
|
assert sign != 0
|
||||||
|
|
||||||
norm_factor = sign * norm_amplitude * numpy.exp(1j * norm_angle)
|
norm_factor = sign * norm_amplitude * numpy.exp(1j * norm_angle)
|
||||||
|
|
||||||
@ -825,7 +821,7 @@ def sensitivity(
|
|||||||
|
|
||||||
|
|
||||||
def solve_modes(
|
def solve_modes(
|
||||||
mode_numbers: list[int],
|
mode_numbers: Sequence[int],
|
||||||
omega: complex,
|
omega: complex,
|
||||||
dxes: dx_lists_t,
|
dxes: dx_lists_t,
|
||||||
epsilon: vfdfield_t,
|
epsilon: vfdfield_t,
|
||||||
@ -858,7 +854,9 @@ def solve_modes(
|
|||||||
A_r = operator_e(numpy.real(omega), dxes_real, numpy.real(epsilon), mu_real)
|
A_r = operator_e(numpy.real(omega), dxes_real, numpy.real(epsilon), mu_real)
|
||||||
|
|
||||||
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_numbers) + mode_margin)
|
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_numbers) + mode_margin)
|
||||||
e_xys = eigvecs[:, -(numpy.array(mode_numbers) + 1)]
|
keep_inds = -(numpy.array(mode_numbers) + 1)
|
||||||
|
e_xys = eigvecs[:, keep_inds].T
|
||||||
|
eigvals = eigvals[keep_inds]
|
||||||
|
|
||||||
#
|
#
|
||||||
# Now solve for the eigenvector of the full operator, using the real operator's
|
# Now solve for the eigenvector of the full operator, using the real operator's
|
||||||
@ -866,13 +864,17 @@ def solve_modes(
|
|||||||
#
|
#
|
||||||
A = operator_e(omega, dxes, epsilon, mu)
|
A = operator_e(omega, dxes, epsilon, mu)
|
||||||
for nn in range(len(mode_numbers)):
|
for nn in range(len(mode_numbers)):
|
||||||
eigvals[nn], e_xys[:, nn] = rayleigh_quotient_iteration(A, e_xys[:, nn])
|
eigvals[nn], e_xys[nn, :] = rayleigh_quotient_iteration(A, e_xys[nn, :])
|
||||||
|
|
||||||
# Calculate the wave-vector (force the real part to be positive)
|
# Calculate the wave-vector (force the real part to be positive)
|
||||||
wavenumbers = numpy.sqrt(eigvals)
|
wavenumbers = numpy.sqrt(eigvals)
|
||||||
wavenumbers *= numpy.sign(numpy.real(wavenumbers))
|
wavenumbers *= numpy.sign(numpy.real(wavenumbers))
|
||||||
|
|
||||||
return e_xys.T, wavenumbers
|
order = wavenumbers.argsort()[::-1]
|
||||||
|
e_xys = e_xys[order]
|
||||||
|
wavenumbers = wavenumbers[order]
|
||||||
|
|
||||||
|
return e_xys, wavenumbers
|
||||||
|
|
||||||
|
|
||||||
def solve_mode(
|
def solve_mode(
|
||||||
@ -894,3 +896,37 @@ def solve_mode(
|
|||||||
kwargs['mode_numbers'] = [mode_number]
|
kwargs['mode_numbers'] = [mode_number]
|
||||||
e_xys, wavenumbers = solve_modes(*args, **kwargs)
|
e_xys, wavenumbers = solve_modes(*args, **kwargs)
|
||||||
return e_xys[0], wavenumbers[0]
|
return e_xys[0], wavenumbers[0]
|
||||||
|
|
||||||
|
|
||||||
|
def inner_product( # TODO documentation
|
||||||
|
e1: vcfdfield_t,
|
||||||
|
h2: vcfdfield_t,
|
||||||
|
dxes: dx_lists_t,
|
||||||
|
prop_phase: float = 0,
|
||||||
|
conj_h: bool = False,
|
||||||
|
trapezoid: bool = False,
|
||||||
|
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||||
|
|
||||||
|
shape = [s.size for s in dxes[0]]
|
||||||
|
|
||||||
|
# H phase is adjusted by a half-cell forward shift for Yee cell, and 1-cell reverse shift for Poynting
|
||||||
|
phase = numpy.exp(-1j * -prop_phase / 2)
|
||||||
|
|
||||||
|
E1 = unvec(e1, shape)
|
||||||
|
H2 = unvec(h2, shape) * phase
|
||||||
|
|
||||||
|
if conj_h:
|
||||||
|
H2 = numpy.conj(H2)
|
||||||
|
|
||||||
|
# Find time-averaged Sz and normalize to it
|
||||||
|
dxes_real = [[numpy.real(dxyz) for dxyz in dxeh] for dxeh in dxes]
|
||||||
|
if integrate:
|
||||||
|
Sz_a = numpy.trapezoid(numpy.trapezoid(E1[0] * H2[1], numpy.cumsum(dxes_real[0][1])), numpy.cumsum(dxes_real[1][0]))
|
||||||
|
Sz_b = numpy.trapezoid(numpy.trapezoid(E1[1] * H2[0], numpy.cumsum(dxes_real[0][0])), numpy.cumsum(dxes_real[1][1]))
|
||||||
|
else:
|
||||||
|
Sz_a = E1[0] * H2[1] * dxes_real[1][0][:, None] * dxes_real[0][1][None, :]
|
||||||
|
Sz_b = E1[1] * H2[0] * dxes_real[0][0][:, None] * dxes_real[1][1][None, :]
|
||||||
|
Sz = 0.5 * (Sz_a.sum() - Sz_b.sum())
|
||||||
|
return Sz
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ As the z-dependence is known, all the functions in this file assume a 2D grid
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
import logging
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.typing import NDArray, ArrayLike
|
from numpy.typing import NDArray, ArrayLike
|
||||||
@ -20,11 +21,13 @@ from ..fdmath.operators import deriv_forward, deriv_back
|
|||||||
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
|
from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def cylindrical_operator(
|
def cylindrical_operator(
|
||||||
omega: complex,
|
omega: complex,
|
||||||
dxes: dx_lists_t,
|
dxes: dx_lists_t,
|
||||||
epsilon: vfdfield_t,
|
epsilon: vfdfield_t,
|
||||||
r0: float,
|
|
||||||
rmin: float,
|
rmin: float,
|
||||||
) -> sparse.spmatrix:
|
) -> sparse.spmatrix:
|
||||||
"""
|
"""
|
||||||
@ -47,8 +50,7 @@ def cylindrical_operator(
|
|||||||
omega: The angular frequency of the system
|
omega: The angular frequency of the system
|
||||||
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
|
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
|
||||||
epsilon: Vectorized dielectric constant grid
|
epsilon: Vectorized dielectric constant grid
|
||||||
r0: Radius of curvature at x=0
|
rmin: Radius at the left edge of the simulation domain (minimum 'x')
|
||||||
rmin: Radius at the left edge of the simulation domain
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Sparse matrix representation of the operator
|
Sparse matrix representation of the operator
|
||||||
@ -57,13 +59,7 @@ def cylindrical_operator(
|
|||||||
Dfx, Dfy = deriv_forward(dxes[0])
|
Dfx, Dfy = deriv_forward(dxes[0])
|
||||||
Dbx, Dby = deriv_back(dxes[1])
|
Dbx, Dby = deriv_back(dxes[1])
|
||||||
|
|
||||||
ra = rmin + dxes[0][0] / 2.0 + numpy.cumsum(dxes[1][0]) # Radius at Ex points
|
Ta, Tb = dxes2T(dxes=dxes, rmin=rmin)
|
||||||
rb = rmin + numpy.cumsum(dxes[0][0]) # Radius at Ey points
|
|
||||||
ta = ra / r0
|
|
||||||
tb = rb / r0
|
|
||||||
|
|
||||||
Ta = sparse.diags(vec(ta[:, None].repeat(dxes[0][1].size, axis=1)))
|
|
||||||
Tb = sparse.diags(vec(tb[:, None].repeat(dxes[1][1].size, axis=1)))
|
|
||||||
|
|
||||||
eps_parts = numpy.split(epsilon, 3)
|
eps_parts = numpy.split(epsilon, 3)
|
||||||
eps_x = sparse.diags(eps_parts[0])
|
eps_x = sparse.diags(eps_parts[0])
|
||||||
@ -99,10 +95,9 @@ def solve_modes(
|
|||||||
omega: complex,
|
omega: complex,
|
||||||
dxes: dx_lists_t,
|
dxes: dx_lists_t,
|
||||||
epsilon: vfdfield_t,
|
epsilon: vfdfield_t,
|
||||||
r0: float,
|
|
||||||
rmin: float,
|
rmin: float,
|
||||||
mode_margin: int = 2,
|
mode_margin: int = 2,
|
||||||
) -> tuple[vcfdfield_t, NDArray[numpy.complex64]]:
|
) -> tuple[vcfdfield_t, NDArray[numpy.complex128]]:
|
||||||
"""
|
"""
|
||||||
TODO: fixup
|
TODO: fixup
|
||||||
Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode
|
Given a 2d (r, y) slice of epsilon, attempts to solve for the eigenmode
|
||||||
@ -114,12 +109,12 @@ def solve_modes(
|
|||||||
dxes: Grid parameters [dx_e, dx_h] as described in meanas.fdmath.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.
|
The first coordinate is assumed to be r, the second is y.
|
||||||
epsilon: Dielectric constant
|
epsilon: Dielectric constant
|
||||||
r0: Radius of curvature for the simulation. This should be the minimum value of
|
rmin: Radius of curvature for the simulation. This should be the minimum value of
|
||||||
r within the simulation domain.
|
r within the simulation domain.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
e_xys: NDArray of vfdfield_t specifying fields. First dimension is mode number.
|
e_xys: NDArray of vfdfield_t specifying fields. First dimension is mode number.
|
||||||
wavenumbers: list of wavenumbers
|
angular_wavenumbers: list of wavenumbers in 1/rad units.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -127,15 +122,17 @@ def solve_modes(
|
|||||||
#
|
#
|
||||||
dxes_real = [[numpy.real(dx) for dx in dxi] for dxi in dxes]
|
dxes_real = [[numpy.real(dx) for dx in dxi] for dxi in dxes]
|
||||||
|
|
||||||
A_r = cylindrical_operator(numpy.real(omega), dxes_real, numpy.real(epsilon), r0=r0, rmin=rmin)
|
A_r = cylindrical_operator(numpy.real(omega), dxes_real, numpy.real(epsilon), rmin=rmin)
|
||||||
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_numbers) + mode_margin)
|
eigvals, eigvecs = signed_eigensolve(A_r, max(mode_numbers) + mode_margin)
|
||||||
e_xys = eigvecs[:, -(numpy.array(mode_numbers) + 1)].T
|
keep_inds = -(numpy.array(mode_numbers) + 1)
|
||||||
|
e_xys = eigvecs[:, keep_inds].T
|
||||||
|
eigvals = eigvals[keep_inds]
|
||||||
|
|
||||||
#
|
#
|
||||||
# Now solve for the eigenvector of the full operator, using the real operator's
|
# Now solve for the eigenvector of the full operator, using the real operator's
|
||||||
# eigenvector as an initial guess for Rayleigh quotient iteration.
|
# eigenvector as an initial guess for Rayleigh quotient iteration.
|
||||||
#
|
#
|
||||||
A = cylindrical_operator(omega, dxes, epsilon, r0=r0, rmin=rmin)
|
A = cylindrical_operator(omega, dxes, epsilon, rmin=rmin)
|
||||||
for nn in range(len(mode_numbers)):
|
for nn in range(len(mode_numbers)):
|
||||||
eigvals[nn], e_xys[nn, :] = rayleigh_quotient_iteration(A, e_xys[nn, :])
|
eigvals[nn], e_xys[nn, :] = rayleigh_quotient_iteration(A, e_xys[nn, :])
|
||||||
|
|
||||||
@ -143,7 +140,15 @@ def solve_modes(
|
|||||||
wavenumbers = numpy.sqrt(eigvals)
|
wavenumbers = numpy.sqrt(eigvals)
|
||||||
wavenumbers *= numpy.sign(numpy.real(wavenumbers))
|
wavenumbers *= numpy.sign(numpy.real(wavenumbers))
|
||||||
|
|
||||||
return e_xys, wavenumbers
|
# Wavenumbers assume the mode is at rmin, which is unlikely
|
||||||
|
# Instead, return the wavenumber in inverse radians
|
||||||
|
angular_wavenumbers = wavenumbers * rmin
|
||||||
|
|
||||||
|
order = angular_wavenumbers.argsort()[::-1]
|
||||||
|
e_xys = e_xys[order]
|
||||||
|
angular_wavenumbers = angular_wavenumbers[order]
|
||||||
|
|
||||||
|
return e_xys, angular_wavenumbers
|
||||||
|
|
||||||
|
|
||||||
def solve_mode(
|
def solve_mode(
|
||||||
@ -160,8 +165,49 @@ def solve_mode(
|
|||||||
**kwargs: passed to `solve_modes()`
|
**kwargs: passed to `solve_modes()`
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(e_xy, wavenumber)
|
(e_xy, angular_wavenumber)
|
||||||
"""
|
"""
|
||||||
kwargs['mode_numbers'] = [mode_number]
|
kwargs['mode_numbers'] = [mode_number]
|
||||||
e_xys, wavenumbers = solve_modes(*args, **kwargs)
|
e_xys, wavenumbers = solve_modes(*args, **kwargs)
|
||||||
return e_xys[0], wavenumbers[0]
|
return e_xys[0], angular_wavenumbers[0]
|
||||||
|
|
||||||
|
|
||||||
|
def linear_wavenumbers(
|
||||||
|
e_xys: vcfdfield_t,
|
||||||
|
angular_wavenumbers: ArrayLike,
|
||||||
|
epsilon: vfdfield_t,
|
||||||
|
dxes: dx_lists_t,
|
||||||
|
rmin: float,
|
||||||
|
) -> NDArray[numpy.complex128]:
|
||||||
|
"""
|
||||||
|
Calculate linear wavenumbers (1/distance) based on angular wavenumbers (1/rad)
|
||||||
|
and the mode's energy distribution.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
e_xys: Vectorized mode fields with shape [num_modes, 2 * x *y)
|
||||||
|
angular_wavenumbers: Angular wavenumbers corresponding to the fields in `e_xys`
|
||||||
|
epsilon: Vectorized dielectric constant grid with shape (3, x, y)
|
||||||
|
dxes: Grid parameters `[dx_e, dx_h]` as described in `meanas.fdmath.types` (2D)
|
||||||
|
rmin: Radius at the left edge of the simulation domain (minimum 'x')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
NDArray containing the calculated linear (1/distance) wavenumbers
|
||||||
|
"""
|
||||||
|
angular_wavenumbers = numpy.asarray(angular_wavenumbers)
|
||||||
|
mode_radii = numpy.empty_like(angular_wavenumbers, dtype=float)
|
||||||
|
|
||||||
|
wavenumbers = numpy.empty_like(angular_wavenumbers)
|
||||||
|
shape2d = (len(dxes[0][0]), len(dxes[0][1]))
|
||||||
|
epsilon2d = unvec(epsilon, shape2d)[:2]
|
||||||
|
grid_radii = rmin + numpy.cumsum(dxes[0][0])
|
||||||
|
for ii in range(angular_wavenumbers.size):
|
||||||
|
efield = unvec(e_xys[ii], shape2d, 2)
|
||||||
|
energy = numpy.real((efield * efield.conj()) * epsilon2d)
|
||||||
|
energy_vs_x = energy.sum(axis=(0, 2))
|
||||||
|
mode_radii[ii] = (grid_radii * energy_vs_x).sum() / energy_vs_x.sum()
|
||||||
|
|
||||||
|
logger.info(f'{mode_radii=}')
|
||||||
|
lin_wavenumbers = angular_wavenumbers / mode_radii
|
||||||
|
return lin_wavenumbers
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,14 +28,16 @@ def vec(f: cfdfield_t) -> vcfdfield_t:
|
|||||||
def vec(f: ArrayLike) -> vfdfield_t | vcfdfield_t:
|
def vec(f: ArrayLike) -> vfdfield_t | vcfdfield_t:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def vec(f: fdfield_t | cfdfield_t | ArrayLike | None) -> vfdfield_t | vcfdfield_t | None:
|
def vec(
|
||||||
|
f: fdfield_t | cfdfield_t | ArrayLike | None,
|
||||||
|
) -> vfdfield_t | vcfdfield_t | None:
|
||||||
"""
|
"""
|
||||||
Create a 1D ndarray from a 3D vector field which spans a 1-3D region.
|
Create a 1D ndarray from a vector field which spans a 1-3D region.
|
||||||
|
|
||||||
Returns `None` if called with `f=None`.
|
Returns `None` if called with `f=None`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
f: A vector field, `[f_x, f_y, f_z]` where each `f_` component is a 1- to
|
f: A vector field, e.g. `[f_x, f_y, f_z]` where each `f_` component is a 1- to
|
||||||
3-D ndarray (`f_*` should all be the same size). Doesn't fail with `f=None`.
|
3-D ndarray (`f_*` should all be the same size). Doesn't fail with `f=None`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -47,33 +49,38 @@ def vec(f: fdfield_t | cfdfield_t | ArrayLike | None) -> vfdfield_t | vcfdfield_
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: None, shape: Sequence[int]) -> None:
|
def unvec(v: None, shape: Sequence[int], nvdim: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: vfdfield_t, shape: Sequence[int]) -> fdfield_t:
|
def unvec(v: vfdfield_t, shape: Sequence[int], nvdim: int) -> fdfield_t:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: vcfdfield_t, shape: Sequence[int]) -> cfdfield_t:
|
def unvec(v: vcfdfield_t, shape: Sequence[int], nvdim: int) -> cfdfield_t:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def unvec(v: vfdfield_t | vcfdfield_t | None, shape: Sequence[int]) -> fdfield_t | cfdfield_t | None:
|
def unvec(
|
||||||
|
v: vfdfield_t | vcfdfield_t | None,
|
||||||
|
shape: Sequence[int],
|
||||||
|
nvdim: int = 3,
|
||||||
|
) -> fdfield_t | cfdfield_t | None:
|
||||||
"""
|
"""
|
||||||
Perform the inverse of vec(): take a 1D ndarray and output a 3D field
|
Perform the inverse of vec(): take a 1D ndarray and output an `nvdim`-component field
|
||||||
of form `[f_x, f_y, f_z]` where each of `f_*` is a len(shape)-dimensional
|
of form e.g. `[f_x, f_y, f_z]` (`nvdim=3`) where each of `f_*` is a len(shape)-dimensional
|
||||||
ndarray.
|
ndarray.
|
||||||
|
|
||||||
Returns `None` if called with `v=None`.
|
Returns `None` if called with `v=None`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
v: 1D ndarray representing a 3D vector field of shape shape (or None)
|
v: 1D ndarray representing a vector field of shape shape (or None)
|
||||||
shape: shape of the vector field
|
shape: shape of the vector field
|
||||||
|
nvdim: Number of components in each vector
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`[f_x, f_y, f_z]` where each `f_` is a `len(shape)` dimensional ndarray (or `None`)
|
`[f_x, f_y, f_z]` where each `f_` is a `len(shape)` dimensional ndarray (or `None`)
|
||||||
"""
|
"""
|
||||||
if v is None:
|
if v is None:
|
||||||
return None
|
return None
|
||||||
return v.reshape((3, *shape), order='C')
|
return v.reshape((nvdim, *shape), order='C')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user