@@ -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
0 commit comments