Finite-Element Mode Solver

Finite-Element Mode Solver#

Using femwell, you can mesh any component cross-section and solve PDEs with its powerful mode solver.

Unlike other solvers that rely on predefined geometries, femwell works directly with the actual component geometry. You can compute the modes of a GDSFactory cross-section, which internally defines a “uz” mesh perpendicular to a straight component using the provided cross-section.

Additionally, you can downsample layers from the LayerStack and modify both the cross-section and LayerStack before running the simulation to adjust the geometry. You can also define refractive indices based on the active PDK.

import logging
import sys

import gdsfactory as gf
import matplotlib.pyplot as plt
from femwell.maxwell.waveguide import compute_modes
from femwell.visualization import plot_domains
from gdsfactory.generic_tech import LAYER_STACK, get_generic_pdk
from gdsfactory.technology import LayerStack
from gplugins.gmsh import get_mesh
from rich.logging import RichHandler
from skfem import Basis, ElementTriP0
from skfem.io.meshio import from_meshio

gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()

logger = logging.getLogger()
logger.removeHandler(sys.stderr)
logging.basicConfig(level="WARNING", datefmt="[%X]", handlers=[RichHandler()])

First we choose a component to simulate. Here, a straight strip waveguide:

xs = gf.cross_section.strip(width=1)

c = gf.components.straight(cross_section=xs)
c

../_images/aedb7b782f194f1789718d6f151826a29390831b23a03ff6354c5c42e8e39951.png

Then we choose a Layer Stack. Here, we simply downsample the generic stack:

filtered_layer_stack = LayerStack(
    layers={
        k: LAYER_STACK.layers[k]
        for k in (
            "core",
            "clad",
            "slab90",
            "box",
        )
    }
)

We can also change some of the values:

filtered_layer_stack.layers[
    "core"
].thickness = 0.22  # Perturb the layer_stack before simulating

filtered_layer_stack.layers[
    "slab90"
].thickness = 0.09  # Perturb the layer_stack before simulating

# When selecting resolutions, the names must match the keys of the layerstack
# Here, choose a finer mesh inside and close to the core
resolutions = {
    "core": {"resolution": 0.02, "DistMax": 2, "SizeMax": 0.2},
}

Using gplugins, we quickly generate a cross-sectional mesh:

mesh_gmsh = get_mesh(
    component=c,
    layer_stack=filtered_layer_stack,
    type="uz",  # we want a cross-section
    xsection_bounds=((1, -3), (1, 3)),  # the line from which we take a cross-section
    wafer_padding=3,  # pad simulation domain 3 microns around the component
    filename="mesh.msh",
    resolutions=resolutions,
    default_characteristic_length=0.5,
)
Info    : [  0%] Difference                                                                                  
Info    : [ 10%] Difference                                                                                  
Info    : [ 20%] Difference                                                                                  
Info    : [ 30%] Difference                                                                                  
Info    : [ 40%] Difference                                                                                  
Info    : [ 50%] Difference                                                                                  
Info    : [ 70%] Difference                                                                                  
Info    : [ 80%] Difference - Splitting faces                                                                                
                                                                                
Info    : [  0%] Fragments                                                                                  
Info    : [ 10%] Fragments                                                                                  
Info    : [ 20%] Fragments                                                                                  
Info    : [ 30%] Fragments                                                                                  
Info    : [ 40%] Fragments                                                                                  
Info    : [ 50%] Fragments                                                                                  
Info    : [ 70%] Fragments                                                                                  
Info    : [ 80%] Fragments - Splitting faces                                                                                
                                                                                
Info    : [  0%] Difference                                                                                  
Info    : [ 10%] Difference                                                                                  
Info    : [ 20%] Difference                                                                                  
Info    : [ 30%] Difference                                                                                  
Info    : [ 40%] Difference                                                                                  
Info    : [ 50%] Difference                                                                                  
Info    : [ 60%] Difference                                                                                  
Info    : [ 70%] Difference                                                                                  
Info    : [ 80%] Difference - Making faces                                                                                
Info    : [ 90%] Difference                                                                                  
                                                                                
