2. Maxwell eigenmode extraction of a meandered resonator designed using Qiskit Metal

2.1. Requirements

2.1.1. Software components

  • QTCAD

  • Gmsh

  • Qiskit Metal

2.1.2. Python script

  • qtcad/examples/tutorials/qiskit_metal_maxwell_eigenmodes.py

2.1.3. References

2.2. Briefing

In this example, with our QTCAD Qiskit Metal renderer, we extract the first three Maxwell eigenmodes of a meandered coplanar waveguide resonator designed using Qiskit Metal. Similarly to Maxwell eigenmode extraction of a coplanar waveguide resonator with adaptive meshing, the resonator is formed by the ground plane and a strip placed on a silicon substrate. The structure is enclosed inside a cavity.

Note

When running Qiskit Metal simulations, make sure the Conda environment where Qiskit Metal was set up is active, not the one in which QTCAD was installed.

If Qiskit Metal was installed following Additional installation instructions for Qiskit Metal, the name of the activated environment will be qiskit-metal. In this case, this environment can be activated using

conda activate qiskit-metal

2.3. Mesh generation

Note that, as opposed to most other tutorials, the mesh and raw geometry files in this tutorial are generated on-the-fly using Qiskit Metal.

Note

We leverage Qiskit Metal’s Gmsh renderer to create meshes, so any device design supported by it will work with the QTCAD renderer.

2.4. Geometry of the problem

For this tutorial, we specifically consider the quarter-wavelength resonator as in the figure below:

Design of the meandered resonator device.

2.5. Setting up the device

2.5.1. Preamble, file paths and base simulation parameters

We start by importing all relevant modules.

"""
Maxwell eigenmode extraction for a meandered resonator device designed
using Qiskit Metal.
"""
from pathlib import Path

# Import Qiskit Metal.
from qiskit_metal import designs
from qiskit_metal.qlibrary.tlines.meandered import RouteMeander
from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround
from qiskit_metal.qlibrary.terminations.short_to_ground import ShortToGround

# Import the QTCAD renderer.
from qiskit_metal.renderers.renderer_qtcad.qtcad_renderer import QQTCADRenderer

We also define the working directory and relevant file paths.

# Directories and file paths.
device_name = "qiskit_metal-meandered_resonator-eig"
# Work in the outputs folder of the current directory.
output_dir = str(Path(__file__).parent.resolve() / "output" / device_name)
# Geometry XAO file required for adaptive meshing.
geo_filepath = output_dir + "/" + device_name + ".xao"
# Mesh file.
mesh_filepath = output_dir + "/" + device_name + ".msh4"

We further define some variables to control the simulation. Here, we consider a flag to control the use of adaptive mesh refinement, in addition to the characteristic lengths of the mesh.

# Whether to calculate the capacitance matrix using adaptive meshing.
adaptive = True
# Set minimum and maximum characteristic lengths for the mesh elements.
mesh_h_min = "150um"
mesh_h_max = "300um"

Note

If not using adaptive mesh refinement, the characteristic lengths should be reduced. For instance, for this example, they could be set associated mesh_h_min = "30um" and mesh_h_max = "80um".

Let us also define how many eigenmodes are to be extracted.

# Number of Maxwell eigenmodes.
num_modes = 3

2.5.2. Creating the device

Using Qiskit Metal methods, let us then design the meandered resonator with open- and short-to-ground terminations. This will give us a quarter-wavelength resonator.

# Instantiate a design object.
design = designs.MultiPlanar({}, True)
# We could specify the size of the silicon chip:
# design.chips.main.size["size_x"] = "3mm"
# design.chips.main.size["size_y"] = "3mm"

# Define terminations, one open and the other short to ground.
otg = OpenToGround(
    design,
    "open_to_ground",
    options=dict(pos_x="0.5mm", pos_y="0um", orientation="0"),
)
stg = ShortToGround(
    design,
    "short_to_ground",
    options=dict(pos_x="-0.5mm", pos_y="0um", orientation="180"),
)

# Create the resonator object `readout`, labelling the terminations (pins).
rt_meander = RouteMeander(
    design,
    "readout",
    dict(
        total_length="3mm",
        fillet="90um",
        lead=dict(start_straight="100um"),
        pin_inputs=dict(
            start_pin=dict(component="short_to_ground", pin="short"),
            end_pin=dict(component="open_to_ground", pin="open"),
        ),
    ),
)

open_pins = [("open_to_ground", "open")]

Note

For more information on how to design devices using Qiskit Metal, we refer the reader to its documentation.

2.5.3. Configuring the QTCAD renderer

Having defined our device, let us instantiate and configure our QTCAD renderer, that is, the eigenmode solver.

