Skip to content

Commit 1f5ce51

Browse files
committed
Updated fully anisotropic mode solver
1 parent e5c8f08 commit 1f5ce51

File tree

2 files changed

+80
-47
lines changed

2 files changed

+80
-47
lines changed

tidy3d/components/mode/solver.py

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ def compute_modes(
274274
direction,
275275
enable_incidence_matrices,
276276
basis_E=basis_E,
277+
dls=dls,
278+
dmin_pmc=dmin_pmc,
277279
)
278280

279281
# Transform back to original axes, E = J^T E'
@@ -308,6 +310,8 @@ def solver_em(
308310
direction,
309311
enable_incidence_matrices,
310312
basis_E,
313+
dls,
314+
dmin_pmc=None,
311315
):
312316
"""Solve for the electromagnetic modes of a system defined by in-plane permittivity and
313317
permeability and assuming translational invariance in the normal direction.
@@ -323,7 +327,7 @@ def solver_em(
323327
mu_tensor : np.ndarray
324328
Shape (3, 3, N), the permittivity tensor at every point in the plane.
325329
der_mats : List[scipy.sparse.csr_matrix]
326-
The sparce derivative matrices dxf, dxb, dyf, dyb, including the PML.
330+
The sparse derivative matrices dxf, dxb, dyf, dyb, including the PML.
327331
num_modes : int
328332
Number of modes to solve for.
329333
neff_guess : float
@@ -334,6 +338,10 @@ def solver_em(
334338
Direction of mode propagation.
335339
basis_E: np.ndarray
336340
Basis for mode solving.
341+
dls: Tuple[List[np.ndarray], List[np.ndarray]]
342+
Primal and dual grid steps along each of the two tangential dimensions.
343+
dmin_pmc: List[bool]
344+
List of booleans indicating whether to apply PMC at the near end of the grid.
337345
338346
Returns
339347
-------
@@ -377,8 +385,8 @@ def conductivity_model_for_good_conductor(
377385
# initial vector for eigensolver in correct data type
378386
vec_init = cls.set_initial_vec(Nx, Ny, is_tensorial=is_tensorial)
379387

380-
# call solver
381-
kwargs = {
388+
# call solver: base kwargs shared by diagonal and tensorial solvers
389+
base_kwargs = {
382390
"eps": eps_tensor,
383391
"mu": mu_tensor,
384392
"der_mats": der_mats,
@@ -388,18 +396,19 @@ def conductivity_model_for_good_conductor(
388396
"mat_precision": mat_precision,
389397
}
390398

391-
is_eps_complex = cls.isinstance_complex(eps_tensor)
392-
393399
if basis_E is not None and is_tensorial:
394400
raise RuntimeError(
395401
"Tensorial eps not yet supported in relative mode solver "
396402
"(with basis fields provided)."
397403
)
398404

405+
# Determine if epsilon has complex values (used to select real vs complex tensorial solver)
406+
is_eps_complex = cls.isinstance_complex(eps_tensor)
407+
399408
if not is_tensorial:
400409
eps_spec = "diagonal"
401410
E, H, neff, keff = cls.solver_diagonal(
402-
**kwargs,
411+
**base_kwargs,
403412
enable_incidence_matrices=enable_incidence_matrices,
404413
basis_E=basis_E,
405414
)
@@ -410,51 +419,28 @@ def conductivity_model_for_good_conductor(
410419

411420
elif not is_eps_complex:
412421
eps_spec = "tensorial_real"
413-
E, H, neff, keff = cls.solver_tensorial(**kwargs, direction="+")
422+
E, H, neff, keff = cls.solver_tensorial(
423+
**base_kwargs,
424+
direction="+",
425+
dls=dls,
426+
Nxy=(Nx, Ny),
427+
dmin_pmc=dmin_pmc,
428+
)
414429
if direction == "-":
415430
E = np.conj(E)
416431
H = -np.conj(H)
417432

418433
else:
419434
eps_spec = "tensorial_complex"
420-
E, H, neff, keff = cls.solver_tensorial(**kwargs, direction=direction)
421-
422-
return E, H, neff, keff, eps_spec
423-
424-
@classmethod
425-
def matrix_data_type(cls, eps, mu, der_mats, mat_precision, is_tensorial):
426-
"""Determine data type that should be used for the matrix for diagonalization."""
427-
mat_dtype = np.float32
428-
# In tensorial case, even though the matrix can be real, the
429-
# expected eigenvalue is purely imaginary. So for now we enforce
430-
# the matrix to be complex type so that it will look for the right eigenvalues.
431-
if is_tensorial:
432-
mat_dtype = np.complex128 if mat_precision == "double" else np.complex64
433-
else:
434-
# 1) check if complex or not
435-
complex_solver = (
436-
cls.isinstance_complex(eps)
437-
or cls.isinstance_complex(mu)
438-
or np.any([cls.isinstance_complex(f) for f in der_mats])
435+
E, H, neff, keff = cls.solver_tensorial(
436+
**base_kwargs,
437+
direction=direction,
438+
dls=dls,
439+
Nxy=(Nx, Ny),
440+
dmin_pmc=dmin_pmc,
439441
)
440-
# 2) determine precision
441-
if complex_solver:
442-
mat_dtype = np.complex128 if mat_precision == "double" else np.complex64
443-
else:
444-
if mat_precision == "double":
445-
mat_dtype = np.float64
446-
447-
return mat_dtype
448442

449-
@classmethod
450-
def trim_small_values(cls, mat: sp.csr_matrix, tol: float) -> sp.csr_matrix:
451-
"""Eliminate elements of matrix ``mat`` for which ``abs(element) / abs(max_element) < tol``,
452-
or ``np.abs(mat_data) < tol``. This operates in-place on mat so there is no return.
453-
"""
454-
max_element = np.amax(np.abs(mat))
455-
mat.data *= np.logical_or(np.abs(mat.data) / max_element > tol, np.abs(mat.data) > tol)
456-
mat.eliminate_zeros()
457-
return mat
443+
return E, H, neff, keff, eps_spec
458444

459445
@classmethod
460446
def solver_diagonal(
@@ -684,9 +670,55 @@ def conditional_inverted_vec(eps_vec, threshold=1):
684670

685671
return E, H, neff, keff
686672

673+
@classmethod
674+
def matrix_data_type(cls, eps, mu, der_mats, mat_precision, is_tensorial):
675+
"""Determine data type that should be used for the matrix for diagonalization."""
676+
mat_dtype = np.float32
677+
# In tensorial case, even though the matrix can be real, the
678+
# expected eigenvalue is purely imaginary. So for now we enforce
679+
# the matrix to be complex type so that it will look for the right eigenvalues.
680+
if is_tensorial:
681+
mat_dtype = np.complex128 if mat_precision == "double" else np.complex64
682+
else:
683+
# 1) check if complex or not
684+
complex_solver = (
685+
cls.isinstance_complex(eps)
686+
or cls.isinstance_complex(mu)
687+
or np.any([cls.isinstance_complex(f) for f in der_mats])
688+
)
689+
# 2) determine precision
690+
if complex_solver:
691+
mat_dtype = np.complex128 if mat_precision == "double" else np.complex64
692+
else:
693+
if mat_precision == "double":
694+
mat_dtype = np.float64
695+
696+
return mat_dtype
697+
698+
@classmethod
699+
def trim_small_values(cls, mat: sp.csr_matrix, tol: float) -> sp.csr_matrix:
700+
"""Eliminate elements of matrix ``mat`` for which ``abs(element) / abs(max_element) < tol``,
701+
or ``np.abs(mat_data) < tol``. This operates in-place on mat so there is no return.
702+
"""
703+
max_element = np.amax(np.abs(mat))
704+
mat.data *= np.logical_or(np.abs(mat.data) / max_element > tol, np.abs(mat.data) > tol)
705+
mat.eliminate_zeros()
706+
return mat
707+
687708
@classmethod
688709
def solver_tensorial(
689-
cls, eps, mu, der_mats, num_modes, neff_guess, vec_init, mat_precision, direction
710+
cls,
711+
eps,
712+
mu,
713+
der_mats,
714+
num_modes,
715+
neff_guess,
716+
vec_init,
717+
mat_precision,
718+
direction,
719+
dls,
720+
Nxy=None,
721+
dmin_pmc=None,
690722
):
691723
"""EM eigenmode solver assuming ``eps`` or ``mu`` have off-diagonal elements."""
692724
import scipy.sparse as sp

tidy3d/components/simulation.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,9 +1580,10 @@ def make_eps_data(coords: Coords):
15801580
return xr.DataArray(eps_array, coords=coords, dims=("x", "y", "z"))
15811581

15821582
# combine all data into dictionary
1583-
if coord_key[0] == "E":
1584-
# off-diagonal components are sampled at respective locations (eg. `eps_xy` at `Ex`)
1585-
coords = grid[coord_key[0:2]]
1583+
if coord_key[0] == "E" and len(coord_key) > 2:
1584+
# off-diagonal components are sampled at grid boundaries
1585+
coords = grid["boundaries"]
1586+
coords = Coords(x=coords.x[:-1], y=coords.y[:-1], z=coords.z[:-1])
15861587
else:
15871588
coords = grid[coord_key]
15881589
return make_eps_data(coords)

0 commit comments

Comments
 (0)