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:

Design of the meandered resonator device.

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_junction the junction’s length and inductance, with the width being automatically obtained from the qubit geometry.

  • Also, it returns pandas.DataFrame with 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_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.

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_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("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()
Electrical field distribution associated to the first two eigenmodes of the resonator-coupled Xmon system.

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=True to plot_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 call qtcad_renderer.plot_eigenmode(n=1) and qtcad_renderer.plot_eigenmode(n=2).

    Electrical field distribution associated to the first eigenmode of the resonator-coupled Xmon system.

    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.

    Electrical field distribution associated to the second eigenmode of the resonator-coupled Xmon system.

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