# Setting up the solver
qtcad_renderer = QQTCADRenderer(
    design,
    options=dict(
        adaptive=adaptive,
        output_dir=output_dir,
        mesh_scale=1e-3,
        adaptive_mesh_scale=1e-3,
        geo_filepath=geo_filepath,
        mesh_filepath=mesh_filepath,
        make_subdir=False,
        maxwell_emode=dict(
            num_modes=num_modes,
            tol_rel=0.05,
        ),
    ),
)
  • Here, the most relevant parameter is maxwell_emode["tol_rel"], the relative error tolerance on the frequencies used in the adaptive mesh refinement.

  • Also, the flag make_subdir defines if the computation results will be stored in a new subdirectory within output_dir and labeled by the current date and time. The results of the Maxwell eigenmode extraction comprise the values of the mode frequencies at each iteration, along with the size of the mesh.

  • Lastly, as discussed in Maxwell eigenmode extraction of a coplanar waveguide resonator with adaptive meshing, one could add the parameter maxwell_emode["min_converged_iters"] (not defined above) to prevent the solver from terminating before the results have agreed between a given number of refinements.

2.5.4. Rendering and meshing the design

Let us now render the device.

# Create the geometry from the design.
qtcad_renderer.render_design(
    open_pins=open_pins,
    # Do not mesh the device (yet).
    mesh_geoms=False,
    initial_mesh_h_min=mesh_h_min,
    initial_mesh_h_max=mesh_h_max,
)

Next, we mesh the design and export the resulting geometry and mesh files. Both files are required by QTCAD if using adaptive mesh refinement.

# Mesh the device.
qtcad_renderer.gmsh.add_mesh(dim=3, intelli_mesh=not adaptive)

# Export the geometry and mesh files.
file_mesh, file_geo = qtcad_renderer.export_mesh()

Note

  • Here, the parameter intelli_mesh is set to True if not using adaptive meshing. It uses the pre-defined mesh size fields of QGmshRenderer to improve the device meshing and give better results. This is not necessary when using adaptive meshing, however.

  • The method export_mesh returns the paths to which the mesh and geometry files were written. It can also export to files in paths different than the ones used when instantiating QQTCADRenderer. To do that, pass to it the keyword arguments mesh_file, for the MSH4 mesh file, and geometry_file for the XAO geometry file.

2.5.5. Running the simulation

Finally, let us export the simulation parameters and compute the eigenmodes using QTCAD.

# Export parameters.
qtcad_renderer.export_parameters()
# Run QTCAD to extract the Maxwell eigenmodes.
qtcad_renderer.run_qtcad("eigs")

This will generate a JSON file qtcad_data.json which will be used to configure QTCAD and run the simulation. Inside the output_dir directory, QTCAD will store the Maxwell eigenvalues into the file qtcad_output_eigs.pickle. Also, the electromagnetic fields (eigenmodes) will be stored in a VTU file named qtcad-fields.vtu.

Note

  • Both methods, export_parameters and run_qtcad accept a custom path to the configuration JSON file via the parameter json_filepath.

  • By default, it is assumed that you have installed QTCAD into a Conda environment named qtcad. If not, when calling the method run_qtcad, make sure to pass the name of the appropriate Conda environment via the parameter env_name. For instance, if QTCAD is installed in a Conda environment named qtcad-sc, the call would be qtcad_renderer.run_qtcad("eigs", env_name="qtcad_sc").

  • The full path to the VTU file storing the eigenmodes is stored in the attribute eig_field_file.

To load the results, use the method load_qtcad_maxwell_eigenmodes as

frequencies = qtcad_renderer.load_qtcad_maxwell_eigenmodes()
print(frequencies)

The resulting frequencies are shown below, with 0 indexing the fundamental-mode frequency of the system:

           Frequency (GHz)
Eigenmode
0                10.193212
1                22.217576
2                30.831367

We can inspect the electrical field distribution using the method plot_eigenmodes. This method, useful to identify the different modes, uses PyVista to generate a slice at \(z=0\) of the absolute value of the electrical field from the VTU file output by QTCAD. Naturally, the VTU file can be further inspected using other software, for instance, ParaView.

qtcad_renderer.plot_eigenmodes()
Electrical field distribution associated to the first three eigenmodes of the meandered-resonator system.

Fig. 2.5.6 Magnitude of the electric field – at the level of the ground plane, \(z=0\) – of the first three eigenmodes of the meandered-resonator system. The values are displayed in logarithmic scale.

