@@ -274,6 +274,8 @@ def compute_modes(
274
274
direction ,
275
275
enable_incidence_matrices ,
276
276
basis_E = basis_E ,
277
+ dls = dls ,
278
+ dmin_pmc = dmin_pmc ,
277
279
)
278
280
279
281
# Transform back to original axes, E = J^T E'
@@ -308,6 +310,8 @@ def solver_em(
308
310
direction ,
309
311
enable_incidence_matrices ,
310
312
basis_E ,
313
+ dls ,
314
+ dmin_pmc = None ,
311
315
):
312
316
"""Solve for the electromagnetic modes of a system defined by in-plane permittivity and
313
317
permeability and assuming translational invariance in the normal direction.
@@ -323,7 +327,7 @@ def solver_em(
323
327
mu_tensor : np.ndarray
324
328
Shape (3, 3, N), the permittivity tensor at every point in the plane.
325
329
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.
327
331
num_modes : int
328
332
Number of modes to solve for.
329
333
neff_guess : float
@@ -334,6 +338,10 @@ def solver_em(
334
338
Direction of mode propagation.
335
339
basis_E: np.ndarray
336
340
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.
337
345
338
346
Returns
339
347
-------
@@ -377,8 +385,8 @@ def conductivity_model_for_good_conductor(
377
385
# initial vector for eigensolver in correct data type
378
386
vec_init = cls .set_initial_vec (Nx , Ny , is_tensorial = is_tensorial )
379
387
380
- # call solver
381
- kwargs = {
388
+ # call solver: base kwargs shared by diagonal and tensorial solvers
389
+ base_kwargs = {
382
390
"eps" : eps_tensor ,
383
391
"mu" : mu_tensor ,
384
392
"der_mats" : der_mats ,
@@ -388,18 +396,19 @@ def conductivity_model_for_good_conductor(
388
396
"mat_precision" : mat_precision ,
389
397
}
390
398
391
- is_eps_complex = cls .isinstance_complex (eps_tensor )
392
-
393
399
if basis_E is not None and is_tensorial :
394
400
raise RuntimeError (
395
401
"Tensorial eps not yet supported in relative mode solver "
396
402
"(with basis fields provided)."
397
403
)
398
404
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
+
399
408
if not is_tensorial :
400
409
eps_spec = "diagonal"
401
410
E , H , neff , keff = cls .solver_diagonal (
402
- ** kwargs ,
411
+ ** base_kwargs ,
403
412
enable_incidence_matrices = enable_incidence_matrices ,
404
413
basis_E = basis_E ,
405
414
)
@@ -410,51 +419,28 @@ def conductivity_model_for_good_conductor(
410
419
411
420
elif not is_eps_complex :
412
421
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
+ )
414
429
if direction == "-" :
415
430
E = np .conj (E )
416
431
H = - np .conj (H )
417
432
418
433
else :
419
434
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 ,
439
441
)
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
448
442
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
458
444
459
445
@classmethod
460
446
def solver_diagonal (
@@ -684,9 +670,55 @@ def conditional_inverted_vec(eps_vec, threshold=1):
684
670
685
671
return E , H , neff , keff
686
672
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
+
687
708
@classmethod
688
709
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 ,
690
722
):
691
723
"""EM eigenmode solver assuming ``eps`` or ``mu`` have off-diagonal elements."""
692
724
import scipy .sparse as sp
0 commit comments