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:
- File locations for nodes within the framework
- How to implement a node
- 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 BaseNode
class.
Below, for example, you have the rabi oscillations node:
class RabiOscillations(ScheduleNode):
= RabiOscillationsMeasurement
measurement_obj = RabiOscillationsAnalysis
analysis_obj = ["rxy:amp180"]
qubit_qois
def __init__(self, name: str, all_qubits: list[str], **schedule_keywords):
super().__init__(name, all_qubits, **schedule_keywords)
self.schedule_samplespace = {
"mw_amplitudes": {
0.002, 0.90, 61) for qubit in self.all_qubits
qubit: np.linspace(
} }
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 instrumentsanalysis_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.