Skip to content

Electrostatic simulations with Elmer

Here, we show how Elmer may be used to perform electrostatic simulations. For a given geometry, one needs to specify the terminals where to apply potential. This effectively solves the mutual capacitance matrix for the terminals and the capacitance to ground. For details on the physics, see [@smolic_capacitance_2021].

Installation

See Elmer FEM – Installation for installation or compilation instructions. Gplugins assumes ElmerSolver, ElmerSolver_mpi, and ElmerGrid are available in your PATH environment variable.

Alternatively, Singularity / Apptainer containers may be used. An example definition file is found at CSCfi/singularity-recipes and can be built with:

singularity build elmer.sif <DEFINITION_FILE>.def
Afterwards, an easy install method is to add scripts to ~/.local/bin (or elsewhere in PATH) calling the Singularity container for each of the necessary executables. For example, one may create a ElmerSolver_mpi file containing
#!/bin/bash
singularity exec <CONTAINER_LOCATION>/elmer.sif ElmerSolver_mpi $@

Geometry, layer config and materials

from math import inf

import gdsfactory as gf
from gdsfactory.components.interdigital_capacitor_enclosed import (
    interdigital_capacitor_enclosed,
)
from gdsfactory.gpdk import LAYER, get_generic_pdk
from gdsfactory.technology import LayerStack
from gdsfactory.technology.layer_stack import LayerLevel

from gplugins.elmer import run_capacitive_simulation_elmer

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

We employ an example LayerStack used in superconducting circuits similar to [@marxer_long-distance_2023].

layer_stack = LayerStack(
    layers=dict(
        substrate=LayerLevel(
            layer=LAYER.WAFER,
            thickness=500,
            zmin=0,
            material="Si",
            mesh_order=99,
        ),
        bw=LayerLevel(
            layer=LAYER.WG,
            thickness=200e-3,
            zmin=500,
            material="Nb",
            mesh_order=2,
        ),
    )
)
material_spec = {
    "Si": {"relative_permittivity": 11.45},
    "Nb": {"relative_permittivity": inf},
    "vacuum": {"relative_permittivity": 1},
}
simulation_box = [[-200, -200], [200, 200]]
c = gf.Component("capacitance_elmer")
cap = c << interdigital_capacitor_enclosed(
    metal_layer=LAYER.WG, gap_layer=LAYER.DEEPTRENCH, enclosure_box=simulation_box
)
c.add_ports(cap.ports)
substrate = gf.components.bbox(bbox=simulation_box, layer=LAYER.WAFER)
c << substrate
c.plot()

Running the simulation

We use the function :func:`~run_capacitive_simulation_elmer`. This runs the simulation and returns an instance of :class:`~ElectrostaticResults` containing the capacitance matrix and a path to the mesh and the field solution.
help(run_capacitive_simulation_elmer)
.. note::
   The meshing parameters and element order shown here are very lax. As such, the computed capacitances are not very accurate.
results = run_capacitive_simulation_elmer(
    c,
    layer_stack=layer_stack,
    material_spec=material_spec,
    n_processes=1,
    element_order=1,
    simulation_folder=Path(os.getcwd()) / "temporary",
    mesh_parameters=dict(
        background_tag="vacuum",
        background_padding=(0,) * 5 + (700,),
        port_names=[port.name for port in c.ports],
        default_characteristic_length=200,
        resolutions={
            "bw": {
                "resolution": 15,
            },
            "substrate": {
                "resolution": 40,
            },
            "vacuum": {
                "resolution": 40,
            },
            **{
                f"bw__{port}": {  # `__` is used as the layer to port delimiter for Elmer
                    "resolution": 20,
                    "DistMax": 30,
                    "DistMin": 10,
                    "SizeMax": 14,
                    "SizeMin": 3,
                }
                for port in c.ports
            },
        },
    ),
)
display(results)

if results.field_file_location:
    pv.start_xvfb()
    pv.set_jupyter_backend("trame")
    field = pv.read(results.field_file_location)
    field_slice = field.slice_orthogonal(z=layer_stack.layers["bw"].zmin * 1e-6)

    p = pv.Plotter()
    p.add_mesh(field_slice, scalars="electric field", cmap="turbo")
    p.show_grid()
    p.camera_position = "xy"
    p.enable_parallel_projection()
    p.show()

Bibliography

Bibliography

:style: unsrt :filter: docname in docnames