3. Maxwell eigenmode extraction of an Xmon coupled to a meandered resonator designed using Qiskit Metal
3.1. Requirements
3.1.1. Software components
QTCAD
Gmsh
Qiskit Metal
3.1.2. Python script
qtcad/examples/tutorials/qiskit_metal_xmon_resonator_eigenmodes.py
3.1.3. References
3.2. Briefing
In this example, with our QTCAD Qiskit Metal renderer, we compute the first two Maxwell eigenmodes of a system comprised of an Xmon coupled to a meandered coplanar waveguide (CPW) resonator designed using Qiskit Metal and adaptive mesh refinement. The Josephson junction of the Xmon is approximated as an inductive lumped port.
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
3.3. Mesh generation
As with the other tutorials involving Qiskit Metal, the mesh and raw geometry files 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.
3.4. Geometry of the problem
For this tutorial, we specifically consider an Xmon coupled to a resonator as in the figure below:
Fig. 3.4.4 Overview of the device designed using Qiskit Metal with the different components labelled as follows: large type refers to the name of that component within Qiskit Metal and smaller type refers to the physical group associated to that component in the mesh and geometry files.
3.5. Setting up the device
3.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.qubits.transmon_cross import TransmonCross
from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround
# 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-coupled-xmon-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 variables to control the adaptive meshing tolerance on the frequency (eigenvalue) and the characteristic lengths of the mesh.
# Adaptive meshing relative tolerance.
tolerance_relative = 0.05
# Set minimum and maximum characteristic lengths for the mesh elements.
mesh_h_min = "150um"
mesh_h_max = "150um"
Let us also define how many eigenmodes are to be extracted and the inductance of the Josephson junction.
# Number of Maxwell eigenmodes.
num_modes = 2
# Tunnelling junction inductance (in henries).
inductance = 15e-9
3.5.2. Creating the device
Using Qiskit Metal methods, let us then design our system. First, we need to instantiate a design object.
# Instantiate a design object.
design = designs.MultiPlanar({}, True)
Then, let us define dictionaries to store the different options for both the
Xmon and the meandered resonator we will add to the design.
As in Capacitance matrix extraction for a transmon qubit designed using Qiskit Metal, the entry labelled
"connection_pads" in the Xmon’s options dictionary should be a dictionary
with the parameters of the coupling pad to which the CPW will be connected.
# Define labels for the different components.
transmon_label = "Q1"
coupler_label = "coupler"
resonator_label = "cpw"
resonator_termination_label = "open_to_ground"
resonator_termination_pin_label = "open"
# Define a dictionary with the parameters associated with the Xmon qubit.
options_qubit = dict(
# Position of the geometric centre of the island relative to the origin.
pos_x="-1500um",
pos_y="0um",
# Length of the ‘arms’ of the island.
cross_length="175um",
# Width of the ‘arms’ of the island.
cross_width="24um",
# Gap between the island and the ground plane.
cross_gap="25um",
# Anticlockwise orientation.
orientation="0",
# Add a claw coupler.
connection_pads={
coupler_label: dict(
# `0` for a claw coupler, `1` for a line coupler.
connector_type="0",
connector_location="180",
claw_length="65um",
claw_width="15um",
claw_cpw_length="0um",
# Separation between the xmon's gap and the gap associated with
# the line's claw.
ground_spacing="5um",
# Gap between the line and the ground.
claw_gap="6um",
),
},
)
# Resonator parameters, including the labelling of the terminations (pins).
options_resonator = dict(
total_length="6000um",
# Radius of the curvature.
fillet="50um",
# Pair of termination points to create a CPW resonator.
pin_inputs=dict(
start_pin=dict(component=transmon_label, pin=coupler_label),
end_pin=dict(
component=resonator_termination_label, pin=resonator_termination_pin_label
),
),
lead=dict(start_straight="100um", end_straight="100um"),
meander=dict(
# Offset between the centre line of the meander line and the
# centre-line that stretches from the lead-in termination to the x (or
# y) coordinate of lead-out termination.
asymmetry="100um",
# Minimum spacing between adjacent meander curves.
spacing="200um",
),
)
# (Open-to-ground) termination, which defines the end of the CPW.
options_res_termination = dict(pos_x="0.5mm", pos_y="0um", orientation="0")
# Define the list of open-to-ground components.
open_pins = [(resonator_termination_label, resonator_termination_pin_label)]
Then, let us include the different components in the design.
# Add the different components to the design.
qubit = TransmonCross(design, transmon_label, options=options_qubit)
res_termination = OpenToGround(
design,
resonator_termination_label,
options=options_res_termination,
)
resonator = RouteMeander(design, resonator_label, options=options_resonator)
3.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=True,
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=tolerance_relative,
),
),
)
Note
For more details about the eigenmode solver options, please refer to Maxwell eigenmode extraction of a meandered resonator designed using Qiskit Metal.
3.5.4. Set up of the Josephson junction, rendering and meshing the design
Before setting up the Josephson junction, let us render the device.
# Create the geometry from the design.
qtcad_renderer.render_design(
open_pins=open_pins,
# Render the Josephson junction.
skip_junctions=False,
# Do not mesh the device (yet).
mesh_geoms=False,
initial_mesh_h_min=mesh_h_min,
initial_mesh_h_max=mesh_h_max,
)
Then, we are able to configure the junction as an inductive port with linear
inductance using the method QQTCADRenderer.set_up_junction.
# Length of the junction: gap between the island and the ground plane.
length = options_qubit["cross_gap"]
# Add the tunneling junction.
table_junctions = qtcad_renderer.set_up_junction(transmon_label, inductance, length)
print(table_junctions)
We need to pass to
set_up_junctionthe junction’s length and inductance, with the width being automatically obtained from the qubit geometry.Also, it returns
pandas.DataFramewith the properties of the junctions set up in the system.
bnd_spec inductance length width dir
0 Q1_rect_jj 1.500000e-08 0.025 0.024 y
Next, we mesh the design and export the resulting geometry and mesh files. Both files are required by QTCAD when using adaptive mesh refinement.
# Mesh the device.
qtcad_renderer.gmsh.add_mesh(dim=3, intelli_mesh=False)
# Export the geometry and mesh files.
file_mesh, file_geo = qtcad_renderer.export_mesh()
Note
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.
3.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 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 4.038574
1 9.922994
From the physics of the problem, we expect the eigenvalue associated to the lower mode to be associated to the Xmon and the higher one to describe an excitation of the resonator.
To confirm that, we can inspect the electrical field distribution using the
method plot_eigenmodes.
It generates a slice at \(z=0\) of the absolute value of the electrical
field from the VTU file output by QTCAD, which can also be inspected using
other software, for instance, ParaView.
qtcad_renderer.plot_eigenmodes()
Fig. 3.5.3 Magnitude of the electric field – at the level of the ground plane, \(z=0\) – of the first two eigenmodes of the resonator-coupled Xmon 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 modes individually, we would callqtcad_renderer.plot_eigenmode(n=1)andqtcad_renderer.plot_eigenmode(n=2).
Fig. 3.5.4 Magnitude of the electric field – at the level of the ground plane, \(z=0\) – of the first eigenmode, associated to the Xmon, of the resonator-coupled Xmon system. The values are displayed in logarithmic scale.
Fig. 3.5.5 Magnitude of the electric field – at the level of the ground plane, \(z=0\) – of the second eigenmode, associated to the resonator, of the resonator-coupled Xmon 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 indeed associated to the Xmon qubit and the second mode is related to the fundamental resonance of the meandered resonator.
The frequencies (eigenvalues) computed, when combined with a capacitance matrix analysis, can be used to further characterize the system. For instance, one could estimate the resonator–coupling constant, usually denoted by \(g\).
3.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.qubits.transmon_cross import TransmonCross
from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround
# Import the QTCAD renderer.
from qiskit_metal.renderers.renderer_qtcad.qtcad_renderer import QQTCADRenderer
# Directories and file paths.
device_name = "qiskit_metal-coupled-xmon-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"
# Adaptive meshing relative tolerance.
tolerance_relative = 0.05
# Set minimum and maximum characteristic lengths for the mesh elements.
mesh_h_min = "150um"
mesh_h_max = "150um"
# Number of Maxwell eigenmodes.
num_modes = 2
# Tunnelling junction inductance (in henries).
inductance = 15e-9
# Instantiate a design object.
design = designs.MultiPlanar({}, True)
# Define labels for the different components.
transmon_label = "Q1"
coupler_label = "coupler"
resonator_label = "cpw"
resonator_termination_label = "open_to_ground"
resonator_termination_pin_label = "open"
# Define a dictionary with the parameters associated with the Xmon qubit.
options_qubit = dict(
# Position of the geometric centre of the island relative to the origin.
pos_x="-1500um",
pos_y="0um",
# Length of the ‘arms’ of the island.
cross_length="175um",
# Width of the ‘arms’ of the island.
cross_width="24um",
# Gap between the island and the ground plane.
cross_gap="25um",
# Anticlockwise orientation.
orientation="0",
# Add a claw coupler.
connection_pads={
coupler_label: dict(
# `0` for a claw coupler, `1` for a line coupler.
connector_type="0",
connector_location="180",
claw_length="65um",
claw_width="15um",
claw_cpw_length="0um",
# Separation between the xmon's gap and the gap associated with
# the line's claw.
ground_spacing="5um",
# Gap between the line and the ground.
claw_gap="6um",
),
},
)
# Resonator parameters, including the labelling of the terminations (pins).
options_resonator = dict(
total_length="6000um",
# Radius of the curvature.
fillet="50um",
# Pair of termination points to create a CPW resonator.
pin_inputs=dict(
start_pin=dict(component=transmon_label, pin=coupler_label),
end_pin=dict(
component=resonator_termination_label, pin=resonator_termination_pin_label
),
),
lead=dict(start_straight="100um", end_straight="100um"),
meander=dict(
# Offset between the centre line of the meander line and the
# centre-line that stretches from the lead-in termination to the x (or
# y) coordinate of lead-out termination.
asymmetry="100um",
# Minimum spacing between adjacent meander curves.
spacing="200um",
),
)
# (Open-to-ground) termination, which defines the end of the CPW.
options_res_termination = dict(pos_x="0.5mm", pos_y="0um", orientation="0")
# Define the list of open-to-ground components.
open_pins = [(resonator_termination_label, resonator_termination_pin_label)]
# Add the different components to the design.
qubit = TransmonCross(design, transmon_label, options=options_qubit)
res_termination = OpenToGround(
design,
resonator_termination_label,
options=options_res_termination,
)
resonator = RouteMeander(design, resonator_label, options=options_resonator)
# Setting up the solver
qtcad_renderer = QQTCADRenderer(
design,
options=dict(
adaptive=True,
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=tolerance_relative,
),
),
)
# Create the geometry from the design.
qtcad_renderer.render_design(
open_pins=open_pins,
# Render the Josephson junction.
skip_junctions=False,
# Do not mesh the device (yet).
mesh_geoms=False,
initial_mesh_h_min=mesh_h_min,
initial_mesh_h_max=mesh_h_max,
)
# Length of the junction: gap between the island and the ground plane.
length = options_qubit["cross_gap"]
# Add the tunneling junction.
table_junctions = qtcad_renderer.set_up_junction(transmon_label, inductance, length)
print(table_junctions)
# Mesh the device.
qtcad_renderer.gmsh.add_mesh(dim=3, intelli_mesh=False)
# 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()