Info    : [  0%] Fragments                                                                                  
Info    : [ 10%] Fragments                                                                                  
Info    : [ 20%] Fragments                                                                                  
Info    : [ 30%] Fragments                                                                                  
Info    : [ 40%] Fragments                                                                                  
Info    : [ 50%] Fragments                                                                                  
Info    : [ 60%] Fragments                                                                                  
Info    : [ 70%] Fragments                                                                                  
Info    : [ 80%] Fragments - Splitting faces                                                                                
                                                                                
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 4 (Line)
Info    : [  0%] Meshing curve 2 (Line)
Info    : [  0%] Meshing curve 1 (Line)
Info    : [  0%] Meshing curve 3 (Line)
Info    : [  0%] Meshing curve 5 (Line)
Info    : [ 10%] Meshing curve 6 (Line)
Info    : [ 10%] Meshing curve 7 (Line)
Info    : [ 30%] Meshing curve 8 (Line)
Info    : [ 30%] Meshing curve 9 (Line)
Info    : [ 30%] Meshing curve 10 (Line)
Info    : [ 30%] Meshing curve 11 (Line)
Info    : [ 30%] Meshing curve 12 (Line)
Info    : [ 30%] Meshing curve 13 (Line)
Info    : [ 30%] Meshing curve 14 (Line)
Info    : Done meshing 1D (Wall 0.00833656s, CPU 0.01908s)
Info    : Meshing 2D...
Info    : [  0%] Meshing surface 1 (Plane, Frontal-Delaunay)
Info    : [  0%] Meshing surface 3 (Plane, Frontal-Delaunay)
Info    : [  0%] Meshing surface 2 (Plane, Frontal-Delaunay)
Info    : Done meshing 2D (Wall 0.0492538s, CPU 0.123462s)
Info    : 3708 nodes 7607 elements
Info    : Writing 'mesh.msh'...
Info    : Done writing 'mesh.msh'
Info    : Writing '/tmp/tmprqgr4jad/mesh.msh'...
Info    : Done writing '/tmp/tmprqgr4jad/mesh.msh'

We can now throw this mesh into FEMWELL directly:

mesh = from_meshio(mesh_gmsh)
mesh.draw().show()

plot_domains(mesh)
plt.show()

../_images/a778613bf4d56997fc967fb804408ba842b011cbecd643ba58bbf257676ad715.png

../_images/7de235c6a0960d07f92b036e31b54736f5d238461f6cc4fbe64a2d99bd6615d8.png

Assign material values

basis0 = Basis(mesh, ElementTriP0())
epsilon = basis0.zeros()
for subdomain, n in {"core": 3.45, "box": 1.444, "clad": 1.444}.items():
    epsilon[basis0.get_dofs(elements=subdomain)] = n**2
basis0.plot(epsilon, colorbar=True).show()

../_images/3f7a567efe167f61128e0a22e38abcd748ce4d5d651e177b0de951ed13421707.png

Solve for the modes:

wavelength = 1.55
modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=2, order=1)

You can use them as inputs to other femwell mode solver functions to inspect or analyze the modes:

print(modes[0].te_fraction)
0.9978513183405068
modes[0].show("E", part="real")

../_images/712683e44e89ca95e504808ec86cdbcce8227c5b08c53e82474974952c850d45.png
dir(modes[0])

[
    'E',
    'H',
    'Sx',
    'Sy',
    'Sz',
    '__annotations__',
    '__class__',
    '__dataclass_fields__',
    '__dataclass_params__',
    '__delattr__',
    '__dict__',
    '__dir__',
    '__doc__',
    '__eq__',
    '__format__',
    '__ge__',
    '__getattribute__',
    '__getstate__',
    '__gt__',
    '__hash__',
    '__init__',
    '__init_subclass__',
    '__le__',
    '__lt__',
    '__match_args__',
    '__module__',
    '__ne__',
    '__new__',
    '__reduce__',
    '__reduce_ex__',
    '__repr__',
    '__setattr__',
    '__sizeof__',
    '__str__',
    '__subclasshook__',
    '__weakref__',
    'basis',
    'basis_epsilon_r',
    'calculate_confinement_factor',
    'calculate_coupling_coefficient',
    'calculate_effective_area',
    'calculate_intensity',
    'calculate_overlap',
    'calculate_pertubated_neff',
    'calculate_power',
    'calculate_propagation_loss',
    'epsilon_r',
    'frequency',
    'k',
    'k0',
    'n_eff',
    'omega',
    'plot',
    'plot_component',
    'plot_intensity',
    'poynting',
    'show',
    'te_fraction',
    'tm_fraction',
    'transversality',
    'wavelength'
]
modes[0].plot_component?
modes[0].plot_component("E", component="x", part="real", colorbar=True)

<Axes: title={'center': 'Ex (real. part)'}>

../_images/5c22d3894f6ade4ebe6a186974c529e242fcb1970c4752f36109594755353943.png
modes[1].plot_component("E", component="x", part="real", colorbar=True)

<Axes: title={'center': 'Ex (real. part)'}>

../_images/4d44cb5fe8158350cf10cf6d02a4c08c2726290ecb96446b496c5768b58117ca.png
modes[1].show("E", part="real")

../_images/5ad8d2e24e8f0f6e4c2885be5be268a78d39cce583257f62d7a468e6458b1aa2.png