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 focussed on the design of superconducting quantum chips and devices, facilitates the process of creating transmon qubit designs, as opposed to the pure Gmsh workflow of Capacitance extraction of a coaxial cable, for instance. Several pre-defined classes of transmons qubits are available from Qiskit Metal’s QComponent library, with several properties – such as the size and gap of the pads – being customizable and methods to create custom designs also being available. While in principle Gmsh scripts could be used to create arbitrary 3D geometries, in practice, this can be time consuming and present and important barrier to entry for new users. For more information on Qiskit Metal, please take a look at Qiskit Metal’s documentation.

Note

When running Qiskit Metal simulations, make sure the conda environment where Qiskit Metal is set up, vide Additional installation instructions for Qiskit Metal on Linux and Windows, is active.

1.3. Mesh generation

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

Note

We leverage the Gmsh Qiskit Metal renderer

to create meshes, so any device design supported by it shall work with the QTCAD renderer.

1.4. Geometry of the problem

For this tutorial, in specific, we will consider a standard ‘pocket’ transmon qubit with a ground plane and two pads connected by a Josephson junction, vide figure below:

Design of the pocket transmon qubit.

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 = "pocket"
# 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 initial (coarse) mesh.

# Whether to calculate the capacitance matrix using adaptive meshing.
adaptive = True
# Set minimum and maximum characteristic lengths based on mesh type.
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. Loading the initial mesh and defining 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’.

# 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
transmon_options = dict(
    pad_width="425um",
    pocket_height="650um",
    connection_pads=dict(
        coupler1=dict(loc_W=-1, loc_H=+1, pad_height="30um"),
        coupler2=dict(loc_W=-1, loc_H=-1, pad_height="50um"),
        readout=dict(loc_W=+1, loc_H=-1, pad_width="200um"),
    ),
)

# Create the pocket transmon qubit object `Q_MAIN`, labelling the pads and the readout
# line.
q_main = TransmonPocket(
    design, "Q_MAIN", options=transmon_options,
)
open_pins = [
    ("Q_MAIN", "coupler1"),
    ("Q_MAIN", "coupler2"),
    ("Q_MAIN", "readout"),
]

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
transmon_options = dict(
    pos_x="0mm",
    pos_y="-1mm",
    gds_cell_name="FakeJunction_01",
    hfss_inductance="14nH",
    pad_width="425 um",
    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"),
    ),
)

# Create the pocket transmon qubit object `Q_MAIN`, labelling the pads and the readout
# line.
q_main = TransmonPocket6(design, "Q_MAIN", options=transmon_options)
open_pins = [
    ("Q_MAIN", "readout"),
    ("Q_MAIN", "bus_01"),
    ("Q_MAIN", "bus_02"),
    ("Q_MAIN", "bus_03"),
    ("Q_MAIN", "bus_04"),
    ("Q_MAIN", "bus_05"),
]

(The remaining steps of the 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 capacitance is negligible.
    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, let us 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 mesh.
qtcad_renderer.export_mesh(
    mesh_file=mesh_filepath, geometry_file=geo_filepath
)

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.

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, which will output 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 capacitance matrix is shown below:

                               Q_MAIN_coupler1_connector_pad  Q_MAIN_coupler2_connector_pad  Q_MAIN_readout_connector_pad  Q_MAIN_pad_top  Q_MAIN_pad_bot  ground_plane
Q_MAIN_coupler1_connector_pad                      50.458734                      -0.365827                     -0.190329      -13.702512       -1.437154    -35.028466
Q_MAIN_coupler2_connector_pad                      -0.365827                      54.744634                     -0.985226       -1.734478      -14.486536    -37.401344
Q_MAIN_readout_connector_pad                       -0.190329                      -0.985226                     60.789908       -2.104326      -19.632415    -38.249972
Q_MAIN_pad_top                                    -13.702512                      -1.734478                     -2.104326       88.369382      -31.041107    -39.375577
Q_MAIN_pad_bot                                     -1.437154                     -14.486536                    -19.632415      -31.041107       99.422612    -32.404807
ground_plane                                      -35.028466                     -37.401344                    -38.249972      -39.375577      -32.404807    182.494882

With it, one can now perform, for instance, 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 = "pocket"
# 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 based on mesh type.
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
transmon_options = dict(
    pad_width="425um",
    pocket_height="650um",
    connection_pads=dict(
        coupler1=dict(loc_W=-1, loc_H=+1, pad_height="30um"),
        coupler2=dict(loc_W=-1, loc_H=-1, pad_height="50um"),
        readout=dict(loc_W=+1, loc_H=-1, pad_width="200um"),
    ),
)

# Create the pocket transmon qubit object `Q_MAIN`, labelling the pads and the readout
# line.
q_main = TransmonPocket(
    design, "Q_MAIN", options=transmon_options,
)
open_pins = [
    ("Q_MAIN", "coupler1"),
    ("Q_MAIN", "coupler2"),
    ("Q_MAIN", "readout"),
]

# 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 capacitance is negligible.
    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 mesh.
qtcad_renderer.export_mesh(
    mesh_file=mesh_filepath, geometry_file=geo_filepath
)


# 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)