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:
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_subdirdefines if the computation results will be stored in a new subdirectory withinoutput_dirand 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_meshis set toTrueif not using adaptive meshing. It uses the pre-defined mesh size fields ofQGmshRendererto improve the device meshing and give better results. This is not necessary when using adaptive meshing, however.The method
export_meshreturns 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 instantiatingQQTCADRenderer. To do that, pass to it the keyword argumentsmesh_file, for the MSH4 mesh file, andgeometry_filefor 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_parametersandrun_qtcadaccept a custom path to the configuration JSON file via the parameterjson_filepath.By default, it is assumed that you have installed QTCAD into a Conda environment named
qtcad. If not, when calling the methodrun_qtcad, make sure to pass the name of the appropriate Conda environment via the parameterenv_name. For instance, if QTCAD is installed in a Conda environment namedqtcad-sc, the call would beqtcad_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()
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=Truetoplot_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 callqtcad_renderer.plot_eigenmode(n=3).
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()