various fixes and improvements

This commit is contained in:
Jan Petykiewicz 2019-08-05 00:20:06 -07:00
parent 94ff3f7853
commit 5951f2bdb1
8 changed files with 40 additions and 54 deletions

View File

@ -4,7 +4,7 @@ from numpy.linalg import norm
import meanas import meanas
from meanas import vec, unvec from meanas import vec, unvec
from meanas.fdfd import waveguide_mode, functional, scpml from meanas.fdfd import waveguide_mode, functional, scpml, operators
from meanas.fdfd.solvers import generic as generic_solver from meanas.fdfd.solvers import generic as generic_solver
import gridlock import gridlock
@ -56,18 +56,23 @@ def test0(solver=generic_solver):
dxes = [grid.dxyz, grid.autoshifted_dxyz()] dxes = [grid.dxyz, grid.autoshifted_dxyz()]
for a in (0, 1, 2): for a in (0, 1, 2):
for p in (-1, 1): for p in (-1, 1):
dxes = meanas.scpml.stretch_with_scpml(dxes, axis=a, polarity=p, omega=omega, dxes = meanas.fdfd.scpml.stretch_with_scpml(dxes, axis=a, polarity=p, omega=omega,
thickness=pml_thickness) thickness=pml_thickness)
J = [numpy.zeros_like(grid.grids[0], dtype=complex) for _ in range(3)] J = [numpy.zeros_like(grid.grids[0], dtype=complex) for _ in range(3)]
J[1][15, grid.shape[1]//2, grid.shape[2]//2] = 1e5 J[1][15, grid.shape[1]//2, grid.shape[2]//2] = 1
''' '''
Solve! Solve!
''' '''
sim_args = {
'omega': omega,
'dxes': dxes,
'epsilon': vec(grid.grids),
}
x = solver(J=vec(J), **sim_args) x = solver(J=vec(J), **sim_args)
A = functional.e_full(omega, dxes, vec(grid.grids)).tocsr() A = operators.e_full(omega, dxes, vec(grid.grids)).tocsr()
b = -1j * omega * vec(J) b = -1j * omega * vec(J)
print('Norm of the residual is ', norm(A @ x - b)) print('Norm of the residual is ', norm(A @ x - b))
@ -208,7 +213,7 @@ def module_available(name):
if __name__ == '__main__': if __name__ == '__main__':
# test0() test0()
if module_available('opencl_fdfd'): if module_available('opencl_fdfd'):
from opencl_fdfd import cg_solver as opencl_solver from opencl_fdfd import cg_solver as opencl_solver

View File

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

View File

@ -8,7 +8,7 @@ e.g. E = [E_x, E_y, E_z] where each component has shape (X, Y, Z)
from typing import List, Callable from typing import List, Callable
import numpy import numpy
from . import dx_lists_t, field_t from .. import dx_lists_t, field_t
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'

View File

@ -32,7 +32,7 @@ from typing import List, Tuple
import numpy import numpy
import scipy.sparse as sparse import scipy.sparse as sparse
from . import vec, dx_lists_t, vfield_t from .. import vec, dx_lists_t, vfield_t
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'

View File

@ -5,6 +5,8 @@ Functions for creating stretched coordinate PMLs.
from typing import List, Callable from typing import List, Callable
import numpy import numpy
from .. import dx_lists_t
__author__ = 'Jan Petykiewicz' __author__ = 'Jan Petykiewicz'

View File

@ -23,7 +23,7 @@ import numpy
from numpy.linalg import norm from numpy.linalg import norm
import scipy.sparse as sparse import scipy.sparse as sparse
from . import vec, unvec, dx_lists_t, field_t, vfield_t from .. import vec, unvec, dx_lists_t, field_t, vfield_t
from . import operators from . import operators
@ -82,7 +82,8 @@ def normalized_fields(v: numpy.ndarray,
omega: complex, omega: complex,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfield_t, epsilon: vfield_t,
mu: vfield_t = None mu: vfield_t = None,
dx_prop: float = 0,
) -> Tuple[vfield_t, vfield_t]: ) -> Tuple[vfield_t, vfield_t]:
""" """
Given a vector v containing the vectorized H_x and H_y fields, Given a vector v containing the vectorized H_x and H_y fields,
@ -94,6 +95,7 @@ def normalized_fields(v: numpy.ndarray,
:param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D) :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types (2D)
:param epsilon: Vectorized dielectric constant grid :param epsilon: Vectorized dielectric constant grid
:param mu: Vectorized magnetic permeability grid (default 1 everywhere) :param mu: Vectorized magnetic permeability grid (default 1 everywhere)
:param dxes_prop: Grid cell width in the propagation direction. Default 0 (continuous).
:return: Normalized, vectorized (e, h) containing all vector components. :return: Normalized, vectorized (e, h) containing all vector components.
""" """
e = v2e(v, wavenumber, omega, dxes, epsilon, mu=mu) e = v2e(v, wavenumber, omega, dxes, epsilon, mu=mu)
@ -105,11 +107,10 @@ def normalized_fields(v: numpy.ndarray,
E = unvec(e, shape) E = unvec(e, shape)
H = unvec(h, shape) H = unvec(h, shape)
S1 = E[0] * numpy.roll(numpy.conj(H[1]), 1, axis=0) * dxes_real[0][1] * dxes_real[1][0] phase = numpy.exp(-1j * wavenumber * dx_prop / 2)
S2 = E[1] * numpy.roll(numpy.conj(H[0]), 1, axis=1) * dxes_real[0][0] * dxes_real[1][1] S1 = E[0] * numpy.conj(H[1] * phase) * dxes_real[0][1] * dxes_real[1][0]
S = 0.25 * ((S1 + numpy.roll(S1, 1, axis=0)) - S2 = E[1] * numpy.conj(H[0] * phase) * dxes_real[0][0] * dxes_real[1][1]
(S2 + numpy.roll(S2, 1, axis=1))) P = numpy.real(S1.sum() - S2.sum())
P = 0.5 * numpy.real(S.sum())
assert P > 0, 'Found a mode propagating in the wrong direction! P={}'.format(P) assert P > 0, 'Found a mode propagating in the wrong direction! P={}'.format(P)
energy = epsilon * e.conj() * e energy = epsilon * e.conj() * e
@ -120,8 +121,6 @@ def normalized_fields(v: numpy.ndarray,
# Try to break symmetry to assign a consistent sign [experimental] # Try to break symmetry to assign a consistent sign [experimental]
E_weighted = unvec(e * energy * numpy.exp(1j * norm_angle), shape) E_weighted = unvec(e * energy * numpy.exp(1j * norm_angle), shape)
sign = numpy.sign(E_weighted[:, :max(shape[0]//2, 1), :max(shape[1]//2, 1)].real.sum()) sign = numpy.sign(E_weighted[:, :max(shape[0]//2, 1), :max(shape[1]//2, 1)].real.sum())
logger.debug('norm_angle = {}'.format(norm_angle))
logger.debug('norm_sign = {}'.format(sign)
norm_factor = sign * norm_amplitude * numpy.exp(1j * norm_angle) norm_factor = sign * norm_amplitude * numpy.exp(1j * norm_angle)

View File

@ -2,9 +2,9 @@ from typing import Dict, List
import numpy import numpy
import scipy.sparse as sparse import scipy.sparse as sparse
from . import vec, unvec, dx_lists_t, vfield_t, field_t from .. import vec, unvec, dx_lists_t, vfield_t, field_t
from . import operators, waveguide, functional from . import operators, waveguide, functional
from .eigensolvers import signed_eigensolve, rayleigh_quotient_iteration from ..eigensolvers import signed_eigensolve, rayleigh_quotient_iteration
def solve_waveguide_mode_2d(mode_number: int, def solve_waveguide_mode_2d(mode_number: int,
@ -12,7 +12,7 @@ def solve_waveguide_mode_2d(mode_number: int,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfield_t, epsilon: vfield_t,
mu: vfield_t = None, mu: vfield_t = None,
wavenumber_correction: bool = True, dx_prop: float = 0,
) -> Dict[str, complex or field_t]: ) -> Dict[str, complex or field_t]:
""" """
Given a 2d region, attempts to solve for the eigenmode with the specified mode number. Given a 2d region, attempts to solve for the eigenmode with the specified mode number.
@ -22,8 +22,8 @@ def solve_waveguide_mode_2d(mode_number: int,
:param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types :param dxes: Grid parameters [dx_e, dx_h] as described in meanas.types
:param epsilon: Dielectric constant :param epsilon: Dielectric constant
:param mu: Magnetic permeability (default 1 everywhere) :param mu: Magnetic permeability (default 1 everywhere)
:param wavenumber_correction: Whether to correct the wavenumber to :param dx_prop: The cell width in the the propagation direction, used to apply a
account for numerical dispersion (default True) correction to the wavenumber. Default 0 (i.e. continuous propagation direction)
:return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex} :return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex}
""" """
@ -51,15 +51,9 @@ def solve_waveguide_mode_2d(mode_number: int,
''' '''
Perform correction on wavenumber to account for numerical dispersion. Perform correction on wavenumber to account for numerical dispersion.
See Numerical Dispersion in Taflove's FDTD book.
This correction term reduces the error in emitted power, but additional
error is introduced into the E_err and H_err terms. This effect becomes
more pronounced as the wavenumber increases.
''' '''
if wavenumber_correction: if dx_prop != 0:
dx_mean = (numpy.hstack(dxes[0]) + numpy.hstack(dxes[1])).mean() / 2 #TODO figure out what dx to use here wavenumber = 2 / dx_prop * numpy.sin(wavenumber * dx_prop / 2)
wavenumber -= 2 * numpy.sin(numpy.real(wavenumber * dx_mean / 2)) / dx_mean - numpy.real(wavenumber)
shape = [d.size for d in dxes[0]] shape = [d.size for d in dxes[0]]
fields = { fields = {
@ -79,7 +73,6 @@ def solve_waveguide_mode(mode_number: int,
slices: List[slice], slices: List[slice],
epsilon: field_t, epsilon: field_t,
mu: field_t = None, mu: field_t = None,
wavenumber_correction: bool = True
) -> Dict[str, complex or numpy.ndarray]: ) -> Dict[str, complex or numpy.ndarray]:
""" """
Given a 3D grid, selects a slice from the grid and attempts to Given a 3D grid, selects a slice from the grid and attempts to
@ -94,8 +87,6 @@ def solve_waveguide_mode(mode_number: int,
as the waveguide cross-section. slices[axis] should select only one as the waveguide cross-section. slices[axis] should select only one
:param epsilon: Dielectric constant :param epsilon: Dielectric constant
:param mu: Magnetic permeability (default 1 everywhere) :param mu: Magnetic permeability (default 1 everywhere)
:param wavenumber_correction: Whether to correct the wavenumber to
account for numerical dispersion (default True)
:return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex} :return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex}
""" """
if mu is None: if mu is None:
@ -115,7 +106,7 @@ def solve_waveguide_mode(mode_number: int,
'dxes': [[dx[i][slices[i]] for i in order[:2]] for dx in dxes], 'dxes': [[dx[i][slices[i]] for i in order[:2]] for dx in dxes],
'epsilon': vec([epsilon[i][slices].transpose(order) for i in order]), 'epsilon': vec([epsilon[i][slices].transpose(order) for i in order]),
'mu': vec([mu[i][slices].transpose(order) for i in order]), 'mu': vec([mu[i][slices].transpose(order) for i in order]),
'wavenumber_correction': wavenumber_correction, 'dx_prop': dxes[0][order[2]][slices[order[2]]],
} }
fields_2d = solve_waveguide_mode_2d(mode_number, omega=omega, **args_2d) fields_2d = solve_waveguide_mode_2d(mode_number, omega=omega, **args_2d)
@ -175,9 +166,6 @@ def compute_source(E: field_t,
:param mu: Magnetic permeability (default 1 everywhere) :param mu: Magnetic permeability (default 1 everywhere)
:return: J distribution for the unidirectional source :return: J distribution for the unidirectional source
""" """
if mu is None:
mu = numpy.ones(3)
J = numpy.zeros_like(E, dtype=complex) J = numpy.zeros_like(E, dtype=complex)
M = numpy.zeros_like(E, dtype=complex) M = numpy.zeros_like(E, dtype=complex)
@ -275,9 +263,9 @@ def solve_waveguide_mode_cylindrical(mode_number: int,
dxes: dx_lists_t, dxes: dx_lists_t,
epsilon: vfield_t, epsilon: vfield_t,
r0: float, r0: float,
wavenumber_correction: bool = True,
) -> Dict[str, complex or field_t]: ) -> Dict[str, complex or field_t]:
""" """
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
of the bent waveguide with the specified mode number. of the bent waveguide with the specified mode number.
@ -288,8 +276,6 @@ def solve_waveguide_mode_cylindrical(mode_number: int,
:param epsilon: Dielectric constant :param epsilon: Dielectric constant
:param r0: Radius of curvature for the simulation. This should be the minimum value of :param r0: Radius of curvature for the simulation. This should be the minimum value of
r within the simulation domain. r within the simulation domain.
:param wavenumber_correction: Whether to correct the wavenumber to
account for numerical dispersion (default True)
:return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex} :return: {'E': List[numpy.ndarray], 'H': List[numpy.ndarray], 'wavenumber': complex}
""" """
@ -313,16 +299,7 @@ def solve_waveguide_mode_cylindrical(mode_number: int,
wavenumber = numpy.sqrt(eigval) wavenumber = numpy.sqrt(eigval)
wavenumber *= numpy.sign(numpy.real(wavenumber)) wavenumber *= numpy.sign(numpy.real(wavenumber))
''' # TODO: Perform correction on wavenumber to account for numerical dispersion.
Perform correction on wavenumber to account for numerical dispersion.
See Numerical Dispersion in Taflove's FDTD book.
This correction term reduces the error in emitted power, but additional
error is introduced into the E_err and H_err terms. This effect becomes
more pronounced as the wavenumber increases.
'''
if wavenumber_correction:
wavenumber -= 2 * numpy.sin(numpy.real(wavenumber / 2)) - numpy.real(wavenumber)
shape = [d.size for d in dxes[0]] shape = [d.size for d in dxes[0]]
v = numpy.hstack((v, numpy.zeros(shape[0] * shape[1]))) v = numpy.hstack((v, numpy.zeros(shape[0] * shape[1])))

View File

@ -9,6 +9,7 @@ from meanas import fdtd
prng = numpy.random.RandomState(12345) prng = numpy.random.RandomState(12345)
def assert_fields_close(a, b, *args, **kwargs): def assert_fields_close(a, b, *args, **kwargs):
numpy.testing.assert_allclose(a, b, verbose=False, err_msg='Fields did not match:\n{}\n{}'.format(numpy.rollaxis(a, -1), numpy.testing.assert_allclose(a, b, verbose=False, err_msg='Fields did not match:\n{}\n{}'.format(numpy.rollaxis(a, -1),
numpy.rollaxis(b, -1)), *args, **kwargs) numpy.rollaxis(b, -1)), *args, **kwargs)