Creating a new node

This tutorial is about how to create a new node class. The word node is an overloaded term, used in many contexts, so, what does a node mean here? If you put all steps to characterize a qubit in a chain, then it could be seen as a directed graph with the calibration steps as nodes. Take a look at the node overview to get an impression on how it looks like. In this guide, we will cover the topics:

  1. File locations for nodes within the framework
  2. How to implement a node
  3. Register the node in the framework

Following these steps, you can easily contribute to the automatic calibration with your own nodes in a couple of hours.

Where are the nodes located?

The nodes are located in tergite_autocalibration/lib/nodes. If you open that module, you will find that there are four submodules for different kind of nodes:

  • characterization: For nodes such as the T1, T2 or the randomized benchmarking.
  • coupler: For all nodes that are related to a two-qubit setup with a coupler.
  • qubit_control: All nodes that calibrate a quantity of interest for the qubit.
  • readout: All nodes that calibrate a quantity of interest for the resonator.

Please create a new submodule for your node in one of the four submodules listed above. A sort of template on how a node module should be structured can be found in tergite_autocalibration/lib/base. Essentially, a proper package in the end should contain:

  • __init__.py: This is an empty file to mark that the folder is a package. Please add this file, because otherwise your classes cannot be found.
  • node.py: A file where the definition of the node class goes.
  • analysis.py: A file where the analysis object is defined.
  • measurement.py: Contains the measurement object with the schedule.
  • tests: A folder with all test function and test data. Read more about unit tests to find out on how to structure them.
  • utils: A folder in case you have defined very specific helper classes.

Before we are going to a look on how this would be implemented in detail, a quick note on naming conventions.

Naming conventions

Since we are creating a lot of node, measurement and analysis objects, there are some naming conventions to make it more standardized and understandable to learn the framework. Taking the rabi oscillations as an example, we have:

  • rabi_oscillations: This is the node name, which is used in the commandline interface or for other purposes to pass the node name as a string.
  • RabiOscillations: This is the name of the node class.
  • RabiOscillationsAnalysis: The name of the respective analysis class.
  • RabiOscillationsMeasurement: And the name of the respective measurement class.

When there are some more complicated nodes for which you do not know how to name it, please just take a look at the already existing nodes and make a guess how it feels to name it correctly. Also, when there is a node that starts with an abbreviation, please have all letter for the abbreviation capitalized e.g.: cz_calibration would be the node name for the CZCalibration node class.

Node implementation details

All node classes are supposed follow the same interface as described in the BaseNodeclass. Below, for example, you have the rabi oscillations node:

class RabiOscillations(ScheduleNode):
  measurement_obj = RabiOscillationsMeasurement
  analysis_obj = RabiOscillationsAnalysis
  qubit_qois = ["rxy:amp180"]

  def __init__(self, name: str, all_qubits: list[str], **schedule_keywords):
    super().__init__(name, all_qubits, **schedule_keywords)
        self.schedule_samplespace = {
          "mw_amplitudes": {
            qubit: np.linspace(0.002, 0.90, 61) for qubit in self.all_qubits
            }
        }

As you can see, it inherits from the ScheduleNode class, which contains a very simple definition of a node that runs a simple sweep over a single quantity. More information about other node classes can be found below. Furthermore, you can see that the node has three class attributes:

  • measurement_obj: Contains the class of the measurement, that defines the pulse schedule for the instruments
  • analysis_obj: Contains the class for the analysis to post-process the measurement results. For example, this could be a simple fitting function.
  • qubit_qois: The quantity of interest (QOI), which is returned by the analysis. In case of the rabi oscillations this is the pulse amplitude.

Also, you can see in the constructor, there is an attribute called schedule_samplespace. Here, we define in the measurement, what quantity will be swept over.

Creating a measurement_obj

The measurement_obj is implemented in the measurement.py file of your node submodule. To initialize we require a dictionary of the extended transmons:

transmons: dict[str, ExtendedTransmon]

It must contain a method called schedule_function that expects the node’s samplespace as input and returns the complete schedule.

Creating an analysis_obj

The analysis_obj is implemented in the analysis.py file from your module and contains the class that perform the analysis.

Node types and samplespaces

In the example above, the node inherits from the class ScheduleNode. This is one option for the node behaviour:

  • ScheduleNode: A node with a simple sweep over the samplespace. The quantify schedule is only compiled once and the parameter values from the samplespace are the input for the schedule function of the measurement.
  • ExternalParameterNode: A node with a more complex measurement procedure. It allows to run multiple steps sequentially, where each step might require recompilation of the schedule. There is an external parameter involved, which is the part of the generating function within the schedule.

When you are implementing a node, you can choose which of the two abstract node classes fit better with the behaviour of your new node. Also, if you want to implement a more sophisticated measurement procedure, you can override the procedures in the measurement function or in other places. Check out the article about node classes for more details.

Register the node in the framework

To add the node to the framework, you have to register it in two places - the node factory and the calibration graph. Also, please do not forget to write documentation for your node.

Node factory

A factory is a programming pattern to create complex objects such as our nodes in a bit more clearly interfaced way. The factory contains a register for all nodes, that map their name to the respective class where the node is implemented. When you are adding a node, please register your node name, by adding it to the tergite_autocalibration.lib.utils.node_factory.NodeFactory class under the self.node_name_mapping attribute in the dictionary.

Calibration graph

In the file tergite_autocalibration/lib/utils/graph.py in the list graph_dependencies insert the edges that describe the position of the new node in the Directed Acyclic Graph. There are two entries required (or one entry if the new node is the last on its path):

  • ('previous_node','new_node')
  • ('new_node', 'next_node')

Documentation

Please add your node to the list of available nodes in this documentation. If possible, please create a separate page that explains what your node is doing and link it there.

Add any relevant information on how to use your node, dependencies and reference to publication as needed for allowing other to use the code you developed.

Details on the implementation on the Node types section.