FDTD Meep#

Meep is a free, open source Finite Difference Time Domain (FDTD) simulator

You to install meep and MPB with:

conda

conda install -c conda-forge pymeep=*=mpi_mpich_* -y

or mamba (faster conda)

mamba install pymeep=*=mpi_mpich_* -y

Works only on Mac, Linux or windows WSL

gdsfactory gmeep plugin computes the transmission spectrum for Photonic planar components.

One of the advantages of using gmeep is that you only need to define your component once using gdsfactory, and automatically can simulate it in meep without having to define the geometry again.

For extracting Sparameters, gmeep automatically swaps the source between ports to compute the full Sparameters matrix.

  • add monitors on each component port

  • extend ports to go over the PML

  • run simulation and compute Sparameter coefficients with proper ratios for each Sparameter. Monitors record Fourier Transform fields. Sparameter is a relationship of those parameters at different frequencies.

The resolution is in pixels/um, you should run with at least resolution=30 for 1/30 um/pixel (33 nm/ pixel)

Notice that most examples run with resolution=20 so they run fast.

Here are some examples on how to extract Sparameters in Meep in some planar devices.

 top view
      ________________________________
     |                               |
     | xmargin_left                  | port_extension
     |<--------->       port_margin ||<-->
  o2_|___________          _________||_o3
     |           \        /          |
     |            \      /           |
     |             ======            |
     |            /      \           |
  o1_|___________/        \__________|_o4
     |   |                 <-------->|
     |   |ymargin_bot   xmargin_right|
     |   |                           |
     |___|___________________________|

side view
      ________________________________
     |                     |         |
     |                     |         |
     |                   zmargin_top |
     |xmargin_left         |         |
     |<---> _____         _|___      |
     |     |     |       |     |     |
     |     |     |       |     |     |
     |     |_____|       |_____|     |
     |       |                       |
     |       |                       |
     |       |zmargin_bot            |
     |       |                       |
     |_______|_______________________|

Single core#

Running on a single CPU core can be slow as the a single core needs to update all the simulation grid points sequentially.

[1]:
import matplotlib.pyplot as plt
import numpy as np
import gdsfactory as gf
import gdsfactory.simulation.gmeep as gm

gf.config.set_plot_options(show_subports=False, show_ports=False)
2022-06-28 17:03:10.473 | INFO     | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.11.4
2022-06-28 17:03:11.491 | INFO     | gdsfactory.simulation.gmeep:<module>:28 - Meep '1.23.0' installed at ['/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep']
Using MPI version 4.0, 1 processes
[2]:
c = gf.components.straight(length=2)
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_2_0.png
[2]:
straight_length2: uid 0, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references

run=False only plots the simulations for you to review that is set up correctly

[3]:
df = gm.write_sparameters_meep(c, run=False, ymargin_top=3, ymargin_bot=3)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_4_1.png
[4]:
help(gm.write_sparameters_meep)
Help on cython_function_or_method in module gdsfactory.simulation.gmeep.write_sparameters_meep:

write_sparameters_meep(component: Union[str, Callable[..., gdsfactory.component.Component], gdsfactory.component.Component, Dict[str, Any]], port_symmetries: Optional[Dict[str, Dict[str, List[str]]]] = None, resolution: int = 30, wavelength_start: float = 1.5, wavelength_stop: float = 1.6, wavelength_points: int = 50, dirpath: Union[str, pathlib.Path, NoneType] = None, layer_stack: Optional[gdsfactory.tech.LayerStack] = None, port_margin: float = 2, port_monitor_offset: float = -0.1, port_source_offset: float = -0.1, filepath: Optional[pathlib.Path] = None, overwrite: bool = False, animate: bool = False, lazy_parallelism: bool = False, run: bool = True, dispersive: bool = False, xmargin: float = 0, ymargin: float = 3, xmargin_left: float = 0, xmargin_right: float = 0, ymargin_top: float = 0, ymargin_bot: float = 0, **settings) -> pandas.core.frame.DataFrame
    Compute Sparameters and writes them to a CSV filepath.
    Simulates each time using a different input port (by default, all of them)
    unless you specify port_symmetries:

    port_symmetries = {"o1":
            {
                "s11": ["s22","s33","s44"],
                "s21": ["s21","s34","s43"],
                "s31": ["s13","s24","s42"],
                "s41": ["s14","s23","s32"],
            }
        }
    - Only simulations using the outer key port names will be run
    - The associated value is another dict whose keys are the S-parameters computed
        when this source is active
    - The values of this inner Dict are lists of s-parameters whose values are copied

    This allows you doing less simulations

    TODO: automate this for common component types
    (geometrical symmetries, reciprocal materials, etc.)

    TODO: enable other port naming conventions, such as (in0, in1, out0, out1)


    .. code::

         top view
              ________________________________
             |                               |
             | xmargin_left                  | port_extension
             |<--------->       port_margin ||<-->
          o2_|___________          _________||_o3
             |           \        /          |
             |            \      /           |
             |             ======            |
             |            /      \           |
          o1_|___________/        \__________|_o4
             |   |                 <-------->|
             |   |ymargin_bot   xmargin_right|
             |   |                           |
             |___|___________________________|

        side view
              ________________________________
             |                     |         |
             |                     |         |
             |                   zmargin_top |
             |xmargin_left         |         |
             |<---> _____         _|___      |
             |     |     |       |     |     |
             |     |     |       |     |     |
             |     |_____|       |_____|     |
             |       |                       |
             |       |                       |
             |       |zmargin_bot            |
             |       |                       |
             |_______|_______________________|



    Args:
        component: to simulate.
        resolution: in pixels/um (30: for coarse, 100: for fine).
        port_symmetries: Dict to specify port symmetries, to save number of simulations.
        dirpath: directory to store Sparameters.
        layer_stack: contains layer to thickness, zmin and material.
            Defaults to active pdk.layer_stack.
        port_margin: margin on each side of the port.
        port_monitor_offset: offset between Component and monitor port in um.
        port_source_offset: offset between Component and source port in um.
        filepath: to store pandas Dataframe with Sparameters in CSV format.
            Defaults to dirpath/component_.csv.
        overwrite: overwrites stored Sparameter CSV results.
        animate: saves a MP4 images of the simulation for inspection, and also
            outputs during computation. The name of the file is the source index.
        lazy_parallelism: toggles the flag "meep.divide_parallel_processes" to
            perform the simulations with different sources in parallel.
        run: runs simulation, if False, only plots simulation.
        dispersive: use dispersive models for materials (requires higher resolution).
        xmargin: left and right distance from component to PML.
        xmargin_left: west distance from component to PML.
        xmargin_right: east distance from component to PML.
        ymargin: top and bottom distance from component to PML.
        ymargin_top: north distance from component to PML.
        ymargin_bot: south distance from component to PML.

    keyword Args:
        extend_ports_length: to extend ports beyond the PML (um).
        zmargin_top: thickness for cladding above core (um).
        zmargin_bot: thickness for cladding below core (um).
        tpml: PML thickness (um).
        clad_material: material for cladding.
        is_3d: if True runs in 3D (much slower).
        wavelength_start: wavelength min (um).
        wavelength_stop: wavelength max (um).
        wavelength_points: wavelength steps.
        dfcen: delta frequency.
        port_source_name: input port name.
        port_field_monitor_name:
        port_margin: margin on each side of the port (um).
        distance_source_to_monitors: in (um).
        port_source_offset: offset between source Component port and source MEEP port.
        port_monitor_offset: offset between monitor Component port and monitor MEEP port.
        material_name_to_meep: dispersive materials have a wavelength
            dependent index. Maps layer_stack names with meep material database names.

    Returns:
        sparameters in a pandas Dataframe (wavelengths, s11a, s12m, ...)
            where `a` is the angle in radians and `m` the module

As you’ve noticed we added ymargin_top and ymargin_bot to ensure we have enough distance to the PML

You can also do this directly with another version of the function that adds ymargin_top and ymargin_bot

