Source code for gplugins.femwell.mode_solver

import time
from typing import Any

import gdsfactory as gf
import numpy as np
from femwell.maxwell.waveguide import Modes, compute_modes
from gdsfactory.pdk import get_layer_stack
from gdsfactory.technology import LayerStack
from gdsfactory.typings import ComponentSpec, CrossSectionSpec, PathType
from skfem import (
    Basis,
    ElementTriN2,
    ElementTriP0,
    ElementTriP2,
    Mesh,
)

from gplugins.gmsh.get_mesh import get_mesh

mesh_filename = "mesh.msh"


def load_mesh_basis(mesh_filename: PathType):
    mesh = Mesh.load(mesh_filename)
    basis = Basis(mesh, ElementTriN2() * ElementTriP2())
    basis0 = basis.with_element(ElementTriP0())
    return mesh, basis0


[docs] def compute_cross_section_modes( cross_section: CrossSectionSpec, layer_stack: LayerStack, wavelength: float = 1.55, num_modes: int = 4, order: int = 1, radius: float = np.inf, wafer_padding: float = 2.0, **kwargs: Any, ) -> Modes: """Calculate effective index of a cross-section. Defines a "straight" component of the cross_section, and calls compute_component_slice_modes. Args: cross_section: gdsfactory cross_section. layer_stack: gdsfactory layer_stack. wavelength: in um. num_modes: to compute. order: order of the mesh elements. 1: linear, 2: quadratic. radius: defaults to inf. wafer_padding: in um. kwargs: kwargs for compute_component_slice_modes Keyword Args: solver: can be slepc or scipy. resolutions (Dict): Pairs {"layername": {"resolution": float, "distance": "float}} to roughly control mesh refinement within and away from entity, respectively. mesh_scaling_factor (float): factor multiply mesh geometry by. default_resolution_min (float): gmsh minimal edge length. default_resolution_max (float): gmsh maximal edge length. background_tag (str): name of the background layer to add (default: no background added). background_padding (Tuple): [xleft, ydown, xright, yup] distances to add to the components and to fill with background_tag. global_meshsize_array: np array [x,y,z,lc] to parametrize the mesh. global_meshsize_interpolant_func: interpolating function for global_meshsize_array. extra_shapes_dict: Optional[OrderedDict] of {key: geo} with key a label and geo a shapely (Multi)Polygon or (Multi)LineString of extra shapes to override component. merge_by_material: boolean, if True will merge polygons from layers with the same layer.material. Physical keys will be material in this case. """ # Get meshable component from cross-section c = gf.components.straight(length=10, cross_section=cross_section) dx = c.xsize dy = c.ysize xsection_bounds = [ [dx / 2, dy - wafer_padding], [dx / 2, dy + wafer_padding], ] # Mesh as component return compute_component_slice_modes( component=c, xsection_bounds=xsection_bounds, layer_stack=layer_stack, wavelength=wavelength, num_modes=num_modes, order=order, radius=radius, wafer_padding=wafer_padding, **kwargs, )
_material_name_to_index = { "si": 3.48, "sio2": 1.44, "sin": 2.0, } def compute_component_slice_modes( component: ComponentSpec, xsection_bounds: tuple[tuple[float, float], tuple[float, float]], layer_stack: LayerStack, wavelength: float = 1.55, num_modes: int = 4, order: int = 1, wafer_padding: float = 2.0, radius: float = np.inf, metallic_boundaries: bool = False, n_guess: float | None = None, solver: str = "scipy", material_name_to_index: dict[str, float] | None = None, **kwargs: Any, ) -> Modes: """Calculate effective index of component slice. Args: component: gdsfactory component. xsection_bounds: xy line defining where to take component cross_section. layer_stack: gdsfactory layer_stack. wavelength: wavelength (um). num_modes: number of modes to return. order: order of the mesh elements. 1: linear, 2: quadratic. wafer_padding: padding beyond bbox to add to WAFER layers. radius: bend radius of the cross-section. metallic_boundaries: if True, will set the boundaries to be metallic. n_guess: initial guess for the effective index. solver: can be slepc or scipy. material_name_to_index: dictionary mapping material names to refractive indices. kwargs: kwargs for get_mesh Keyword Args: resolutions (Dict): Pairs {"layername": {"resolution": float, "distance": "float}} to roughly control mesh refinement within and away from entity, respectively. default_characteristic_length (float): gmsh characteristic length. background_tag (str): name of the background layer to add (default: no background added). background_padding (Tuple): [xleft, ydown, xright, yup] distances to add to the components and to fill with background_tag. background_remeshing_file (str): filename to load background remeshing from. global_meshsize_array: np array [x,y,z,lc] to parametrize the mesh. global_meshsize_interpolant_func: interpolating function for global_meshsize_array. extra_shapes_dict: Optional[OrderedDict] of {key: geo} with key a label and geo a shapely (Multi)Polygon or (Multi)LineString of extra shapes to override component. merge_by_material: boolean, if True will merge polygons from layers with the same layer.material. Physical keys will be material in this case. wafer_layer: layer to use for WAFER padding. """ material_name_to_index = material_name_to_index or _material_name_to_index # Mesh mesh = get_mesh( component=component, type="uz", xsection_bounds=xsection_bounds, layer_stack=layer_stack, filename=mesh_filename, wafer_padding=wafer_padding, **kwargs, ) # Assign materials to mesh elements mesh, basis0 = load_mesh_basis(mesh_filename) epsilon = basis0.zeros(dtype=complex) for layername, layer in layer_stack.layers.items(): if layername in mesh.subdomains.keys(): epsilon[basis0.get_dofs(elements=layername)] = ( material_name_to_index[layer.material] ** 2 ) if "background_tag" in kwargs: epsilon[basis0.get_dofs(elements=kwargs["background_tag"])] = ( material_name_to_index[kwargs["background_tag"]] ** 2 ) return compute_modes( basis0, epsilon, wavelength=wavelength, mu_r=1, num_modes=num_modes, order=order, radius=radius, solver=solver, n_guess=n_guess, metallic_boundaries=metallic_boundaries, ) if __name__ == "__main__": import matplotlib.pyplot as plt start = time.time() filtered_layer_stack = LayerStack( layers={ k: get_layer_stack().layers[k] for k in ( "core", "clad", "slab90", "box", ) } ) filtered_layer_stack.layers["core"].thickness = 0.2 resolutions = { "core": {"resolution": 0.02, "distance": 2}, "clad": {"resolution": 0.2, "distance": 1}, "box": {"resolution": 0.2, "distance": 1}, "slab90": {"resolution": 0.05, "distance": 1}, } cross_section = False cross_section = True if cross_section: modes = compute_cross_section_modes( cross_section="rib", layer_stack=filtered_layer_stack, wavelength=1.55, num_modes=4, order=1, radius=np.inf, resolutions=resolutions, ) mode = modes[0] mode.show(mode.E.real, colorbar=True, direction="x") else: component = gf.components.coupler_full(dw=0) component.show() modes = compute_component_slice_modes( component, [[0, -3], [0, 3]], layer_stack=filtered_layer_stack, wavelength=1.55, num_modes=4, order=1, radius=np.inf, mesh_filename="./mesh.msh", resolutions=resolutions, ) print(modes) print(modes[0].te_fraction) modes[0].show(np.real(modes[0].E)) modes[0].show(np.imag(modes[0].E)) modes[0].show(np.real(modes[0].H)) modes[0].show(np.imag(modes[0].H)) integrals = np.zeros((len(modes),) * 2, dtype=complex) for i in range(len(modes)): for j in range(len(modes)): integrals[i, j] = modes[i].calculate_overlap(modes[j]) plt.imshow(np.real(integrals)) plt.colorbar() plt.show() # Create basis to select a certain simulation extent def sel_fun(x): return (x[0] < 0) * (x[0] > -1) * (x[1] > 0) * (x[1] < 0.5) print(modes.sorted(lambda mode: mode.calculate_power(elements=sel_fun)))