Benchmark of the mode solver 2

Contents

Benchmark of the mode solver 2#

Reproducing [1], where the modes of a strip and several rib waveguide were calculated and presented with an error value. The error for all modes is calculated to be smaller than \(\pm 3 \cdot 10^{-6}\), thus this should be the maximum derivation for our simulations. The results are presented here:

Hide code cell source
from collections import OrderedDict

import numpy as np
import pandas as pd
import shapely
import shapely.affinity
from juliacall import Main as jl
from skfem import Basis, ElementTriP0
from skfem.io.meshio import from_meshio

from femwell.maxwell.waveguide import compute_modes
from femwell.mesh import mesh_from_OrderedDict

julia_script = [
    "using Gridap",
    "using Gridap.Geometry",
    "using GridapGmsh",
    "using Femwell.Maxwell.Waveguide",
    "using GridapMakie, CairoMakie",
    "import PhysicalConstants.CODATA2018: c_0, μ_0, ε_0",
    "import Unitful: ustrip",
]

for line in julia_script:
    jl.seval(line)

core_width = 3
core_thickness = 1
slab_width = 18
box_thickness = 10
clad_thickness = 3

slab_thicknesses = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
neff_values_paper = [
    3.412022,
    3.412126,
    3.412279,
    3.412492,
    3.412774,
    3.413132,
    3.413571,
    3.414100,
]
neff_values_femwell_slepc = []
neff_values_femwell_scipy = []
neff_values_julia = []

for slab_thickness in slab_thicknesses:
    if slab_thickness == 0.0:
        core = shapely.Polygon(
            (
                (-core_width / 2, 0),
                (-core_width / 2, core_thickness),
                (core_width / 2, core_thickness),
                (core_width / 2, 0),
            )
        )
    else:
        core = shapely.Polygon(
            (
                (-slab_width / 2, 0),
                (-slab_width / 2, slab_thickness),
                (-core_width / 2, slab_thickness),
                (-core_width / 2, core_thickness),
                (core_width / 2, core_thickness),
                (core_width / 2, slab_thickness),
                (slab_width / 2, slab_thickness),
                (slab_width / 2, 0),
            )
        )

    polygons = OrderedDict(
        core=core,
        box=shapely.box(-slab_width / 2, -box_thickness, slab_width / 2, 0),
        clad=shapely.box(-slab_width / 2, clad_thickness, slab_width / 2, 0),
    )

    resolutions = dict(core={"resolution": 0.1, "distance": 3})

    mesh = from_meshio(
        mesh_from_OrderedDict(
            polygons, resolutions, default_resolution_max=0.3, filename="mesh.msh"
        )
    )
    refractive_indices = {"core": 3.44, "box": 3.40, "clad": 1}
    jl.refractive_indices = refractive_indices

    julia_script = [
        'model = GmshDiscreteModel("mesh.msh")',
        "Ω = Triangulation(model)",
        "labels = get_face_labeling(model)",
        "τ = CellField(get_face_tag(labels, num_cell_dims(model)), Ω)",
        'refractive_indices = Dict("core" => 3.44^2, "box" => 3.40^2, "clad" => 1^2)',
        "ε(tag) = Dict(get_tag_from_name(labels, u) => v for (u, v) in refractive_indices)[tag]",
        "modes = calculate_modes(model, ε ∘ τ,  λ = 1.15, order = 1)",
        "neffs = [real(n_eff(mode)) for mode in modes]",
    ]
    for line in julia_script:
        jl.seval(line)

    neff_values_julia.append(jl.neffs[0])

    basis0 = Basis(mesh, ElementTriP0())
    epsilon = basis0.zeros()
    for subdomain, n in refractive_indices.items():
        epsilon[basis0.get_dofs(elements=subdomain)] = n**2

    # modes = compute_modes(basis0, epsilon, wavelength=1.15, num_modes=1, order=2, solver="slepc")
    # neff_values_femwell_slepc.append(np.real(modes[0].n_eff))

    modes = compute_modes(basis0, epsilon, wavelength=1.15, num_modes=1, order=2, solver="scipy")
    neff_values_femwell_scipy.append(np.real(modes[0].n_eff))
Hide code cell output
[juliapkg] Found dependencies: /home/runner/miniconda3/lib/python3.10/site-packages/juliacall/juliapkg.json
[juliapkg] Found dependencies: /home/runner/miniconda3/lib/python3.10/site-packages/juliapkg/juliapkg.json
[juliapkg] Locating Julia ~1.6.1, ~1.7, ~1.8, ~1.9, =1.10.0, ^1.10.3
[juliapkg] Using Julia 1.10.3 at /opt/hostedtoolcache/julia/1.10.3/x64/bin/julia
[juliapkg] Using Julia project at /home/runner/work/femwell/femwell
Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython
Info    : Reading 'mesh.msh'...
Info    : 25 entities
Info    : 5848 nodes
Info    : 11624 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9421 nodes
Info    : 18975 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9386 nodes
Info    : 18902 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9619 nodes
Info    : 19363 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9735 nodes
Info    : 19590 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9940 nodes
Info    : 19997 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 9990 nodes
Info    : 20092 elements
Info    : Done reading 'mesh.msh'
Info    : Reading 'mesh.msh'...
Info    : 29 entities
Info    : 10248 nodes
Info    : 20605 elements
Info    : Done reading 'mesh.msh'
Hide code cell source
pd.DataFrame(
    {
        "slab_thickness": slab_thicknesses,
        "reference value": (f"{n:.6f}" for n in neff_values_paper),
        # "calculated value slepc": (f"{n:.6f}" for n in neff_values_femwell_slepc),
        # "difference slepc": (
        #    f"{n1-n2:.6f}" for n1, n2 in zip(neff_values_paper, neff_values_femwell_slepc)
        # ),
        "calculated value scipy": (f"{n:.6f}" for n in neff_values_femwell_scipy),
        "difference scipy": (
            f"{n1-n2:.6f}" for n1, n2 in zip(neff_values_paper, neff_values_femwell_scipy)
        ),
        "calculated value julia": (f"{n:.6f}" for n in neff_values_julia),
        "difference julia": (
            f"{n1-n2:.6f}" for n1, n2 in zip(neff_values_paper, neff_values_julia)
        ),
    }
).style.apply(
    lambda differences: [
        "background-color: green" if abs(float(difference)) < 5e-6 else "background-color: red"
        for difference in differences
    ],
    subset=["difference scipy", "difference julia"],  # "difference slepc",
)
  slab_thickness reference value calculated value scipy difference scipy calculated value julia difference julia
0 0.000000 3.412022 3.412021 0.000001 3.412020 0.000002
1 0.100000 3.412126 3.412126 0.000000 3.412124 0.000002
2 0.200000 3.412279 3.412280 -0.000001 3.412278 0.000001
3 0.300000 3.412492 3.412493 -0.000001 3.412492 0.000000
4 0.400000 3.412774 3.412777 -0.000003 3.412775 -0.000001
5 0.500000 3.413132 3.413135 -0.000003 3.413133 -0.000001
6 0.600000 3.413571 3.413573 -0.000002 3.413572 -0.000001
7 0.700000 3.414100 3.414103 -0.000003 3.414102 -0.000002

Bibliography#

[1]

G.R. Hadley. High-accuracy finite-difference equations for dielectric waveguide analysis II: dielectric corners. Journal of Lightwave Technology, 20(7):1219–1231, July 2002. URL: https://doi.org/10.1109/jlt.2002.800371, doi:10.1109/jlt.2002.800371.