[5]:
c = gf.components.straight(length=2)
df = gm.write_sparameters_meep(c, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
../../../_images/notebooks_plugins_meep_001_meep_sparameters_7_1.png

Because components with left-right ports are very common write_sparameters_meep y_margin = 3um

[6]:
c = gf.components.taper(length=2.0, width1=0.5, width2=1)
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_9_0.png
[6]:
taper_length2p0_width21: uid 10, ports ['o1', 'o2'], aliases [], 3 polygons, 0 references
[7]:
df = gm.write_sparameters_meep(c, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
../../../_images/notebooks_plugins_meep_001_meep_sparameters_10_1.png
[8]:
df = gm.write_sparameters_meep(c, resolution=20)
2022-06-28 17:03:12.094 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep:write_sparameters_meep:345 - Simulation loaded from PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/taper_length2p0_width21_ae267354.csv')
[9]:
gf.simulation.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_12_0.png
[10]:
gf.simulation.plot.plot_sparameters(df, keys=("s21m",), logscale=False)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_13_0.png
[11]:
gf.simulation.plot.plot_sparameters(df, keys=("s21m",))
../../../_images/notebooks_plugins_meep_001_meep_sparameters_14_0.png

For a small taper S21 (Transmission) is around 0dB (100% transmission)

Port symmetries#

You can save some simulations in reciprocal devices. If the device looks the same going from in -> out as out -> in, we only need to run one simulation

[12]:
c = gf.components.bend_euler(radius=3)
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_17_0.png
[12]:
bend_euler_radius3: uid 16, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references
[13]:
df = gm.write_sparameters_meep_1x1_bend90(c, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_18_1.png
[14]:
df = gm.write_sparameters_meep_1x1_bend90(c, run=True)
2022-06-28 17:03:13.869 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep:write_sparameters_meep:345 - Simulation loaded from PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/bend_euler_radius3_4c26976e.csv')
[15]:
gf.simulation.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_20_0.png
[16]:
gf.simulation.plot.plot_sparameters(df, keys=("s21m",), logscale=False)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_21_0.png
[17]:
gf.simulation.plot.plot_sparameters(df, keys=("s11m",))
../../../_images/notebooks_plugins_meep_001_meep_sparameters_22_0.png
[18]:
c = gf.components.crossing()
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_23_0.png
[18]:
crossing: uid 23, ports ['o1', 'o3', 'o4', 'o2'], aliases [], 5 polygons, 0 references

Here are the port symmetries for a crossing

port_symmetries = {
    "o1": {
        "s11": ["s22", "s33", "s44"],
        "s21": ["s12", "s34", "s43"],
        "s31": ["s13", "s24", "s42"],
        "s41": ["s14", "s23", "s32"],
    }
}
[19]:
df = gm.write_sparameters_meep(
    c,
    resolution=20,
    ymargin=0,
    port_symmetries=gm.port_symmetries.port_symmetries_crossing,
    run=False,
)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_25_1.png
[20]:
df = gm.write_sparameters_meep(
    c,
    resolution=20,
    ymargin=0,
    port_symmetries=gm.port_symmetries.port_symmetries_crossing,
    run=True,
)
2022-06-28 17:03:14.934 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep:write_sparameters_meep:345 - Simulation loaded from PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/crossing_073b4930.csv')
[21]:
gm.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_27_0.png
[22]:
gm.plot.plot_sparameters(df, keys=("s31m",))
../../../_images/notebooks_plugins_meep_001_meep_sparameters_28_0.png

As you can see this crossing looks beautiful but is quite lossy (9dB @ 15550nm)

Multicore (MPI)#

You can divide each simulation into multiple cores thanks to MPI (message passing interface)

Lets try to reproduce the coupler results from the Meep docs

According to the simulations in the doc to get a 3dB (50%/50%) splitter you need 150nm over 8um

[23]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time

import gdsfactory as gf
import gdsfactory.simulation as sim
import gdsfactory.simulation.gmeep as gm
[24]:
help(gf.components.coupler)
Help on function coupler in module gdsfactory.components.coupler:

coupler(gap: float = 0.236, length: float = 20.0, coupler_symmetric: Union[str, Callable[..., gdsfactory.component.Component], gdsfactory.component.Component, Dict[str, Any]] = <function coupler_symmetric at 0x7f2f9dab2670>, coupler_straight: Union[str, Callable[..., gdsfactory.component.Component], gdsfactory.component.Component, Dict[str, Any]] = <function coupler_straight at 0x7f2f9dab2430>, dy: float = 5.0, dx: float = 10.0, cross_section: Union[str, Callable[..., gdsfactory.cross_section.CrossSection], gdsfactory.cross_section.CrossSection, Dict[str, Any]] = 'strip', **kwargs) -> gdsfactory.component.Component
    Symmetric coupler.

    Args:
        gap: between straights in um.
        length: of coupling region in um.
        coupler_symmetric.
        coupler_straight.
        dy: port to port vertical spacing in um.
        dx: length of bend in x direction in um.
        cross_section: spec (CrossSection, string or dict).
        kwargs: cross_section settings.

    .. code::

               dx                                 dx
            |------|                           |------|
         o2 ________                           ______o3
                    \                         /           |
                     \        length         /            |
                      ======================= gap         | dy
                     /                       \            |
            ________/                         \_______    |
         o1                                          o4

                        coupler_straight  coupler_symmetric

[25]:
c = gf.components.coupler(length=8, gap=0.13)
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_33_0.png
[25]:
coupler_gap0p13_length8: uid 29, ports ['o1', 'o2', 'o3', 'o4'], aliases [], 21 polygons, 0 references
[26]:
gm.write_sparameters_meep(component=c, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_34_1.png
[27]:
filepath = gm.write_sparameters_meep_mpi(
    component=c,
    cores=4,
    resolution=30,
)
2022-06-28 17:03:19.054 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_mpi:write_sparameters_meep_mpi:140 - Simulation PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/coupler_gap0p13_length8_dd873517.csv') already exists
[28]:
df = pd.read_csv(filepath)
[29]:
gf.simulation.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_37_0.png
[30]:
gf.simulation.plot.plot_sparameters(df, keys=["s13m", "s14m"])
../../../_images/notebooks_plugins_meep_001_meep_sparameters_38_0.png

Batch#

You can also run a batch of multicore simulations

[31]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import gdsfactory as gf

import gdsfactory.simulation as sim
import gdsfactory.simulation.gmeep as gm
[32]:
c = gf.components.straight(length=3.1)
[33]:
gm.write_sparameters_meep(c, ymargin=3, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_42_1.png
[34]:
c1_dict = {"component": c, "ymargin": 3}
jobs = [
    c1_dict,
]

filepaths = gm.write_sparameters_meep_batch_1x1(
    jobs=jobs,
    cores_per_run=4,
    total_cores=8,
    lazy_parallelism=True,
)
2022-06-28 17:03:19.513 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:139 - Simulation PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/straight_length3p1_5332dc15.csv') not found. Adding it to the queue
2022-06-28 17:03:19.515 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:147 - Running 1 simulations
2022-06-28 17:03:19.515 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:148 - total_cores = 8 with cores_per_run = 4
2022-06-28 17:03:19.517 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:149 - Running 1 batches with up to 2 jobs each.
  0%|          | 0/1 [00:00<?, ?it/s]2022-06-28 17:03:19.521 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:160 - Task 0 of batch 0 is job 0
2022-06-28 17:03:19.524 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_mpi:write_sparameters_meep_mpi:140 - Simulation PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/straight_length3p1_a2a4b59a.csv') already exists
100%|██████████| 1/1 [00:00<00:00, 234.54it/s]
{'component': straight_length3p1: uid 42, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references,
 'ymargin': 3}

[35]:
df = pd.read_csv(filepaths[0])
gf.simulation.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_44_0.png
[36]:
c = gf.components.coupler_ring()
c
../../../_images/notebooks_plugins_meep_001_meep_sparameters_45_0.png
[36]:
coupler_ring: uid 47, ports ['o2', 'o1', 'o3', 'o4'], aliases [], 0 polygons, 3 references
[37]:
p = 2.5
gm.write_sparameters_meep(c, ymargin=0, ymargin_bot=p, xmargin=p, run=False)
Warning: grid volume is not an integer number of pixels; cell size will be rounded to nearest pixel.
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/meep/__init__.py:4472: ComplexWarning: Casting complex values to real discards the imaginary part
  return _meep._get_epsilon_grid(gobj_list, mlist, _default_material, _ensure_periodicity, gv, cell_size, cell_center, nx, xtics, ny, ytics, nz, ztics, grid_vals, frequency)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_46_1.png
[38]:
c1_dict = dict(
    component=c,
    ymargin=0,
    ymargin_bot=p,
    xmargin=p,
)
jobs = [c1_dict]

filepaths = gm.write_sparameters_meep_batch(
    jobs=jobs,
    cores_per_run=4,
    total_cores=8,
    delete_temp_files=False,
    lazy_parallelism=True,
)
2022-06-28 17:03:26.221 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:139 - Simulation PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/coupler_ring_714aa5b6.csv') not found. Adding it to the queue
2022-06-28 17:03:26.221 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:147 - Running 1 simulations
2022-06-28 17:03:26.224 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:148 - total_cores = 8 with cores_per_run = 4
2022-06-28 17:03:26.225 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:149 - Running 1 batches with up to 2 jobs each.
  0%|          | 0/1 [00:00<?, ?it/s]2022-06-28 17:03:26.226 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_batch:write_sparameters_meep_batch:160 - Task 0 of batch 0 is job 0
2022-06-28 17:03:26.228 | INFO     | gdsfactory.simulation.gmeep.write_sparameters_meep_mpi:write_sparameters_meep_mpi:140 - Simulation PosixPath('/home/runner/work/gdsfactory/gdsfactory/gdslib/sp/coupler_ring_eea93f16.csv') already exists
100%|██████████| 1/1 [00:00<00:00, 387.86it/s]
{'component': coupler_ring: uid 47, ports ['o2', 'o1', 'o3', 'o4'], aliases [], 0 polygons, 3 references,
 'xmargin': 2.5,
 'ymargin': 0,
 'ymargin_bot': 2.5}

[39]:
df = pd.read_csv(filepaths[0])
[40]:
gm.plot.plot_sparameters(df)
../../../_images/notebooks_plugins_meep_001_meep_sparameters_49_0.png
[41]:
gm.plot.plot_sparameters(df, keys=["s31m", "s41m"])
../../../_images/notebooks_plugins_meep_001_meep_sparameters_50_0.png
[42]:
gm.plot.plot_sparameters(df, keys=["s31m"])
../../../_images/notebooks_plugins_meep_001_meep_sparameters_51_0.png
[43]:
gm.plot.plot_sparameters(df, keys=["s41m"])
../../../_images/notebooks_plugins_meep_001_meep_sparameters_52_0.png