1. Capacitance matrix extraction for a transmon qubit designed using Qiskit Metal
1.1. Requirements
1.1.1. Software components
QTCAD
Gmsh
Qiskit Metal
1.1.2. Python script
qtcad/examples/tutorials/qiskit_metal_cap_pocket0.py
1.1.3. References
1.2. Briefing
In this example, with our QTCAD Qiskit Metal renderer, we extract the capacitance matrix for a transmon qubit designed using Qiskit Metal.
The use of Qiskit Metal, an open-source Python framework focused on the design of superconducting quantum chips and devices, facilitates the process of creating transmon qubit designs. This is to be contrasted with the pure Gmsh workflow of Capacitance matrix extraction for an Xmon qubit, for instance. While, in principle, Gmsh can be used to create arbitrary 3D geometries, in practice, this can be time consuming and present an important barrier to entry for new users.
Qiskit Metal makes the design process easier by providing several pre-defined
classes of transmons qubits in its QComponent library.
Several properties of these qubits and related components (such as the size
and gap of the superconducting islands, as well as number of coupling pads)
are customizable, and there exist methods to create custom designs.
For more information on Qiskit Metal, please take a look at the
Qiskit Metal documentation.
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
1.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.
1.4. Geometry of the problem
For this tutorial, we specifically consider a standard pocket transmon qubit with a ground plane and two superconducting islands (pads) connected by a Josephson junction; vide figure below:
Fig. 1.4.14 Components of the pocket transmon qubit. The Josephson junction is represented by a hatched rectangle in-between the pads.
Several parameters controls properties of the transmon and of the coupling pads:
Fig. 1.4.15 Main parameters associated to the (pads of a) pocket transmon overlaid over the current design. The superconducting islands (pads) are highlighted.
Fig. 1.4.16 Main parameters associated to the coupling pads of pocket transmons
overlaid over the current design.
The coupling pads are highlighted.
Despite the parameters being drawn next to different couplers, they are all
available for each coupling pad.
Also, (±1,±1) refers to the tuples (loc_W, loc_H) which define
where (which “quadrant”) to place a coupling pad.
1.5. Setting up the device
1.5.1. Preamble, file paths, and base simulation parameters
We start by importing all relevant modules.
"""
Capacitance matrix extraction for a transmon qubit designed using Qiskit
Metal.
"""
from pathlib import Path
# Import Qiskit Metal.
from qiskit_metal import designs
from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket
# 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-pocket-cap"
# 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"
Further, let us 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 initial (coarse) 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 = "150um"
Note
If not using adaptive mesh refinement, the characteristic lengths should be
reduced. For instance, for this example, they could be set as
mesh_h_min = "30um" and mesh_h_max = "80um".
1.5.2. Creating the device
Let us then create a single floating transmon qubit with two pads (vide qlibrary.TransmonPocket) and parameters based on Qiskit Metal’s tutorial Capacitance matrix and LOM analysis.
We first need to instantiate a design object.
# Instantiate a design object.
design = designs.MultiPlanar({}, True)
Now, let us define dictionaries to store the different options for both the
transmons and the coupling pads we will add to the design.
Note that, to add coupling pads to the transmon, we need to include their
settings as a dictionary in an entry labelled "connection_pads" of the
transmon’s options dictionary.
# Define a dictionary with the parameters associated with the chosen pocket
# transmon qubit. Values taken from
# https://qiskit.org/ecosystem/metal/tut/4-Analysis/4.01-Capacitance-and-LOM.html
options_transmon = dict(
pad_gap="30um",
pad_width="425um",
pad_height="90um",
pocket_width="650um",
pocket_height="650um",
)
# Get some default values. Note that, if we were to omit these entries in the
# dictionary defining a coupling pad, Qiskit Metal would already use these
# variables. We do it here explicitly for completeness.
cpw_width = design.variables["cpw_width"]
cpw_gap = design.variables["cpw_gap"]
# Properties common to all coupling pads.
options_coupler_common = dict(
pad_gap="15um",
pad_cpw_shift="5um",
pad_cpw_extent="25um",
cpw_width=cpw_width,
cpw_gap=cpw_gap,
cpw_extend="100um",
pocket_extent="5um",
pocket_rise="65um",
)
# Include the coupling pads’ properties into the dictionary with the transmon’s
# properties.
options_couplers = dict(
coupler1=dict(
loc_W=-1,
loc_H=+1,
pad_width="125um",
pad_height="30um",
**options_coupler_common,
),
coupler2=dict(
loc_W=-1,
loc_H=-1,
pad_width="125um",
pad_height="50um",
**options_coupler_common,
),
readout=dict(
loc_W=+1,
loc_H=-1,
pad_width="200um",
pad_height="30um",
**options_coupler_common,
),
)
options_transmon["connection_pads"] = options_couplers
# Lastly, define which coupling pads should have their ends open to ground.
open_pins = [
("Q1", "coupler1"),
("Q1", "coupler2"),
("Q1", "readout"),
]
Then, let us add the transmon with the above properties to the design.
# Create the pocket transmon qubit object `Q1`.
qubit = TransmonPocket(
design,
"Q1",
options=options_transmon,
)
Note
We could have chosen another transmon qubit template or created a custom device. For instance, if we were to consider the transmon pocket with six connection pads as in Qiskit Metal’s Full chip design example, we should have written
from qiskit_metal.qlibrary.qubits.transmon_pocket_6 import TransmonPocket6
# Define a dictionary with the parameters associated with the chosen pocket transmon.
# Values taken from
# https://github.com/qiskit-community/qiskit-metal/blob/main/docs/circuit-examples/full-design-flow-examples/Example-full-chip-design.ipynb
options_transmon = dict(
pad_width="425um",
pocket_height="650um",
connection_pads=dict(
readout=dict(loc_W=0, loc_H=-1, pad_width="80um", pad_gap="50um"),
bus_01=dict(loc_W=-1, loc_H=-1, pad_width="60um", pad_gap="10um"),
bus_02=dict(loc_W=-1, loc_H=+1, pad_width="60um", pad_gap="10um"),
bus_03=dict(loc_W=0, loc_H=+1, pad_width="90um", pad_gap="30um"),
bus_04=dict(loc_W=+1, loc_H=+1, pad_width="60um", pad_gap="10um"),
bus_05=dict(loc_W=+1, loc_H=-1, pad_width="60um", pad_gap="10um"),
),
)
# Define which coupling pads should have their ends open to ground.
open_pins = [
("Q1", "readout"),
("Q1", "bus_01"),
("Q1", "bus_02"),
("Q1", "bus_03"),
("Q1", "bus_04"),
("Q1", "bus_05"),
]
# Create the pocket transmon qubit object `Q1`, labelling the pads and the readout
# line.
qubit = TransmonPocket6(design, "Q1", options=options_transmon)
The remaining steps of this tutorial would remain the same.
1.5.3. Configuring the QTCAD renderer
Having defined our device, let us instantiate and configure our QTCAD renderer, that is, the capacitance 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,
capacitance=dict(
tol_rel=0.05,
tol_abs=0.0,
),
),
)
Here, the most relevant parameters are
capacitance["tol_rel"]andcapacitance["tol_abs"], the error thresholds used in adaptive mesh refinement for the capacitance solver. As explained in Capacitance extraction of a coaxial cable with adaptive meshing, the tolerance for an entry \(C_{ij}\) of the capacitance matrix is given bycapacitance["tol_rel"]× \(C_{ij}\) +capacitance["tol_abs"].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 capacitance extraction comprise the values of the capacitance matrix entries at each iteration, along with the size of the mesh.Lastly, as in Capacitance extraction of a coaxial cable with adaptive meshing, one could add the parameter
capacitance["min_converged_iters"](not defined above) to prevent the solver from terminating before the results have agreed between a given number of refinements.
1.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 render the Josephson junction, as its properties are usually given by the
# foundry and its capacitance can be considered afterwards easily.
skip_junctions=True,
# 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.
1.5.5. Running the simulation
Finally, let us export the simulation parameters and compute the capacitance matrix using QTCAD.
# Export parameters.
qtcad_renderer.export_parameters()
# Run QTCAD to extract the capacitance matrix.
qtcad_renderer.run_qtcad("cap")
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 capacitance matrix
into the file qtcad_output_cap.pickle.
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("cap", env_name="qtcad_sc").
To load the results, use the method load_qtcad_capacitance_matrix as
capacitance_matrix = qtcad_renderer.load_qtcad_capacitance_matrix()
print(capacitance_matrix)
The resulting Maxwell capacitance matrix is shown below:
Q1_coupler1_connector_pad Q1_coupler2_connector_pad Q1_readout_connector_pad Q1_pad_top Q1_pad_bot ground_plane
Q1_coupler1_connector_pad 50.541531 -0.366939 -0.190682 -13.713046 -1.440281 -35.089658
Q1_coupler2_connector_pad -0.366939 54.831942 -0.988277 -1.736123 -14.493555 -37.477397
Q1_readout_connector_pad -0.190682 -0.988277 60.713626 -2.107905 -19.655871 -38.220137
Q1_pad_top -13.713046 -1.736123 -2.107905 88.418387 -31.113635 -39.431221
Q1_pad_bot -1.440281 -14.493555 -19.655871 -31.113635 99.679584 -32.435846
ground_plane -35.089658 -37.477397 -38.220137 -39.431221 -32.435846 182.736076
With it, for instance, one could compute the effective capacitance of the transmon as to obtain its charging energy, usually denoted by \(E_C\), as part of a lumped-oscillator model (LOM) analysis.
1.6. Full code
__copyright__ = "Copyright 2022-2025, Nanoacademic Technologies Inc."
"""
Capacitance matrix extraction for a transmon qubit designed using Qiskit
Metal.
"""
from pathlib import Path
# Import Qiskit Metal.
from qiskit_metal import designs
from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket
# Import the QTCAD renderer.
from qiskit_metal.renderers.renderer_qtcad.qtcad_renderer import QQTCADRenderer
# Directories and file paths.
device_name = "qiskit_metal-pocket-cap"
# 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 = "150um"
# Instantiate a design object.
design = designs.MultiPlanar({}, True)
# Define a dictionary with the parameters associated with the chosen pocket
# transmon qubit. Values taken from
# https://qiskit.org/ecosystem/metal/tut/4-Analysis/4.01-Capacitance-and-LOM.html
options_transmon = dict(
pad_gap="30um",
pad_width="425um",
pad_height="90um",
pocket_width="650um",
pocket_height="650um",
)
# Get some default values. Note that, if we were to omit these entries in the
# dictionary defining a coupling pad, Qiskit Metal would already use these
# variables. We do it here explicitly for completeness.
cpw_width = design.variables["cpw_width"]
cpw_gap = design.variables["cpw_gap"]
# Properties common to all coupling pads.
options_coupler_common = dict(
pad_gap="15um",
pad_cpw_shift="5um",
pad_cpw_extent="25um",
cpw_width=cpw_width,
cpw_gap=cpw_gap,
cpw_extend="100um",
pocket_extent="5um",
pocket_rise="65um",
)
# Include the coupling pads’ properties into the dictionary with the transmon’s
# properties.
options_couplers = dict(
coupler1=dict(
loc_W=-1,
loc_H=+1,
pad_width="125um",
pad_height="30um",
**options_coupler_common,
),
coupler2=dict(
loc_W=-1,
loc_H=-1,
pad_width="125um",
pad_height="50um",
**options_coupler_common,
),
readout=dict(
loc_W=+1,
loc_H=-1,
pad_width="200um",
pad_height="30um",
**options_coupler_common,
),
)
options_transmon["connection_pads"] = options_couplers
# Lastly, define which coupling pads should have their ends open to ground.
open_pins = [
("Q1", "coupler1"),
("Q1", "coupler2"),
("Q1", "readout"),
]
# Create the pocket transmon qubit object `Q1`.
qubit = TransmonPocket(
design,
"Q1",
options=options_transmon,
)
# 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,
capacitance=dict(
tol_rel=0.05,
tol_abs=0.0,
),
),
)
# Create the geometry from the design.
qtcad_renderer.render_design(
open_pins=open_pins,
# Do not render the Josephson junction, as its properties are usually given by the
# foundry and its capacitance can be considered afterwards easily.
skip_junctions=True,
# 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 capacitance matrix.
qtcad_renderer.run_qtcad("cap")
capacitance_matrix = qtcad_renderer.load_qtcad_capacitance_matrix()
print(capacitance_matrix)