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:

Design of the pocket transmon qubit, with the different components labelled.

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:

Main parameters associated to the pocket transmon overlaid over the current design.

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.

Main parameters associated to the coupling pads of pocket transmons overlaid over the current design.

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"] and capacitance["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 by capacitance["tol_rel"] × \(C_{ij}\) + capacitance["tol_abs"].

  • 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 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_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.

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_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("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)