Note

  • By default, the intensity plot is going to be shown, but not saved. By passing save=True to plot_eigenmodes, the plot will be saved and its path will be returned by the method, in addition to being printed on screen.

  • We can also plot the \(z=0\) slice for a specific mode using the method plot_eigenmode. For example, to plot the third mode (eigenmode indexed by 2 above), we would call qtcad_renderer.plot_eigenmode(n=3).

    Electrical field distribution associated to the third eigenmode of the meandered-resonator system.

    Fig. 2.5.7 Magnitude of the electric field – at the level of the ground plane, \(z=0\) – of the third eigenmode of the meandered-resonator system. The values are displayed in logarithmic scale.

  • Both of the eigenmode plotting methods allow for the customization of the intensity plots. Check their docstrings for more information.

By inspecting the distribution of the electric field, we find that:

  • The first mode is associated to the fundamental resonance of the meandered resonator. The corresponding eigenvalue is usually denoted \(\omega_\text{r}\).

  • The second mode is related to a slotline mode. Such a mode is expected due to the lack of conductor crossovers along the meandered resonator; vide Maxwell eigenmode extraction of a coplanar waveguide resonator with adaptive meshing.

  • The third mode corresponds to the next possible excitation of the resonator. Since we have a quarter-wavelength resonator, the ideal ratio between its eigenvalue and the one associated to the first eigenmode is 3. In this example, the relative error compared to the ideal eigenvalue ratio is 0.8%.

2.6. Full code

__copyright__ = "Copyright 2022-2025, Nanoacademic Technologies Inc."

"""
Maxwell eigenmode extraction for a meandered resonator device designed
using Qiskit Metal.
"""
from pathlib import Path
# Import Qiskit Metal.
from qiskit_metal import designs
from qiskit_metal.qlibrary.tlines.meandered import RouteMeander
from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround
from qiskit_metal.qlibrary.terminations.short_to_ground import ShortToGround
# Import the QTCAD renderer.
from qiskit_metal.renderers.renderer_qtcad.qtcad_renderer import QQTCADRenderer

# Directories and file paths.
device_name = "qiskit_metal-meandered_resonator-eig"
# Work in the outputs folder of the current directory.
output_dir = str(Path(__file__).parent.resolve() / "output" / device_name)
# Geometry XAO file required for adaptive meshing.
geo_filepath = output_dir + "/" + device_name + ".xao"
# Mesh file.
mesh_filepath = output_dir + "/" + device_name + ".msh4"

# Whether to calculate the capacitance matrix using adaptive meshing.
adaptive = True
# Set minimum and maximum characteristic lengths for the mesh elements.
mesh_h_min = "150um"
mesh_h_max = "300um"


# Number of Maxwell eigenmodes.
num_modes = 3


# Instantiate a design object.
design = designs.MultiPlanar({}, True)
# We could specify the size of the silicon chip:
# design.chips.main.size["size_x"] = "3mm"
# design.chips.main.size["size_y"] = "3mm"

# Define terminations, one open and the other short to ground.
otg = OpenToGround(
    design,
    "open_to_ground",
    options=dict(pos_x="0.5mm", pos_y="0um", orientation="0"),
)
stg = ShortToGround(
    design,
    "short_to_ground",
    options=dict(pos_x="-0.5mm", pos_y="0um", orientation="180"),
)

# Create the resonator object `readout`, labelling the terminations (pins).
rt_meander = RouteMeander(
    design,
    "readout",
    dict(
        total_length="3mm",
        fillet="90um",
        lead=dict(start_straight="100um"),
        pin_inputs=dict(
            start_pin=dict(component="short_to_ground", pin="short"),
            end_pin=dict(component="open_to_ground", pin="open"),
        ),
    ),
)

open_pins = [("open_to_ground", "open")]


# Setting up the solver
qtcad_renderer = QQTCADRenderer(
    design,
    options=dict(
        adaptive=adaptive,
        output_dir=output_dir,
        mesh_scale=1e-3,
        adaptive_mesh_scale=1e-3,
        geo_filepath=geo_filepath,
        mesh_filepath=mesh_filepath,
        make_subdir=False,
        maxwell_emode=dict(
            num_modes=num_modes,
            tol_rel=0.05,
        ),
    ),
)

# Create the geometry from the design.
qtcad_renderer.render_design(
    open_pins=open_pins,
    # Do not mesh the device (yet).
    mesh_geoms=False,
    initial_mesh_h_min=mesh_h_min,
    initial_mesh_h_max=mesh_h_max,
)


# Mesh the device.
qtcad_renderer.gmsh.add_mesh(dim=3, intelli_mesh=not adaptive)

# Export the geometry and mesh files.
file_mesh, file_geo = qtcad_renderer.export_mesh()

# Export parameters.
qtcad_renderer.export_parameters()
# Run QTCAD to extract the Maxwell eigenmodes.
qtcad_renderer.run_qtcad("eigs")

frequencies = qtcad_renderer.load_qtcad_maxwell_eigenmodes()
print(frequencies)


qtcad_renderer.plot_eigenmodes()