Welcome to qc-toolkit’s documentation!¶
Contents:
Concepts¶
This section will explain the fundamental design concepts of the qctoolkit.
Pulse Templates¶
The qctoolkit represents pulses as abstract pulse templates. A pulse template can be understood as a class of pulses that share a similar structure but differ in the concrete amplitude or duration of voltage levels. To this end, pulse templates are parametrizable. Pulse templates are also designed to feature easy reusability of existing templates and conditional execution based on hardware triggers, if supported by the devices.
There are 6 types of different pulse template classes, briefly explained in the following. TablePulseTemplate
and FunctionPulseTemplate
are used to define the atomic building blocks of pulses in the following ways: TablePulseTemplate
allows the user to specify pairs of time and voltage values and choose an interpolation strategy between neighbouring points. FunctionPulseTemplate
will accept any mathematical function that maps time to voltage values. All other pulse template variants are then used to construct arbitrarily complex pulses by combining existing ones into new structures: SequencePulseTemplate
enables the user to specify a sequence of existing pulse templates (subtemplates) and modify parameter values using a mapping function. RepetitionPulseTemplate
is used to simply repeat one existing pulse template a given (constant) number of times. BranchPulseTemplate
and LoopPulseTemplate
implement conditional execution if supported. All of these pulse template variants can be similarly accessed through the common interface declared by PulseTemplate
. [1] [2]
Each pulse template can be stored persistently in a human-readable JSON file. Read more about serialization.
Parameters¶
As mentioned above, all pulse templates may contain parameters. TablePulseTemplate
allows parameter references as table entries on the time and voltage domain. These are represented as ParameterDeclaration
objects which are identified by a unique name and can impose lower and upper boundaries to the expected parameter value as well as a default value. SequencePulseTemplate
allows to specify a set of new parameter declarations and a mapping of these to the parameter declarations of its subtemplates. This allows renaming of parameters, e.g., to avoid name clashes if several subtemplates declare similarly named parameters. The mapping also allows mathematical transformation of parameter values, such that values that are passed to subtemplates can be obtained by deriving them from one or more other parameter values passed to the SequencePulseTemplate
. RepetitionPulseTemplate
, LoopPulseTemplate
and BranchPulseTemplate
will simply pass parameters to their subtemplates without modifying them in any way.
The mathematical expressions (for parameter transformation or as the function of the FunctionPulseTemplate
) are encapsulated into an Expression
class which wraps existing python packages that are able to parse and evaluate expressions given as strings such as py_expression_eval and numexpr.
Obtaining a Concrete Pulse¶
To obtain a pulse ready for execution on the hardware from a pulse template, the user has to specify parameter values (if parameters were used in the pulse templates in question). In the simplest case, parameters are constant values that can be provided as plain float values. Other cases may require parameter values to be computed based on some measurement values obtained during preceding executions. If so, a subclass of the Parameter
class which performs this computations when queried for a value can be provided. In order to translate the object structures that encode the pulse template in the software into a sequential representation of the concrete pulse with the given parameter values that is understandable by the hardware, the sequencing process has to be invoked. During this process, all parameter values are checked for consistency with the boundaries declared by the parameter declarations and the process is aborted if any violation occurs. Read more about the sequencing process.
Relevant Examples¶
Examples demonstrating the use of pulse templates and parameters are Modelling a Simple TablePulseTemplate, Combining PulseTemplates Using SequencePulseTemplate and Modelling Pulses Using Functions And Expressions.
Footnotes
[1] | Regarded as objects in the programming language, each pulse template is a tree of PulseTemplate objects, where the atomic templates (TablePulseTemplate and FunctionPulseTemplate objects) are the leafs while the remaining ones form the inner nodes of the tree. |
[2] | The design of the pulse template class hierarchy is an application of the Composite Pattern. |
Serialization¶
Serialization and deserilization mechanisms were implemented to enable persistent storage and thus reusability of pulse template definitions. Currently, the serialization format is a plain text document containing JSON formatted data. [1]
Serialization was constructed in a way that allows that a given pulse template may refer to subtemplates which are used by several different parent templates (or more than once in one) such as, e.g., the measurement pulse. These should then be stored in a separate file and referenced by a unique identifier in all parent templates to avoid unnecessary redundancy. On the other hand, there might be subtemplates which are only relevant to their parent and thus should be embedded in its serialization to avoid creating a multitude of files that are meaningless to a user. To allow the serialization process to make this distinction, each pulse template (or other serializable object) provides an optional identifier (which can be set by the user via the constructor for all pulse template variants). If an identifier is present in a pulse template, it is stored in a separate file. If not, it is embedded in its parent’s serialization.
The implementation of (de)serialization features Serializer
class and a Serializable
interface. Every class that implements the latter can be serialized and thus stored as a JSON file. Currently, this is the case for all PulseTemplate
variants as well as the ParameterDeclaration
class. Additionally, the StorageBackend
interface abstracts the actual storage backend. While currently there only exists a single implementation of this interface, namely the FileSystemStorageBackend
, this allows to support, e.g., database storage, in the future.
The essential methods of Serializer
are Serializer.serialize()
and Serializer.deserialize()
. Serializer.serialize()
serializes a serializable object (i.e., any object of a class that implements/derives from Serializable
) in a recursive process: It invokes the Serializable.get_serialization_data()
method of the provided serializable object, which in turn might invoke the Sequencer
to obtain a serialization for complex embedded data (such as a ParameterDeclaration
in a TablePulseTemplate
). In the end, a dictionary representation of the object is obtained which is then converted into a JSON string using built-in Python functionality. The JSON representation is finally stored using a given StorageBackend
. Deserialization (Serializer.deserialize()
) works analogously and is thus not explained in detail here.
For an example of how to use serialization to store and load pulse templates, see Storing Pulse Templates: Serialization in the examples section.
Implementing a Serializable
Class¶
To make any new class serializable, it must derive from the Serializable
and implement the methods Serializable.get_serialization_data()
, Serializable.deserialize()
and the Serializable.identifier
property.
If class objects should be stored in a separate file, the identifier must be a non-empty string. If, on the other hand, class objects should be embedded into their parent’s serialization (as is the case for, e.g., ParameterDeclaration
), identifier must be None.
The method serialize should return a dictionary of native Python types containing all relevant data. If the class has members that are not native Python types but must be serialized, they must be serializable and the serialize method can obtain their serialization as the return value of Serializer._serialize_subpulse()
and embed it in its result. The dictionary returned by serialize should not include the identifier in the returned dictionary.
The method deserialize is invoked with all key-value pairs created by a call to serialize as keyword arguments as well as an additional identifier keyword argument (which may be None) and must return a valid corresponding class instance.
The following may serve as a simple example:
from qctoolkit.serialization import Serializable, Serializer
from qctoolkit.pulses import PulseTemplate
from typing import Any, Dict, Optional
class Foo(Serializable):
def __init__(self, template: PulseTemplate, identifier: Optional[str]=None) -> None:
self.__template = template
self.__identifier = identifier
@property
def identifier(self) -> Optional[str]:
return self.__identifier
def get_serialization_data(self, serializer: Serializer) -> Dict[str, Any]:
return dict(template=serializer._serialize_subpulse(self.__template))
@staticmethod
def deserialize(serializer: Serializer, template: Dict[str, Any], identifer: Optional[str]=None) -> Serializable:
return Foo(serialzer.deserialize(template), identifier=identifier)
Footnotes
[1] | After some discussion of the format in which to store the data, JSON files were the favored solution. The main competitor were relational SQL databases, which could provide a central, globally accessible pulse database. However, since pulses are often changed between experiments, a more flexible solution that can be maintained by users without database experience and also allows changes only in a local environment was desired. Storing pulse templates in files was the obvious solution to this. This greatest-simplicity-requirement was also imposed on the data format, which thus resulted in JSON being chosen over XML or other similar formats. An additional favorable argument for JSON is the fact that Python already provides methods that convert dictionaries containing only native python types into valid JSON and back. |
Conditional Branching¶
The qctoolkit was designed to model conditional branching in pulse execution using the classes BranchPulseTemplate
and LoopPulseTemplate
of the pulse template definition classes. The software is intended to support use of trigger-based hardware jumps between waveforms as well as software-based evaluation of branching conditions, if the hardware does not support triggers or highly sophisticated evaluation of measured data is required. These options are represented as instances of the Condition
class: BranchPulseTemplate
and LoopPulseTemplate
both contain unique identifiers (chosen by the user) of a condition. In the beginning of the sequencing process, the user provides a dictionary of Condition
instances which are used to determine how the conditions for each template are evaluated. Currently, two different subclasses of Condition
exist: SoftwareCondition
represents a software-evaluated condition and accepts a function reference to any evaluation function which must return a boolean value indicating whether or not the condition is fulfilled or None, if it cannot yet be evaluated; HardwareCondition
represents trigger-based hardware evaluation and basically stores a reference to a trigger which will later be evaluated by the specific hardware driver to set up the device accordingly. Note that software-based evaluation will interrupt the sequencing process and thus the execution of the pulse if the evaluation cannot be performed during the sequencing run, e.g., if the evaluation of the condition is based on measurement data made during pulse execution. In this case, the pulse instruction sequence is only generated up to the branching and must be re-invoked later on, after executing this first part and obtaining the required data. Software-based evaluation is thus not feasible when high performance is required.
Future Work¶
Currently, there is no detailed concept on hardware abstraction and thus no meaningful representation of triggers and no hardware driver implementation that configures any device. This is still an open task.
It is quite common for hardware to allow triggers that are represented not only a boolean signal but, e.g., any 8-bit signal, thus enabling more than two branching options. While this could still be represented by the current classes by nesting BranchPulseTemplate
objects and configuring triggers appropriately, the implementation of a template class which acts like a C-style switch statement might be a worthwhile consideration.
Sequencing¶
Overview and Usage¶
Defining pulses using the pulse template definition class structures yields a tree structure of PulseTemplate
objects. To obtain a concrete pulse that can be executed, parameters have to be replaced by corresponding values and the object structure has to be converted into a sequence of waveforms (and possibly triggered jump annotations) that the hardware drivers can comprehend. This process is called sequencing in the qctoolkit. It converts TablePulseTemplate
and FunctionPulseTemplate
objects into a waveform representation by sampling voltage values along the time domain using the specified interpolation rules between table values or evaluating the defining function respectively. The tree structure arising from the use of SequencePulseTemplate
, RepetitionPulseTemplate
, BranchPulseTemplate
and LoopPulseTemplate
is converted into an intermediate instruction language consisting of four instructions: Execute a waveform (EXECInstruction
), an unconditional goto to another instruction (GOTOInstruction
), a conditional jump to another instruction (JMPInstruction
) and a stop command, halting execution (STOPInstruction
).
This approach was inspired by translation of syntax trees to machine instructions in modern compilers and necessitated by the fact that the hardware requires sequential commands rather than convoluted object structures. The output of the sequencing process is a set of waveforms and a sequence of instructions. These will later be interpreted by hardware drivers which will configure the specific devices accordingly (assuming these are capable of the required functionality). If BranchPulseTemplate
and LoopPulseTemplate
are not used, the compiled instruction sequence will consist only of execution instructions followed by a stop instruction, which represents a simple sequence of waveforms to play back.
The sequencing process is performed by a Sequencer
and started with a call to Sequencer.build()
. Sequencer
maintains a stack of SequencingElement
objects (i.e. pulse templates) that still need to be translated. Pushing a pulse template to the stack using Sequencer.push()
will cause Sequencer
to translate it next. Translation works as follows: Sequencer
pops the first element from the stack and calls its SequencingElement.build_sequence()
method. In the case of a TablePulseTemplate
, this adds an EXECInstruction
referring to a TableWaveform
to the instruction sequence. In the case of a SequencePulseTemplate
, this simply adds all subtemplates to the sequencing stack such that they are translated next. FunctionPulseTemplate
and RepetitionPulseTemplate
act very similarly. BranchPulseTemplate
and LoopPulseTemplate
behave in a more complicated way due to their branching ability. See the section on implementation details below.
The sequencing process can be interrupted at any point, e.g., if some parameter value depends on measurements that are to be made in the preceding part of the pulse. In this case, the method SequencingElement.requires_stop()
of the first stack element will return true. Sequencer
then stops the translation and returns the instruction sequence generated so far. This can then be executed and measurements can be made. Afterwards, the sequencing process can be invoked again. Sequencer
will resume where it was interrupted previously (with the first item that remained on the stack). Sequencer.has_finished()
allows to check, whether the sequencing process was completed.
Sequencing of Conditional Branching¶
Software and hardware conditions result in different instruction sequences generated by the sequencing process: Hardware conditions will produce instructions for all possible branches with branching instructions corresponding to the triggers specified by the HardwareCondition
instance. The selection of the correct branch is then made by the hardware. Contrary, software conditions will only produce instructions for the branch selected by the condition (the hardware will then never know of potential other execution paths). In this case, no branching instructions will be used. This enables usage of branching even on hardware that does not support jumps with the disadvantage of being not real-time capable (cf. Read more on conditional branching.).
Implementation Details and Algorithm Walkthrough¶
The implementation sequencing algorithm itself is spread over all participating classes. The Sequencer
maintains a stack of pulse templates which are to be translated (the sequencing stack) and implements the overall control flow of the algorithm: It proceeds to remove the first template from the stack and call PulseTemplate.build_sequence()
until either the stack is empty or the first element cannot be translated. Afterwards it embeds all InstructionBlock
s into one single InstructionSequence
and resolves pointers in JMPInstruction
s. Details on why this is necessary will follow below.
A crucial component of the sequencing process are the methods PulseTemplate.requires_stop()
and PulseTemplate.build_sequence()
(defined in the abstract interface SequencingElement
). These accept a mapping of parameters and conditions as well as the InstructionBlock
that is currently under construction and implement the specific functionality of the corresponding template required in the sequencing process. Examples of this were already briefly stated above but shall be elaborated more in the following.
Considering the TablePulseTemplate
, the required result of the sequencing process is a waveform representation of the pulse defined by the template and an instruction that this waveform shall be executed by the hardware. If any parameter cannot be evaluated at the time, the sequencing process should stop since the waveform cannot be created. Consequently, TablePulseTemplate.requires_stop()
is implemented to return true if any of the parameters required by the template cannot be evaluated, causing the Sequencer
to interrupt the sequencing process before calling TablePulseTemplate.build_sequence()
. TablePulseTemplate.build_sequence()
, evaluates the required parameters, creates the waveform and adds a corresponding EXECInstruction
to the InstructionBlock
. Since conditions are only relevant for branching, the corresponding mapping passed into the method is ignored in the TablePulseTemplate
implementation.
For a SequencePulseTemplate
we require that all templates forming this sequence will be translated, i.e., the translation of the SequencePulseTemplate
is the sequential translation of all contained subtemplates. To this end, SequencePulseTemplate.build_sequence()
does not perform any translation by itself but simply pushes all subtemplates to the sequencing stack, only taking care to map parameters if necessary. Conditions are ignored as in the case of TablePulseTemplate
. Since SequencePulseTemplate
does not need to evaluate any parameters by itself, SequencePulseTemplate.requires_stop()
always returns false.
Finally, for a LoopPulseTemplate
we expect one of the following behaviours depending on whether the looping condition is evaluated hardware- or software-based: In the first case, the template representing the loop body should be translated and wrapped with conditional jumping instructions which will cause the hardware device to repeat the resulting waveforms as long as the condition holds. In the second case, the condition must be evaluated by the software and, if it is true, the loop body must be translated and executed in-place, i.e. without any jumps. Afterwards, the execution must stop to reevaluate the condition in the software and decide whether the loop body must be executed again. Since these different behaviours for hardware- and software-based evaluation are similar for loops and branches, they are not directly implemented in the LoopPulseTemplate
and BranchPulseTemplate
classes but in the corresponding implementations of Condition
. The task of LoopPulseTemplate.build_sequence()
is thus only to obtain the correct instance of Condition
from the provided conditions mapping and delegate the call to Condition.build_sequence_loop()
. Depending on whether the condition is a HardwareCondition
or SoftwareCondition
instance, Condition.build_sequence_loop()
will push the required templates to the sequencing stack and generate the corresponding jumping instructions.
During the sequencing process, instructions are not directly stored in a fixed sequence. This would be impractical since during the process it is not clear how many alternative paths due to branching and looping will arise and thus where to place the corresponding instructions in a single sequence. Instead, each branch or loop body is represented by an InstructionBlock
comprising all execution instructions in this branch as well as potential jump instructions into other blocks. This allows independent construction of blocks during the sequencing process. The Sequencer
maintains separate sequencing stacks for each block which allows to continue translating other blocks if a particular block encounters a template which requires a stop. The block into which instructions have to be placed is passed as an argument into PulseTemplate.build_sequence()
. With the exception of the main instruction block, which represents the entry point of instruction execution, every block is entered by a conditional jump instruction in another block and ends with an unconditional jump/goto back to the next instruction of that block. When the sequencing process finished, Sequencer
embeds all instruction blocks into a single sequence and resolves the instruction pointers of the jump instructions to positions in this sequence instead of references to other blocks. The result is a sequence of instructions represented as InstructionSequence
in which no blocks remain and executions paths are solely represented by jumps to instruction positions.
For two step-by-step examples on how the sequencing process proceeds, see Detailed Sequencing Walkthrough.
Note
Provide an exemplary sequencing run
Examples¶
All examples are provided as static text in this documentation and, additionally, as interactive jupyter notebooks accessible by running jupyter notebook in the /doc/source/examples directory of the source tree.
Modelling a Simple TablePulseTemplate¶
This example demonstrates how to set up a simple TablePulseTemplate.

The pulse we want to model using the qctoolkit
Assume we want to model a pulse as depicted by the figure above. Since
the structure is relatively simple and relies only on a few critical
points between which the values are interpolated (indicated by the
crosses in the figure), we will do so by using a TablePulseTemplate
and setting values at appropriate times. First, let us instantiate a
TablePulseTemplate
object:
In [2]:
from qctoolkit.pulses import TablePulseTemplate
template = TablePulseTemplate(identifier='foo')
For our first try, let’s just fix some values for the parameters. Let us
set \(t_a\) = 2, \(v_a\) = 2, \(t_b\) = 4, \(v_b\) = 3,
\(t_{end}\) = 6. Our pulse then holds a value of 0 for 2 units of
time, then jumps to 2 and subsequently ramps to 3 over the next 2 units
of time. Finally, it returns to holding 0 for another 2 units of time.
The supporting points for the table are thus (0,0), (2,2), (4,3), (6,0).
We add these to our TablePulseTemplate
object template
with the
correct interpolation strategies as follows:
In [2]:
template.add_entry(0, 0)
template.add_entry(2, 2, interpolation='hold')
template.add_entry(4, 3, interpolation='linear')
template.add_entry(6, 0, interpolation='jump')
Note that we could omit the first instruction: If the time value of the
first call to add_entry
is greater than zero, a starting entry (0,0)
is automatically set. Note further that the interpolation set for an
entry always applies to the range from the previous entry to the new
entry. Thus, the value for the first entry is always ignored. The
default value for interpolation is ‘hold’. ‘hold’ and ‘jump’ differ in
that ‘hold’ will hold the previous value until the new entry is reached
while ‘jump’ will immediately assume the value of the new entry.
We plot template
to see if everything is correct (ignore the
matplotlib warning here):
In [5]:
%matplotlib inline
from qctoolkit.pulses import plot
plot(template, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

Alright, we got what we wanted.
Note that the time domain in pulse defintions does not correspond to any fixed real world time unit. The mapping from a single time unit in a pulse definition to real time in execution is made by setting a sample rate when converting the pulse templates to waveforms. For more on this, see The Sequencing Process: Obtaining Pulse Instances From Pulse Templates.
Introducing Parameters¶
Now we want to make the template parameterizable. This allows us to
reuse the template for pulses with similar structure. Say we would like
to have the same pulse, but the intermediate linear interpolation part
should last 4 units of time instead of only 2. Instead of creating
another template with hardcoded values, we instruct the
TablePulseTemplate
instance to rely on parameters.
In [6]:
param_template = TablePulseTemplate()
param_template.add_entry('ta', 'va', interpolation='hold')
param_template.add_entry('tb', 'vb', interpolation='linear')
param_template.add_entry('tend', 0, interpolation='jump')
Instead of using numerical values, we simply insert parameter names in
our calls to add_entry
. You can use any combination of numerical
values or parameters names here. Note that we also gave our object the
optional identifier ‘foo’. Our param_template
thus now defines a set
of parameters.
In [7]:
print(param_template.parameter_names)
{'va', 'ta', 'tend', 'vb', 'tb'}
We now have a pulse template that we can instantiate with different parameter values, which we simply provide as a dictionary. To achieve the same pulse as above:
In [8]:
%matplotlib inline
parameters = {'ta': 2,
'va': 2,
'tb': 4,
'vb': 3,
'tend': 6}
plot(param_template, parameters, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

To instantiate the pulse with longer intermediate linear interpolation
we now simply adjust the parameter values without touching the
param_template
itself:
In [9]:
%matplotlib inline
parameters = {'ta': 2,
'va': 2,
'tb': 6,
'vb': 3,
'tend': 8}
plot(param_template, parameters, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

Combining PulseTemplates Using SequencePulseTemplate¶
In this example we will use the SequencePulseTemplate
class to build
a sequence of two of the simple table pulses defined in Modelling a
Simple TablePulseTemplate. We will also
map the parameters such that we can individually adapt values in either
of the two subtemplates.
We start with the defintion of the TablePulseTemplate
:
In [1]:
from qctoolkit.pulses import TablePulseTemplate
template = TablePulseTemplate(identifier='foo')
template.add_entry('ta', 'va', interpolation='hold')
template.add_entry('tb', 'vb', interpolation='linear')
template.add_entry('tend', 0, interpolation='jump')
Constructing a sequence of two of these requires us to set up a
SequencePulseTemplate
instance which expects a list of subtemplates,
a set of external parameters and corresponding parameters mappings:
In [2]:
from qctoolkit.pulses import SequencePulseTemplate
external_parameters = ['ta', 'tb', 'tc', 'td', 'va', 'vb', 'tend']
first_mapping = {
'ta': 'ta',
'tb': 'tb',
'va': 'va',
'vb': 'vb',
'tend': 'tend'
}
second_mapping = {
'ta': 'tc',
'tb': 'td',
'va': 'vb',
'vb': 'va + vb',
'tend': '2 * tend'
}
sequence = SequencePulseTemplate([(template, first_mapping),
(template, second_mapping)],
external_parameters)
Our sequence now exposes the parameters declared in the
external_parameters
set:
In [3]:
print(sequence.parameter_names)
frozenset({'td', 'tc', 'ta', 'vb', 'va', 'tb', 'tend'})
The mappings are constructed such that the first occurance of our table template will receive its parameters without any modification. For the second, however, we renamed the parameters: The ‘tc’ parameter of the sequence is mapped to the ‘ta’ parameter of the table template instance; ‘td’ is mapped to ‘tb’ of the subtemplate, ‘vb’ (of the sequence template) to ‘va’ (of the subtemplate). The value for ‘vb’ of the subtemplate is computed as the sum of the values of ‘va’ and ‘vb’ passed to the sequence and the value for ‘tend’ is double before passing it on. We can do a variety of transformations here, allowing us to modify how parameters are handled to add additional computations in composed pulse templates or simply to avoid name collisions between subtemplate parameters.
Let’s throw in some values and plot our sequence:
In [3]:
%matplotlib inline
from qctoolkit.pulses import plot
parameters = {'ta': 2,
'va': 2,
'tb': 4,
'vb': 3,
'tc': 5,
'td': 11,
'tend': 6}
plot(sequence, parameters, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

In [ ]:
Modelling Pulses Using Functions And Expressions¶
Assume we want to model a pulse that represents a damped sine function.
While we could, in theory, do this using TablePulseTemplate
s by
piecewise linear approximation (cf. Modelling a Simple
TablePulseTemplate), this would be a
tedious endeavor. A much simpler approach presents itself in the form of
the FunctionPulseTemplate
class of the qctoolkit. Like the
TablePulseTemplate
, a FunctionPulseTemplate
represents an atomic
pulse which will be converted into a waveform for execution. The
difference between both is that FunctionPulseTemplate
accepts a
mathematical expression which is parsed and evaluated using
py_expression_eval
to sample the waveform instead of the linear
interpolation between specified supporting points as it is done in
TablePulseTemplate
.
To define the sine function pulse template, we can thus do the following:
In [2]:
from qctoolkit.pulses import FunctionPulseTemplate
template = FunctionPulseTemplate('exp(-t/2)*sin(2*t)', '2*3.1415')
%matplotlib inline
from qctoolkit.pulses import plot
plot(template, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

The first argument to FunctionPulseTemplate
‘s constructor is the
string representation of the formula that the pulse represents. The
second argument is used to compute the length of the pulse. In this
case, this is simply a constant expression. Refer to
py-expression-eval’s
documentation
to read about the usable operators and functions in the expressions.
The t
is reserved as the free variable of the time domain in the
first argument and must be present. Other variables can be used at will
and corresponding values have to be passed in as a parameter when
instantiating the FunctionPulseTemplate
:
In [9]:
param_template = FunctionPulseTemplate('exp(-t/lambda)*sin(phi*t)', 'duration')
%matplotlib inline
from qctoolkit.pulses import plot
plot(param_template, {'lambda': 4, 'phi': 8, 'duration': 4*3.1415}, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

In [ ]:
Storing Pulse Templates: Serialization¶
So far, we have constructed new pulse templates in code for each session
(which were discarded afterwards). We now want to store them
persistently in the file system to be able to reuse them in later
sessions. For this, qctoolkit offers us serialization and
deserialization using the Serializer
and StorageBackend
classes.
Serializing Atomic Templates¶
Storing¶
In [1]:
from qctoolkit.pulses import TablePulseTemplate
from qctoolkit.serialization import Serializer, FilesystemBackend
anonymous_table = TablePulseTemplate()
anonymous_table.add_entry('ta', 'va', interpolation='hold')
anonymous_table.add_entry('tb', 'vb', interpolation='linear')
anonymous_table.add_entry('tend', 0, interpolation='jump')
backend = FilesystemBackend("./serialized_pulses")
serializer = Serializer(backend)
serializer.serialize(anonymous_table)
Assuming that we, again, have the TablePulseTemplate
from Modelling
a Simple TablePulseTemplate we instantiate
a Serializer
object to store it. Serializer
requires a
StorageBackend
instance which represents the actual data storage. We
provide a FilesystemBackend
instance which will store data in the
directory ./serialized_pulses
. With this setup, storing the pulse
template simply means invoking the serialize
method of the
Serializer
instance and passing in our template. This will store a
JSON representation of the object in the specified storage. Since we
have not provided any file name, Serializer
chooses the file name as
`main
<serialized_pulses/main>`__.
To specify a name, we can provide an identifier as an argument to the constructor of any pulse template:
In [2]:
identified_table = TablePulseTemplate(identifier='table_template')
identified_table.add_entry('ta', 'va', interpolation='hold')
identified_table.add_entry('tb', 'vb', interpolation='linear')
identified_table.add_entry('tend', 0, interpolation='jump')
serializer.serialize(identified_table)
This will create a file named
`table_template
<serialized_pulses/table_template>`__ with the same
contents as above.
Loading¶
To load a previously stored pulse template, we use the method
Serializer.deserialize
with the file name. The following code will
load a very simple table pulse template which ramps from 0 to 20 over a
duration of 4 units of time and which is stored under the name
`stored_template
<serialized_pulses/stored_template>`__:
In [3]:
loaded_template = serializer.deserialize('stored_template')
%matplotlib inline
from qctoolkit.pulses import plot
plot(loaded_template, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

Serializing Composite Templates¶
Serializing atomic templates (TablePulseTemplate
and
FunctionPulseTemplate
) is a straightforward task. However, when
storing composite templates that refer to subtemplates, e.g.,
SequencePulseTemplate
, one must decide whether to embed the
subtemplates into the composite’s serialization or to store them
separately. The latter one will be preferred if the subtemplate is used
in several composite templates. Sequencer
will decide whether to
store a subtemplate separately based on the fact whether or not it
provides an identifier: Subtemplates without an identifier will be
embedded.
In [4]:
from qctoolkit.pulses import SequencePulseTemplate
mapping = {
'ta': '1',
'tb': '2',
'va': '5',
'vb': '0',
'tend': '5'
}
sequence1 = SequencePulseTemplate([(anonymous_table, mapping)], {}, identifier='sequence_embedded')
serializer.serialize(sequence1)
sequence2 = SequencePulseTemplate([(identified_table, mapping)], {}, identifier='sequence_referenced')
serializer.serialize(sequence2)
The above code snippet creates two SequencePulseTemplate
s
consisting of only one table subtemplate (one of those defined above
respectively). The anonymous_table
is used in sequence1
. Since
it has no identifier, it is embedded in the serialization:
`sequence_embedded
<serialized_pulses/sequence_embedded>`__.
In contrast, the table subtemplate in sequence2
has an identifier,
so the serialization of sequence2
will contain only a reference to
the serialization of identified_table
:
`sequence_referenced
<serialized_pulses/sequence_referenced>`__.
Storage Backends¶
So far, we have only used the FilesystemBackend
to store pulse
template directly in some folder. However, the abstraction of the
StorageBackend
theoretically allows us to implement other backends,
e.g., a SQL database, without changing the Serializer
.
Additionally, the class CachingBackend
can be used to decorate and
add caching functionality to any other StorageBackend
instance to
speed up loading of frequently accessed templates as follows:
In [5]:
from qctoolkit.serialization import CachingBackend
cached_serializer = Serializer(CachingBackend(FilesystemBackend("./serialized_pulses")))
In [ ]:
The Sequencing Process: Obtaining Pulse Instances From Pulse Templates¶
In the previous examples, we have modelled pulses using the basic
members of qctoolkit’s PulseTemplate
class hierarchy. However, these
are only templates (or classes) of pulses and may contain parameters so
that they cannot be run directly on hardware. First, we have to
instantiate concrete waveforms and instructions in which order these
will be run. This process is called sequencing in the qctoolkit. It
involves the instantiations of parameters with concrete values and the
sampling of atomic pulse templates (TablePulseTemplate
and
FunctionPulseTemplate
) to obtain waveforms. Sequencing is
performed by the Sequencer
class. This example will explore how.
First, let’s assume we have the PulseTemplate
s from the examples
Modelling a Simple TablePulseTemplate and
Modelling Pulses Using Functions And
Expressions composed in a
SequencePulseTable
(cf. Combining PulseTemplates Using
SequencePulseTemplate) with some parameter
mappings:
In [5]:
%matplotlib inline
from qctoolkit.pulses import TablePulseTemplate, FunctionPulseTemplate, SequencePulseTemplate, plot
table_template = TablePulseTemplate()
table_template.add_entry('ta', 'va', interpolation='hold')
table_template.add_entry('tb', 'vb', interpolation='linear')
table_template.add_entry('tend', 0, interpolation='jump')
function_template = FunctionPulseTemplate('exp(-t/lambda)*sin(phi*t)', 'duration')
table_parameter_mapping = {
'ta': 'ta',
'tb': 'ta + duration',
'tend': '15',
'va': 'va',
'vb': '0'
}
function_parameter_mapping = {
'lambda': 'lambda',
'phi': 'phi',
'duration': 'duration'
}
sequence_template = SequencePulseTemplate([(table_template, table_parameter_mapping),
(function_template, function_parameter_mapping)],
{'ta', 'duration', 'va', 'lambda', 'phi'})
plot(sequence_template, {'lambda': 4, 'phi': 8, 'duration': 4*3.1415, 'ta': 1, 'va': 2}, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

While the plot illustrates how the pulse will look like for the given
parameter values, the object structure we created is only an abstract
representation of pulses with this structure. (This is the reason we
always have to provide specific parameters values for plotting.) To
convert this tree-like object structure into something the hardware
understands, we will use the Sequencer
class:
In [7]:
from qctoolkit.pulses import Sequencer
sequencer = Sequencer()
sequencer.push(sequence_template, {'lambda': 4, 'phi': 8, 'duration': 4*3.1415, 'ta': 1, 'va': 2})
instruction_sequence = sequencer.build()
print(instruction_sequence)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000734FEF0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000072E3E48>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x00000000072E3E10>]
Using the push
method of Sequencer
, we add PulseTemplate
objects to the sequencing stack, i.e., the last pushed object will be
sequenced first. We then invoke the build
method to start the
sequencing process, which will process all objects on the stack and
return a sequence of hardware instructions. As you can see in the output
of the above code snippet, the Sequencer
created a sequence of two
EXECInstruction
s and a STOPInstruction
. The two
EXECInstruction
s refer to waveforms obtained by sampling the
atomic template objects table_template
and function_template
of
which the sequence_template
we’ve sequenced is composed.
These waveforms are represented by objects of the Waveform
class,
which exposes a method sample
, accepting an array of sample points
in the time domain and returning an equally long array of sample values.
The choice of the sample points passed into the sample
method
together with the sample rate of the playback device determine the
duration of a time unit in a pulse template. For example, a pulse
template with a length of 1 time unit that is sampled using 1000
equidistant sample points will play for 1 µs on a playback device with a
sample rate of 1 GHz.
After the sequencing process is complete, the instruction sequence will be passed to a hardware-dependent interpreter which will configure the corresponding device accordingly and perform the sampling of the waveform depending on the device’s sample rate and typically a parameter indicating the real-time duration of a pulse template time unit.
Note that the plot
function internally employs sequencing and then
simply plots the generated waveforms.
Side Note: Instructions¶
The generated instruction sequence will consist of some of the following instructions: - EXECInstruction: execute a waveform - STOPInstruction: stop the execution - JUMPInstruction: jump to some instruction in the instruction sequence based on some trigger/condition - GOTOInstruction: go to some instruction in the instruction sequence
The latter two will only be generated when hardware-based conditional
branching is used in LoopPulseTemplate
and BranchPulseTemplate
.
This was the main motivator for the usage of such an instruction
sequence rather than just compling one large waveform. Using only the
PulseTemplate
s discussed so far, only EXECInstruction
s and a
final STOPInstruction
will be output by the Sequencer
.
In [ ]:
More Sophisticated Parameters¶
This example assumes that you are familiar with the sequencing process. If you need a refresher on this, have a look at The Sequencing Process: Obtaining Pulse Instances From Pulse Templates first.
Attention/Broken: During the creation of this example some implementation errors were found in the qctoolkit. Due to time constraints, these were not fixed immediately, leaving this example to be slightly flawed. However, in order to demonstrate the underlying principles, this example is published in its current form. Annotations like this mark where the current behavior of the qctoolit diverges from the intended one.
So far we have only considered constant parameter values. Now assume that we need to derive the value for a parameter based on some measurements we’ve made during the execution of previous parts of a composite pulse. For example, let the pulse we want to execute be constructed as follows:
In [1]:
%matplotlib inline
from qctoolkit.pulses import TablePulseTemplate, SequencePulseTemplate, plot
init_template = TablePulseTemplate()
init_template.add_entry(2, 5)
init_template.add_entry(4, -5)
init_template.add_entry(6, 0)
init_template.add_entry(8, 0)
measurement_template = TablePulseTemplate(measurement=True)
measurement_template.add_entry(0, 2)
measurement_template.add_entry(4, 0)
dependent_template = TablePulseTemplate()
dependent_template.add_entry(2, 0)
dependent_template.add_entry(5, 'v', 'linear')
dependent_template.add_entry(10, 0, 'linear')
sequence_template = SequencePulseTemplate([(init_template, {}),
(measurement_template, {}),
(dependent_template, {'v': 'v'}),
(init_template, {})
], {'v'})
plot(sequence_template, {'v': 1}, sample_rate=100)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

Now we want to let the value of parameter v
depend somehow on the
measurement we make between time 8 and 12 (assuming we have a way to
obtain measurement data, which is currently not the case (work in
progress)). Thus we need to execute the first part of the pulse, then
compute the parameter value and execute the remainder. We can do so be
encapsulating the computation of the parameter value in a custom
subclass of Parameter
. Assuming, for simplicity, that we have some
measurement_manager
object which we can query whether or not the
measurement has been made (is_measurement_available()
) and what data
was obtained (get_data()
) and that the value of v
shall simply
be twice the measured data, this subclass might look like this:
In [2]:
from qctoolkit.pulses import Parameter
class MeasurementDependentParameter(Parameter):
def __init__(self, measurement_manager) -> None:
self.measurement_manager = measurement_manager
@property
def requires_stop(self) -> bool:
return not self.measurement_manager.is_measurement_available()
def get_value(self) -> float:
return 2*(self.measurement_manager.get_data())
def get_serialization_data(self, serializer):
raise NotImplementedError()
@staticmethod
def deserialize(serializer):
raise NotImplementedError()
We overwrite the abstract property requires_stop
and the abstract
method get_value
of the Parameter
base class. requires_stop
is used to indicate to the Sequencer
whether the Parameter
object can currently be evaluated or whether the sequencing process has
to be interrupted. Our MeasurementDependentParameter
will return
True
if no measurement data is available (in contrast, the
ConstantParameter
- which internally represents any float value
passed in - always returns False
). The get_value
method returns
the parameter value. It is only called if requires_stop
is false. In
the MesaurementDependentParameter
class, we assume that the measured
data is a single float and that we simple want to multiply it by 2 as
the parameter’s value. The other two methods, get_serialization_data
and deserialize
also must be overwritten since each Parameter
implements the `Serializable
interface <03Serialization.ipynb>`__.
However, we just raise an exception here since these methods are not
relevant in this example.
We would then set up our pulse for execution like as in the following
snippet (including a stub implementation of a MeasurementManager
just for demonstration purposes):
In [3]:
from qctoolkit.pulses import Sequencer
# We define a stub for the measurement manager here only for illustration purposes.
class MeasurementManager:
def __init__(self, sequencer: Sequencer) -> None:
self.sequencer = sequencer
self.is_available = False
def is_measurement_available(self) -> bool:
return self.is_available
def get_data(self) -> float:
return 3
sequencer = Sequencer()
measurement_manager = MeasurementManager(sequencer)
parameter = MeasurementDependentParameter(measurement_manager)
sequencer.push(init_template)
sequencer.push(dependent_template, {'v': parameter})
sequencer.push(measurement_template)
sequencer.push(init_template)
The MeasurementManager.is_measurement_available
stub will simply
return the value to which we have set the is_available
member
variable of the class.
When we invoke Sequencer.build
, for each template on the sequencing
stack it first queries whether or not all parameters can be evaluated.
If any of them returns True
via the requires_stop
method, the
sequencing process will be interrupted. In our example, Sequencer
will first proceed through the first two subtemplates of
sequence_template
. When it arrives at dependent_template
, it
will stop:
In [4]:
first_sequence = sequencer.build()
print(first_sequence)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074D5E48>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074D58D0>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x00000000074D5908>]
As you can see in the output above, only two executions instructions are
generated, one for each TablePulseTemplate
instance before the
dependent_template
. Let us now switch the is_available
variable
of the MeasurementManager
instance to True
, simulating that
we’ve obtained some measurement result, and invoke the Sequencer
again:
In [5]:
measurement_manager.is_available = True
second_sequence = sequencer.build()
print(second_sequence)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074D5E48>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074D58D0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074B79E8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074A8EF0>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x000000000752C2E8>]
We have now obtained the complete sequence with one execution
instruction for each TablePulseTemplate
. Attention/Broken:
Currently this is incorrect behavior: We would want to only get the
remainder of the pulse in the second call since we wouldn’t want to
execute the first part of the pulse again. Needs fixing (`issue
105 <https://github.com/qutech/qc-toolkit/issues/105>`__).
Conditional Execution¶
The qctoolkit is desinged to support conditional execution of pulse (segments). This allows pulse designers to play back different waveforms depending on, e.g., environment data or state measurements of the quantum dot. Conditional execution may be implemented via trigger-based jumps to instructions directly on the playback device or emulated in software by choosing which pulse to send to the hardware for playback, if the hardware itself does not support branching.
Since the decision whether a condition will be evaluated software- or hardware-based depends on the hardware setup of the experiment and not on the pulse template the qctoolkit relies on an indirection scheme for conditions which is similar to the handling of parameters, as you will see in the following.
qctoolkit offers two PulseTemplate
subclasses for conditional
execution: LoopPulseTemplate
and BranchPulseTemplate
(Note:
BranchPulseTemplate is currently pending implementation (`issue
22 <https://github.com/qutech/qc-toolkit/issues/22>`__)).
LoopPulseTemplate
takes an identifier for a condition and a
subtemplate which is repeated as long as the condition evaluates to
True
. Let’s assume that we want to construct a pulse that waits
until some initialization is completed using a TablePulseTemplate
representing a zero-pulse of length 5 and a LoopPulseTemplate
:
In [4]:
from qctoolkit.pulses import LoopPulseTemplate, TablePulseTemplate
wait_template = TablePulseTemplate()
wait_template.add_entry(5, 0)
loop_template = LoopPulseTemplate('initialization_in_progress', wait_template)
loop_template
is now configured to evaluate a condition called
‘initialization_in_progress’. How this condition is implemented needs
not to be specified to declare pulse templates.
We will now look into the actual implementation of conditions. The
abstract interface of conditions is the Condition
class. As
mentioned in the beginning, conditions can be evaluated software- and
hardware-based. The classes SoftwareCondition
and
HardwareCondition
represent this in the qctoolkit. Note that these
classes don’t do the actual evaluation but encapsulate it against the
Sequencer
and are used to generate correct sequences for both cases.
Instances of Condition
s are passed directly into the Sequencer
and mapped to the PulseTemplate
s via the identifier, similar to
parameters.
Software-Based Conditions¶
SoftwareCondition
takes a callback function which is called to
evaluate the condition (and thus must return a boolean value). During
the sequencing process, this function will be called. If the return
value is True
, the subtemplate will be included in the instruction
sequence and another evaluation will be made until the return value is
False
. The generated instruction sequence will thus contain a number
of repetitions of the subtemplate but no actual jumping instructions,
the loop is essentially unrolled. The callback function is passed an
integer value indicating the current iteration of the evaluation.
As an example, we could use this to repeat the wait_template
a fixed
number of times, say 5, as follows:
In [6]:
from qctoolkit.pulses import SoftwareCondition, Sequencer
constant_repeat_condition = SoftwareCondition(lambda x: x < 5)
conditions = {'initialization_in_progress': constant_repeat_condition}
s = Sequencer()
parameters = {}
s.push(loop_template, parameters, conditions)
instructions = s.build()
print(instructions)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000748DAC8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000748DC18>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000748DC88>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000748DCF8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000748DD68>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x000000000748DDA0>]
We obtain an instruction sequence that repeats an execution instruction
(for the wait_template
waveform) five times. This is, of course, a
very simple example. The callback function passed into the
SoftwareCondition
instance will more likely evaluate some measured
data. Since this might not always be available in the sequencing pass,
the callback may return None
, which will interrupt the sequencing
process similar to the `requires_stop
method of the Parameter
class <05Parameters.ipynb>`__.
Hardware-Based Conditions¶
Since software-based evaluation of conditions is slow and might
interrupt the sequencing process, it might not always be applicable. If
supported by the hardware, hardware-based condition evaluation is
preferrable. For the sequencing process, this is represented by
instances of HardwareCondition
, which only take some identifier of
the hardware trigger. This must first be obtained from the hardware
currently not implemented and is embedded in the generated instruction
sequence which will contain jump instructions.
Assuming we have a hardware setup with a trigger that fires continuously until temperature threshold is reached and we want our pulse from above to repeat as long as the trigger fires. We can realize this as follows:
In [8]:
from qctoolkit.pulses import HardwareCondition, Trigger
# stub representation for the trigger which must be obtained from the hardware in a real use case
temperature_trigger = Trigger()
temperature_trigger_condition = HardwareCondition(temperature_trigger)
conditions = {'initialization_in_progress': temperature_trigger_condition}
s = Sequencer()
parameters = {}
s.push(loop_template, parameters, conditions)
instructions = s.build()
print(instructions)
[<qctoolkit.pulses.instructions.CJMPInstruction object at 0x000000000748DF28>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x000000000748DFD0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007462DD8>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x000000000749B080>]
As you can see in the output, the sequencing process now procudes instructions that perform conditional jumps and returns with goto instructions. The details are omitted here, suffice it to say that the trigger is embedded in the conditional jump instruction and this sequence will loop on the hardware as long as the trigger fires.
In [ ]:
Pulse-Control Integration¶
In [ ]:
Gate Configuration - A Real Use Case¶
An example for a real use case of the qctoolkit is the search for and evaluation of parameters for pulses that represent quantum gate operations.
Description of the Experiment¶
The experiment will typically involve a set of gate pulses \(G_j, 0 \leq j \lt N_{Gates}\).
The template for a gate pulse \(G_j\) is a sequence of \(\epsilon_i, 0 \leq i \lt N_{G_j}\) voltage levels held for time \(\Delta t = 1\) ns as illustrated in the figure below (with \(N_{G_j} = 7\)).

Template of a gate pulse
The experiment defines a number of sequences \(S_k, 0 \leq k \lt N_{Sequences}\) of the \(G_j\) as
where \(N_{S_k}\) is the length of sequence \(k\) and \(m_k(i): \{0, \dots, N_{S_k} - 1\} \rightarrow \{0, \dots, N_{Gates} - 1\}\) is a function that maps an index \(i\) to the \(m_k(i)\)-th gate of sequence \(S_k\) and thus fully describes the sequence. (These sequences express the sequential application of the gates to the qubit. In terms of quantum mathematics they may rather be expressed as multiplication of the matrices describing the unitary transformations applied by the gates: \(S_k = \prod_{i=N_{S_k} - 1}^{0} G_{m_k(i)} = G_{(N_{S_k} - 1)} \cdot \dots \cdot G_{1} \cdot G_{0}\).)
Measuring and analysing the effects of these sequences on the qubit’s state to derive parameters \(\epsilon_i\) for gate pulses that achieve certain state transformations is the goal of the experiment.
To this end, every sequence must be extended by some preceeding initialization pulse and a succeeding measurement pulse. Furthermore, due to hardware constraints in measuring, all sequences must be of equal length (which is typically 4 µs). Thus, some sequences require some wait time before initialization to increase their playback duration. These requirements give raise to extended sequences \(S_k'\) of the form:
where the functions \(p(k)\) and \(q(k)\) respectively select some initialization pulse \(I_{p(k)} \in \{I_1, I_2, \dots\}\) and measurement pulse \(M_{q(k)} \in \{M_1, M_2, \dots\}\) for sequence \(k\) and \(W_k\) is the aforementioned wait pulse. The ‘|’ denote concatenation of pulses, i.e., sequential execution.
Since measurement of quantum state is a probabilistic process, many measurements of the effect of a single sequence must be made to reconstruct the resulting state of the qubit. Thus, the experiment at last defines scanlines (typically of duration 1 second), which are sequences of the \(S_k'\). (These simply represent batches of sequences to configure playback and measurement systems and have no meaning to the experiment beyond these practical considerations.)
Implementation Using the qctoolkit¶
We now want to illustrate how to setup the experiment described above
using the qctoolkit. Let us assume the experiment considers only two
different gates pulses (\(N_{Gates} = 2\)). We further assume that
\(N_{G_1} = 20\) and \(N_{G_2} = 18\). We define them using
instances of TablePulseTemplate
:
In [2]:
from qctoolkit.pulses import TablePulseTemplate
delta_t = 1 # assuming that delta_t is 1 elementary time unit long in pulse defintions, i.e. 1 time unit = 1 ns
gate_0 = TablePulseTemplate()
for i in range(0, 19):
gate_0.add_entry((i) * delta_t, 'gate_0_eps_' + str(i))
gate_1 = TablePulseTemplate()
for i in range(0, 17):
gate_1.add_entry((i) * delta_t, 'gate_1_eps_' + str(i))
gates = [gate_0, gate_1]
We thus obtain two TablePulseTemplate
of the desired form with
parameters ‘gate_1_eps_1’ to ‘gate_1_eps_20’ and ‘gate_2_eps_1’
to ‘gate_2_eps_18’.
Next, we will define sequences as SequncePulseTemplate
objects. We
assume that the mapping functions \(m_k\) are given as a 2
dimensional array (which impilicitly also defines \(N_{Sequences}\)
and \(N_{S_k}\)), e.g.:
In [3]:
m = [
[0, 1, 0, 0, 0, 1, 1, 0, 1], # m_0(i)
[1, 1, 0, 0, 1, 0], # m_1(i)
[1, 0, 0, 1, 1, 0, 0, 1] #m_2(i)
]
The SequencePulseTemplate
objects can now easily be constructed:
In [4]:
from qctoolkit.pulses import SequencePulseTemplate
# SequencePulseTemplate requires a definition of parameter mappings from parameters passed into the
# SequencePulseTemplate object to its subtemplates. In this case, we want parameters to map 1 to 1
# and thus create an identity mapping of parameter names for both gates using python list/set/dict comprehension
epsilon_mappings = [
{param_name: param_name for param_name in gates[0].parameter_names},
{param_name: param_name for param_name in gates[1].parameter_names}
]
all_epsilons = gates[0].parameter_names | gates[1].parameter_names
sequences = []
for m_k in m:
subtemplates = []
for g_ki in m_k:
subtemplates.append((gates[g_ki], epsilon_mappings[g_ki]))
sequences.append(SequencePulseTemplate(subtemplates, all_epsilons))
We end up with a list sequences
which contains the sequences
described by our \(m\) as qctoolkit pulse templates.
To visualize our progress, let us plot our two gates and the second sequence with some random values between \(-5\) and \(5\) for the \(\epsilon_i\):
In [17]:
import random
random.seed('Some seed such that numbers generated are predictable')
parameters = {parameter_name: random.random() * 10 - 5 for parameter_name in all_epsilons}
%matplotlib inline
from qctoolkit.pulses import plot
plot(gates[0], parameters)
plot(gates[1], parameters)
plot(sequences[1], parameters)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "



We now must construct the \(S_k'\). For simplicity, we assume that there is only one initialization and one measurement pulse which are defined somehow and define only stubs here. We also define a waiting pulse with a variable length:
In [6]:
# stub for an initialization pulse of length 4
init = TablePulseTemplate()
init.add_entry(0, 5)
init.add_entry(4, 0, 'linear')
# stub for a measurement pulse of length 12
measure = TablePulseTemplate()
measure.add_entry(0, 0)
measure.add_entry(12, 5, 'linear')
# a wating pulse
wait = TablePulseTemplate()
wait.add_entry('wait_duration', 0)
For our example, let us assume that we want all \(S_k'\) to take 200
ns (since we’ve chosen the \(S_k\) to be rather short). We know that
the duration of our gate pulses in nanoseconds is equal to the number of
entries in the TablePulseTemplate
objects (each voltage level is
held for one unit of time in the tables which corresponds to
\(\Delta t = 1\) ns). Accordingly, the init pulse lasts for 4 ns and
the measure pulse for 12 ns. The required length of the wait pulse can
then be computed as follows:
In [7]:
wait_times = []
desired_time = 200
for m_k in m:
duration_k = 4 + 12 # init + measurement duration
for g_ki in m_k:
duration_k += len(gates[g_ki].entries) # add the number of entries of all gates in the sequence
wait_time_k = desired_time - duration_k
wait_times.append(wait_time_k)
print(wait_times)
[21, 76, 40]
Finally we can construct the \(S_k'\):
In [8]:
# an identity mapping for all epsilons
all_epsilons_map = {param_name: param_name for param_name in all_epsilons}
final_sequences = []
for k in range(0, len(sequences)):
#prepare the subtemplate of the sequence S_k'
subtemplates = []
# include the wait pulse first. pass in the appropriate wait time.
# note that the wait time has to be cast to a string. In parameter mappings, some string containing a mathematical
# expression is expected. Here, we provide a mathematical expression consisting only of a constant value.
subtemplates.append((wait, {'wait_duration': str(wait_times[k])}))
# append the init pulse
subtemplates.append((init, {}))
# append the k-th sequence
subtemplates.append((sequences[k], all_epsilons_map))
# append the measuring
subtemplates.append((measure, {}))
# construct the object for S_k'
s_k_prime = SequencePulseTemplate(subtemplates, all_epsilons)
final_sequences.append(s_k_prime)
Let us plot \(S_1'\) to see what whether we’ve accomplished our goal:
In [9]:
plot(final_sequences[1], parameters)
C:\Anaconda3\lib\site-packages\matplotlib\figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
"matplotlib is currently using a non-GUI backend, "

Finally, we construct a single scanline which just repeats all three sequences over and over again. Since our \(S_k'\) are short, we just build a scanline with a duration of 0.6 microseconds. With a duration for each \(S_k'\) of 200 ns, we can fit 1‘000 repetitions of \(S_1' | S_2' | S_3'\) in our scanline.
In [10]:
from qctoolkit.pulses import Sequencer
subtemplates = []
for i in range(0, 1000):
subtemplates.append((final_sequences[0], all_epsilons_map))
subtemplates.append((final_sequences[1], all_epsilons_map))
subtemplates.append((final_sequences[2], all_epsilons_map))
scanline = SequencePulseTemplate(subtemplates, all_epsilons)
In [ ]:
Detailed Sequencing Walkthrough¶
This example will provide two step-by-step illustrations of the internals of the sequencing process. Note that this will involve some calls into the object structure to unveil internals which are not intended to be made in a productive use case and produce some very technical outputs. These are broken down and explained in detail where necessary.
Example 1 (Software-Evaluated Loop Condition)¶
In the first example, we will emulate the behaviour of a
RepetitonPulseTemplate
to repeats a TablePulseTemplate
for a
fixed number of times using a LoopPulseTemplate
with a
SoftwareCondition
. We have done so already in the example for
conditional execution but here we will
explore the sequencing process in detail. The definitions of the classes
are the following (resulting in 2 repetitions):
In [1]:
from qctoolkit.pulses import LoopPulseTemplate, TablePulseTemplate, SoftwareCondition, Sequencer
# define a table pulse template which we want to repeat (including a parameter)
table_template = TablePulseTemplate()
table_template.add_entry(1, 'foo', 'linear')
table_template.add_entry(3, 'foo')
table_template.add_entry(4, 0, 'linear')
# define a software condition will evaluate to true as long as the loop counter is less than 5 and false afterwards
repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process
# define a loop template consisting of the table template as body and a condition identified by 'rcon'
loop_template = LoopPulseTemplate('rcon', table_template)
# provide sequencing mappings: condition 'rcon' -> repeat_condition and parameter 'foo' -> 2
conditions = {'rcon': repeat_condition}
parameters = {'foo': 2}
# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings
s = Sequencer()
s.push(loop_template, parameters, conditions)
# store references to the main instruction block and the corresponding sequencing stack
main_block = s._Sequencer__main_block
sequencing_stack = s._Sequencer__sequencing_stacks[main_block]
In [2]:
print(sequencing_stack) # print the current sequencing stack for the main block
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000070423C8>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>})]
As you can see in the dump of the sequencing stack of the main
instruction block, there is currently one item on the stack, which a
tuple consisting of our LoopPulseTemplate
loop_template
and the
mappings parameters
and conditions
. The following figure
illustrates the current content sequencing stack.

The sequencing stack after pushing loop_template
Running Sequencer.build()
would run the entire sequencing process,
resulting in the desired instruction sequence. However, since we want to
understand the process itself, we will perform the necessary steps
ourselves by manually calling the corresponding functions. We now
translate the topmost (and only) stack item:
In [3]:
# get the topmost item from the sequencing stack
(template, params, conds) = sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
The build_sequence
method looks up the condition identified by
‘rcon’ in the conditions map conditions
which is our
repeat_condition
object define dabove. It then invokes the
build_sequence_loop
method of this object. Being a
SoftwareCondition
object, it evaluates its evaluation function,
which returns true, and thus adds the body, our table_template
to
the sequencing stack. Since the loop condition must be evaluated again
after the loop body was run, also the loop_template
is pushed to the
stack again. Thus, the stack now looks as follows:
In [4]:
print(sequencing_stack) # print the current sequencing stack for the main block
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000070423C8>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>}), (<qctoolkit.pulses.table_pulse_template.TablePulseTemplate object at 0x00000000045D1B00>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>})]

The sequencing stack after translating loop_template
Note that no waveforms or instructions have been generated so far, i.e., the main instruction block is empty:
In [5]:
print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block
[]
Sequencer
would now enter the next iteration, i.e., pop and
translate the topmost element from the stack.
In [6]:
# get the topmost item from the sequencing stack
(template, params, conds) = sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
This time, our table_template
, that is, the body of the loop, is at
the top. It’s translation via build_sequence()
looks up the
parameter value for ‘foo’, generates a waveform and inserts a
corresponding instruction in the main block:
In [7]:
print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000070424A8>]
Since we’ve successfully processed the table_template
item on the
sequencing stack, we are left with a loop_template
item. That means,
the stack looks just like in the beginning (refer to Figure 1).
In [8]:
print(sequencing_stack)
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000070423C8>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>})]
We will fetch it from the stack and translate it. Since the loop counter
in the SoftwareCondition
object is currently 1, it will still
evaluate to true, meaning that the loop continues, i.e., the body
template and the loop template are again pushed to the stack (cf. Figure
2).
In [9]:
# get the topmost item from the sequencing stack
(template, params, conds) = sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
print(sequencing_stack)
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000070423C8>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>}), (<qctoolkit.pulses.table_pulse_template.TablePulseTemplate object at 0x00000000045D1B00>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>})]
Proceeding as before, we translate the topmost element, which is again
the loop body table_template
. This results in the expected
EXECInstruction
and a stack in which the loop_template
remains
for reevaluation.
In [10]:
# get the topmost item from the sequencing stack
(template, params, conds) = sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
print(sequencing_stack)
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000070423C8>, {'foo': <ConstantParameter 2>}, {'rcon': <qctoolkit.pulses.conditions.SoftwareCondition object at 0x00000000045D1E48>})]
Our main instruction block now contains two EXECInstruction
s:
In [11]:
print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000070424A8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074B5D68>]
We are left with the loop_template
on the stack, which we will
translate in the following. However, this time the repeat_condition
will evaluate to false, meaning that neither body nor loop template are
pushed to the stack. We are done with the loop.
In [12]:
# get the topmost item from the sequencing stack
(template, params, conds) = sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
print(sequencing_stack)
[]
Note that we have not yet obtained an InstructionSequence
but only
constructed the main InstructionBlock
object. We will conclude the
sequencing by converting the main block into the desired sequence:
In [13]:
instruction_sequence = main_block.compile_sequence()
print(instruction_sequence)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000070424A8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074B5D68>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x00000000074C0D68>]
In this case, this is a trivial task, as it simply takes both
instructions contains in the main block and adds a STOPInstruction
to generate the sequence. Now we are done.
In [14]:
print(s.has_finished()) # are we done?
True
We have explored what happens internally when we invoke
Sequencer.build()
on our loop_template
. In a productive use
case, we can let Sequencer
handle all of this and get the same
result (apart from memory addresses of the involved python objects):
In [15]:
s = Sequencer()
repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process
conditions = {'rcon': repeat_condition}
s.push(loop_template, parameters, conditions)
instruction_sequence = s.build()
print(instruction_sequence)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074C94A8>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074C9588>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x00000000074C95C0>]
Example 2 (Hardware Evaluated Branch Nested In Loop)¶
In this example we want to look into hardware-based branching evaluation
based using the HardwareCondition
class and how
InstructionBlocks
are created and handled during the Sequencing
process. The pulse we want to translate is a loop which contains a
branch template (if-else-construct) which in turn contains table pulse
templates:
In [16]:
from qctoolkit.pulses import LoopPulseTemplate, BranchPulseTemplate, TablePulseTemplate, HardwareCondition, Sequencer
from qctoolkit.pulses import Trigger
# two table pulse templates for the alternative paths of the branch pulse template
# they differ in their interpolation behaviour (jump vs linear ramp)
pos_template = TablePulseTemplate()
pos_template.add_entry(1, 'foo', 'linear')
pos_template.add_entry(3, 'foo')
pos_template.add_entry(4, 0, 'linear')
neg_template = TablePulseTemplate()
neg_template.add_entry(1, 'foo')
neg_template.add_entry(3, 'foo')
neg_template.add_entry(4, 0)
parameters = {'foo': 2}
# the branch pulse template
branch_template = BranchPulseTemplate('bcon', pos_template, neg_template)
# the loop pulse template
loop_template = LoopPulseTemplate('lcon', branch_template)
# for this example: Introduce a trigger that can be identified by a name
class NamedTrigger(Trigger):
def __init__(self, name: str) -> None:
self.name = name
def __str__(self) -> str:
return "Trigger '{}'".format(self.name)
# create HardwareCondition objects for branch and loop
branch_condition = HardwareCondition(NamedTrigger("branch_trigger"))
loop_condition = HardwareCondition(NamedTrigger("loop_trigger"))
# mapping of identifiers to conditions
conditions = {'bcon': branch_condition, 'lcon': loop_condition}
# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings
s = Sequencer()
s.push(loop_template, parameters, conditions)
# store references to the main instruction block and the corresponding sequencing stack
main_block = s._Sequencer__main_block
main_sequencing_stack = s._Sequencer__sequencing_stacks[main_block]
The sequencing stack now contains a single entry, namely the tuple containing our ‘loop_template’ and the mappings ‘parameters’ and ‘conditions’:
In [17]:
print(main_sequencing_stack)
[(<qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate object at 0x00000000074B55C0>, {'foo': <ConstantParameter 2>}, {'bcon': <qctoolkit.pulses.conditions.HardwareCondition object at 0x00000000074B5400>, 'lcon': <qctoolkit.pulses.conditions.HardwareCondition object at 0x00000000074B5CF8>})]

The initial sequencing stack for example 2
Entering the sequencing process, we translate the topmost element as before:
In [18]:
# get the topmost item from the sequencing stack
(template, params, conds) = main_sequencing_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
main_sequencing_stack.pop()
template.build_sequence(s, params, conds, main_block)
print(main_sequencing_stack)
[]
Surprisingly at a first glance, the sequencing stack of the main
instruction block is empty afterwards although we are far from being
done with the sequencing process. What happended here is that the call
to LoopPulseTemplate
s build_sequence()
method resulted in a
call to build_sequence_loop
of the corresponding condition object
loop_condition
. This is of type HardwareConditon
, meaning that
all possible execution paths must be translated into a hardware
understandable format. Thus, a new InstructionBlock
was instantiated
into which the body of the loop will be sequenced. Accordingly, the
remaining templates which represent the loops body are pushed to the
specific sequencing stack of this new instruction block. In the main
block we will simply find a CJMPInstruction
(conditional jump
instruction) to the new block.
In [19]:
print(main_block._InstructionBlock__instruction_list)
[<qctoolkit.pulses.instructions.CJMPInstruction object at 0x00000000074C07F0>]
In [20]:
# obtain a reference to the new InstructionBlock representing the body of the loop
loop_body_block = main_block._InstructionBlock__instruction_list[0].target.block
loop_body_stack = s._Sequencer__sequencing_stacks[loop_body_block]
The contents of the sequencing stacks are the following:
In [21]:
print(loop_body_stack) # print the sequencing stack for the loop body block
[(<qctoolkit.pulses.branch_pulse_template.BranchPulseTemplate object at 0x00000000074B55F8>, {'foo': <ConstantParameter 2>}, {'bcon': <qctoolkit.pulses.conditions.HardwareCondition object at 0x00000000074B5400>, 'lcon': <qctoolkit.pulses.conditions.HardwareCondition object at 0x00000000074B5CF8>})]

Sequencing stacks after translating the loop template
Sequencer
continues the sequencing process, until it cannot proceed
for any instruction block currently under construction. Thus, although
the stack for the main block is empty, we continue with the loop body
block:
In [22]:
# get the topmost item from the sequencing stack
(template, params, conds) = loop_body_stack[-1]
# remove template from stack and translate it it does not require a stop
if not template.requires_stop(params, conds):
loop_body_stack.pop()
template.build_sequence(s, params, conds, loop_body_block)
print(loop_body_stack)
[]
Since we translated a BranchLoopTemplate
with a HardwareConditon
we end up with two new instructions blocks, one for the if-branch and
one for the else-branch, with separate sequencing stacks. We also obtain
corresponding jump instructions in the loop body block: A conditional
jump into the if-branch, performed if the condition is fulfulled
followed by an unconditional goto into the else-branch, if the
conditional jump does not occur, i.e., the condition is not fullfilled.
In [23]:
print(loop_body_block._InstructionBlock__instruction_list)
[<qctoolkit.pulses.instructions.CJMPInstruction object at 0x00000000074C9DA0>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x00000000074C9D30>]
In [24]:
# get references to if and else branches
if_branch_block = loop_body_block._InstructionBlock__instruction_list[0].target.block
else_branch_block = loop_body_block._InstructionBlock__instruction_list[1].target.block
if_branch_stack = s._Sequencer__sequencing_stacks[if_branch_block]
else_branch_stack = s._Sequencer__sequencing_stacks[else_branch_block]
The stacks now look as follows:

Sequencing stacks after translating the branch template
The table pulse templates pos_template
and neg_template
are
translated in the usual manner, resulting in an EXECInstruction
in
the respective instruction blocks:
In [25]:
# translate if-branch stack
(template, params, conds) = if_branch_stack[-1]
if not template.requires_stop(params, conds):
if_branch_stack.pop()
template.build_sequence(s, params, conds, if_branch_block)
# translate else-branch stack
(template, params, conds) = else_branch_stack[-1]
if not template.requires_stop(params, conds):
else_branch_stack.pop()
template.build_sequence(s, params, conds, else_branch_block)
Afterwards, all stacks are empty
In [26]:
print(main_sequencing_stack)
print(loop_body_stack)
print(if_branch_stack)
print(else_branch_stack)
[]
[]
[]
[]
and we are left with four instruction blocks, two of which contains
EXECInstructions
while the rest only specifies control flow, that
is, (conditional) jumps into other blocks. In an illustration, the
blocks look like this:

Interconnected instruction block
Note that the returning unconditional gotos are not listed explicitely in the outputs above but always implicitely defined.
In the final step of the sequencing process, these blocks are now converted into a single sequence of instructions by embedding each blocks instructions into the instructions of the block jumping into it and then adapting the jump pointers. The final sequence is as follows:
In [27]:
instruction_sequence = main_block.compile_sequence()
print(instruction_sequence)
[<qctoolkit.pulses.instructions.CJMPInstruction object at 0x00000000074C07F0>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x00000000074D52E8>, <qctoolkit.pulses.instructions.CJMPInstruction object at 0x00000000074C9DA0>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x00000000074C9D30>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x00000000074D5390>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074AAE48>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x00000000074D5438>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x00000000074AAB00>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x00000000074D54A8>]

Interconnected instruction block
This instruction sequence indeed represents our desired behavior.
In [28]:
print(s.has_finished()) # really done?
True
qctoolkit package¶
Subpackages¶
qctoolkit.pulses package¶
Submodules¶
qctoolkit.pulses.branch_pulse_template module¶
qctoolkit.pulses.conditions module¶
-
class
qctoolkit.pulses.conditions.
Condition
(*args, **kwargs) → None[source]¶ Bases:
object
A condition on which the execution of a pulse may depend.
Conditions are used for branching and looping of pulses and thus relevant for BranchPulseTemplate and LoopPulseTemplate. Implementations of Condition may rely on software variables, measured data or be mere placeholders for hardware triggers.
-
build_sequence_branch
(delegator: qctoolkit.pulses.sequencing.SequencingElement, if_branch: qctoolkit.pulses.sequencing.SequencingElement, else_branch: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶ Translate a branching SequencingElement using this Condition into an instruction sequence for the given instruction block using sequencer and the given parameter sets.
delegator refers to the SequencingElement which has delegated the invocation of build_sequence to this Condition object. if_branch and else_branch are the elements to be translated into if and else branch instructions. See also SequencingElement.build_sequence().
-
build_sequence_loop
(delegator: qctoolkit.pulses.sequencing.SequencingElement, body: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶ Translate a looping SequencingElement using this Condition into an instruction sequence for the given instruction block using sequencer and the given parameter sets.
delegator refers to the SequencingElement which has delegated the invocation of build_sequence to this Condition object. body is the loop body element. See also SequencingElement.build_sequence().
-
-
exception
qctoolkit.pulses.conditions.
ConditionEvaluationException
[source]¶ Bases:
Exception
Indicates that a SoftwareCondition cannot be evaluated yet.
-
class
qctoolkit.pulses.conditions.
SoftwareCondition
(evaluation_callback: typing.Callable[[int], typing.Union[bool, NoneType]]) → None[source]¶ Bases:
qctoolkit.pulses.conditions.Condition
A condition that will be evaluated in the software.
SoftwareConditions are evaluated in software, allowing them to rely on sophisticated measurement evaluation or to be used when the hardware device does not support trigger based jumping instructions.
On the downside, this means that a translation processes may be interrupted because a SoftwareCondition relying on measurement data cannot be evaluated before that data is acquired. In this case, the already translated part has to be executed, the measurement is made and in a subsequent translation, the SoftwareCondition is evaluated and the corresponding instructions of one branch/the loop body are generated without jumping instructions.
This interruption of pulse execution might not be feasible in some environments.
-
build_sequence_branch
(delegator: qctoolkit.pulses.sequencing.SequencingElement, if_branch: qctoolkit.pulses.sequencing.SequencingElement, else_branch: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
build_sequence_loop
(delegator: qctoolkit.pulses.sequencing.SequencingElement, body: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
-
class
qctoolkit.pulses.conditions.
HardwareCondition
(trigger: qctoolkit.pulses.instructions.Trigger) → None[source]¶ Bases:
qctoolkit.pulses.conditions.Condition
A condition that will be evaluated using hardware triggers.
During the translation process, HardwareCondition instanced will produce in code blocks for branches/loop bodies and the corresponding conditional jump instructions.
-
build_sequence_branch
(delegator: qctoolkit.pulses.sequencing.SequencingElement, if_branch: qctoolkit.pulses.sequencing.SequencingElement, else_branch: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
build_sequence_loop
(delegator: qctoolkit.pulses.sequencing.SequencingElement, body: qctoolkit.pulses.sequencing.SequencingElement, sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
qctoolkit.pulses.function_pulse_template module¶
-
class
qctoolkit.pulses.function_pulse_template.
FunctionPulseTemplate
(expression: str, duration_expression: str, measurement: bool = False, identifier: str = None) → None[source]¶ Bases:
qctoolkit.pulses.pulse_template.PulseTemplate
Defines a pulse via a time-domain expression.
FunctionPulseTemplate stores the expression and its external parameters. The user must provide two things: one expression that calculates the length of the pulse from the external parameters and the time-domain pulse shape itself as a expression. The external parameters are derived from the expressions themselves. Like other PulseTemplates the FunctionPulseTemplate can be declared to be a measurement pulse.
The independent variable in the expression is called ‘t’ and is given in units of nano-seconds.
-
build_sequence
(sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
get_measurement_windows
(parameters: typing.Union[typing.Dict[str, qctoolkit.pulses.parameters.Parameter], NoneType] = {}) → typing.List[typing.Tuple[float, float]][source]¶ Return all measurement windows defined in this PulseTemplate.
A ExpressionPulseTemplate specifies either no measurement windows or exactly one that spans its entire duration, depending on whether the measurement_pulse flag was given during construction.
-
get_pulse_length
(parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter]) → float[source]¶ Return the length of this pulse for the given parameters.
-
is_interruptable
¶ Return true, if this PulseTemplate contains points at which it can halt if interrupted.
-
parameter_declarations
¶ Return a set of all parameter declaration objects of this TablePulseTemplate.
-
parameter_names
¶ Return the set of names of declared parameters.
-
-
class
qctoolkit.pulses.function_pulse_template.
FunctionWaveform
(parameters: typing.Dict[str, float], expression: qctoolkit.expressions.Expression, duration_expression: qctoolkit.expressions.Expression) → None[source]¶ Bases:
qctoolkit.pulses.instructions.Waveform
-
duration
¶
-
qctoolkit.pulses.instructions module¶
-
qctoolkit.pulses.instructions.
WaveformTable
¶ alias of
Tuple
-
class
qctoolkit.pulses.instructions.
WaveformTableEntry
(t, v, interp)¶ Bases:
tuple
-
interp
¶ Alias for field number 2
-
t
¶ Alias for field number 0
-
v
¶ Alias for field number 1
-
-
class
qctoolkit.pulses.instructions.
Waveform
[source]¶ Bases:
qctoolkit.comparable.Comparable
-
duration
¶ Return the duration of the waveform in time units.
-
sample
(sample_times: numpy.ndarray, first_offset: float = 0) → numpy.ndarray[source]¶ Sample the waveform.
Will be sampled at the given sample_times. These, however, will be normalized such that the lie in the range [0, waveform.duration] for interpolation. first_offset is the offset of the discrete first sample from the actual beginning of the waveform in a continuous time domain.
-
-
class
qctoolkit.pulses.instructions.
InstructionPointer
(block: qctoolkit.pulses.instructions.InstructionBlock, offset: int) → None[source]¶ Bases:
object
-
class
qctoolkit.pulses.instructions.
CJMPInstruction
(trigger: qctoolkit.pulses.instructions.Trigger, block: qctoolkit.pulses.instructions.InstructionBlock, offset: int = 0) → None[source]¶
-
class
qctoolkit.pulses.instructions.
EXECInstruction
(waveform: qctoolkit.pulses.instructions.Waveform) → None[source]¶
-
class
qctoolkit.pulses.instructions.
GOTOInstruction
(block: qctoolkit.pulses.instructions.InstructionBlock, offset: int = 0) → None[source]¶
-
class
qctoolkit.pulses.instructions.
InstructionBlock
(outer_block: typing.Union[qctoolkit.pulses.instructions.InstructionBlock, NoneType] = None) → None[source]¶ Bases:
object
-
add_instruction_cjmp
(trigger: qctoolkit.pulses.instructions.Trigger, target_block: qctoolkit.pulses.instructions.InstructionBlock, offset: int = 0) → None[source]¶
-
add_instruction_goto
(target_block: qctoolkit.pulses.instructions.InstructionBlock, offset: int = 0) → None[source]¶
-
instructions
¶
-
-
qctoolkit.pulses.instructions.
InstructionSequence
¶ alias of
List
-
exception
qctoolkit.pulses.instructions.
InstructionBlockNotYetPlacedException
[source]¶ Bases:
Exception
Indicates that an attempt was made to obtain the start address of an InstructionBlock that was not yet placed inside the corresponding outer block.
qctoolkit.pulses.interpolation module¶
-
class
qctoolkit.pulses.interpolation.
HoldInterpolationStrategy
[source]¶ Bases:
qctoolkit.pulses.interpolation.InterpolationStrategy
Holds previous value and jumps to the current value at the last sample.
-
class
qctoolkit.pulses.interpolation.
JumpInterpolationStrategy
[source]¶ Bases:
qctoolkit.pulses.interpolation.InterpolationStrategy
Jumps to the current value at the first sample and holds.
-
class
qctoolkit.pulses.interpolation.
LinearInterpolationStrategy
[source]¶ Bases:
qctoolkit.pulses.interpolation.InterpolationStrategy
Interpolates linearly.
qctoolkit.pulses.loop_pulse_template module¶
-
class
qctoolkit.pulses.loop_pulse_template.
LoopPulseTemplate
(condition: str, body: qctoolkit.pulses.pulse_template.PulseTemplate, identifier: typing.Union[str, NoneType] = None) → None[source]¶ Bases:
qctoolkit.pulses.pulse_template.PulseTemplate
Conditional looping in a pulse.
A LoopPulseTemplate is a PulseTemplate which is repeated during execution as long as a certain condition holds.
-
body
¶
-
build_sequence
(sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, qctoolkit.pulses.conditions.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
condition
¶
-
static
deserialize
(serializer: qctoolkit.serialization.Serializer, condition: str, body: typing.Dict[str, typing.Any], identifier: typing.Union[str, NoneType] = None) → qctoolkit.pulses.loop_pulse_template.LoopPulseTemplate[source]¶
-
get_measurement_windows
(parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter] = None) → typing.Tuple[float, float][source]¶
-
get_serialization_data
(serializer: qctoolkit.serialization.Serializer) → typing.Dict[str, typing.Any][source]¶
-
is_interruptable
¶
-
parameter_declarations
¶
-
parameter_names
¶
-
qctoolkit.pulses.parameters module¶
-
class
qctoolkit.pulses.parameters.
Parameter
→ None[source]¶ Bases:
qctoolkit.serialization.Serializable
A parameter for pulses.
Parameter specifies a concrete value which is inserted instead of the parameter declaration reference in a PulseTemplate if it satisfies the minimum and maximum boundary of the corresponding ParameterDeclaration. Implementations of Parameter may provide a single constant value or obtain values by computation (e.g. from measurement results).
-
requires_stop
¶ Return True if the evaluation of this Parameter instance requires a stop in execution/sequencing, e.g., because it depends on data that is only measured in during the next execution.
-
-
class
qctoolkit.pulses.parameters.
ParameterDeclaration
(name: str, min: typing.Union[float, typing.ParameterDeclaration] = -inf, max: typing.Union[float, typing.ParameterDeclaration] = inf, default: typing.Union[float, NoneType] = None) → None[source]¶ Bases:
qctoolkit.serialization.Serializable
A declaration of a parameter required by a pulse template.
PulseTemplates may declare parameters to allow for variations of values in an otherwise static pulse structure. ParameterDeclaration represents a declaration of such a parameter and allows for the definition of boundaries and a default value for a parameter.
-
BoundaryValue
¶ alias of
Union
-
absolute_max_value
¶ Return this ParameterDeclaration’s maximum value.
If the maximum value of this ParameterDeclaration instance is a reference to another instance, references are resolved until a concrete value or None is obtained.
-
absolute_min_value
¶ Return this ParameterDeclaration’s minimum value.
If the minimum value of this ParameterDeclaration instance is a reference to another instance, references are resolved until a concrete value or None is obtained.
-
default_value
¶ Return this ParameterDeclaration’s default value.
-
static
deserialize
(serializer: qctoolkit.serialization.Serializer, name: str, min_value: typing.Union[str, float], max_value: typing.Union[str, float], default_value: float) → qctoolkit.pulses.parameters.ParameterDeclaration[source]¶
-
get_serialization_data
(serializer: qctoolkit.serialization.Serializer) → typing.Dict[str, typing.Any][source]¶
-
is_parameter_valid
(p: qctoolkit.pulses.parameters.Parameter) → bool[source]¶ Checks whether a given parameter satisfies this ParameterDeclaration.
A parameter is valid if all of the following statements hold: - If the declaration specifies a minimum value, the parameter’s value must be greater or equal - If the declaration specifies a maximum value, the parameter’s value must be less or equal
-
max_value
¶ Return this ParameterDeclaration’s maximum value or reference.
-
min_value
¶ Return this ParameterDeclaration’s minimum value or reference.
-
name
¶
-
-
class
qctoolkit.pulses.parameters.
ConstantParameter
(value: float) → None[source]¶ Bases:
qctoolkit.pulses.parameters.Parameter
A pulse parameter with a constant value.
-
static
deserialize
(serializer: qctoolkit.serialization.Serializer, constant: float) → qctoolkit.pulses.parameters.ConstantParameter[source]¶
-
requires_stop
¶
-
static
-
exception
qctoolkit.pulses.parameters.
ParameterNotProvidedException
(parameter_name: str) → None[source]¶ Bases:
Exception
Indicates that a required parameter value was not provided.
-
exception
qctoolkit.pulses.parameters.
ParameterValueIllegalException
(parameter_declaration: qctoolkit.pulses.parameters.ParameterDeclaration, parameter_value: float) → None[source]¶ Bases:
Exception
Indicates that the value provided for a parameter is illegal, i.e., is outside the parameter’s bounds or of wrong type.
qctoolkit.pulses.plotting module¶
qctoolkit.pulses.pulse_template module¶
-
qctoolkit.pulses.pulse_template.
MeasurementWindow
¶ alias of
Tuple
-
class
qctoolkit.pulses.pulse_template.
PulseTemplate
(identifier: typing.Union[str, NoneType] = None) → None[source]¶ Bases:
qctoolkit.serialization.Serializable
,qctoolkit.pulses.sequencing.SequencingElement
A PulseTemplate represents the parameterized general structure of a pulse.
A PulseTemplate described a pulse in an abstract way: It defines the structure of a pulse but might leave some timings or voltage levels undefined, thus declaring parameters. This allows to reuse a PulseTemplate for several pulses which have the same overall structure and differ only in concrete values for the parameters. Obtaining an actual pulse which can be executed by specifying values for these parameters is called instantiation of the PulseTemplate.
-
get_measurement_windows
(parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter] = None) → typing.List[typing.Tuple[float, float]][source]¶ Return all measurement windows defined in this PulseTemplate.
-
is_interruptable
¶ Return true, if this PulseTemplate contains points at which it can halt if interrupted.
-
parameter_declarations
¶ Return the set of ParameterDeclarations.
-
parameter_names
¶ Return the set of names of declared parameters.
-
qctoolkit.pulses.repetition_pulse_template module¶
qctoolkit.pulses.sequence_pulse_template module¶
-
class
qctoolkit.pulses.sequence_pulse_template.
SequencePulseTemplate
(subtemplates: typing.List[typing.Tuple[qctoolkit.pulses.pulse_template.PulseTemplate, typing.Dict[str, str]]], external_parameters: typing.List[str], identifier: typing.Union[str, NoneType] = None) → None[source]¶ Bases:
qctoolkit.pulses.pulse_template.PulseTemplate
A sequence of different PulseTemplates.
SequencePulseTemplate allows to group smaller PulseTemplates (subtemplates) into on larger sequence, i.e., when instantiating a pulse from a SequencePulseTemplate all pulses instantiated from the subtemplates are queued for execution right after one another. SequencePulseTemplate allows to specify a mapping of parameter declarations from its subtemplates, enabling renaming and mathematical transformation of parameters. The default behavior is to exhibit the union of parameter declarations of all subtemplates. If two subpulses declare a parameter with the same name, it is mapped to both. If the declarations define different minimal and maximal values, the more restricitive is chosen, if possible. Otherwise, an error is thrown and an explicit mapping must be specified. ^outdated
-
build_sequence
(sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, qctoolkit.pulses.conditions.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶
-
static
deserialize
(serializer: qctoolkit.serialization.Serializer, is_interruptable: bool, subtemplates: typing.Iterable[typing.Dict[str, typing.Union[str, typing.Dict[str, typing.Any]]]], external_parameters: typing.Iterable[str], identifier: typing.Union[str, NoneType] = None) → qctoolkit.pulses.sequence_pulse_template.SequencePulseTemplate[source]¶
-
get_measurement_windows
(parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter] = None) → typing.List[typing.Tuple[float, float]][source]¶
-
get_serialization_data
(serializer: qctoolkit.serialization.Serializer) → typing.Dict[str, typing.Any][source]¶
-
is_interruptable
¶
-
parameter_declarations
¶
-
parameter_names
¶
-
-
exception
qctoolkit.pulses.sequence_pulse_template.
MissingMappingException
(template, key) → None[source]¶ Bases:
Exception
qctoolkit.pulses.sequencing module¶
-
class
qctoolkit.pulses.sequencing.
SequencingElement
→ None[source]¶ Bases:
object
An entity which can be sequenced using Sequencer.
-
build_sequence
(sequencer: qctoolkit.pulses.sequencing.Sequencer, parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition], instruction_block: qctoolkit.pulses.instructions.InstructionBlock) → None[source]¶ Translate this SequencingElement into an instruction sequence for the given instruction_block using sequencer and the given parameter sets.
Implementation guide: Use instruction_block methods to add instructions or create new InstructionBlocks. Use sequencer to push child elements to the translation stack.
-
requires_stop
(parameters: typing.Dict[str, qctoolkit.pulses.parameters.Parameter], conditions: typing.Dict[str, typing.Condition]) → bool[source]¶ Return True if this SequencingElement cannot be translated yet.
Sequencer will check requires_stop() before calling build_sequence(). If requires_stop() returns True, Sequencer interrupts the current translation process and will not call build_sequence().
Implementation guide: requires_stop() should only return True, if this SequencingElement cannot be build, i.e., the return value should only depend on the parameters/conditions of this SequencingElement, not on possible child elements. If this SequencingElement contains a child element which requires a stop, this information will be regarded during translation of that element.
-
-
class
qctoolkit.pulses.sequencing.
Sequencer
→ None[source]¶ Bases:
object
Translates tree structures of SequencingElement objects to linear instruction sequences contained in a InstructionBlock.
-
StackElement
¶ alias of
Tuple
-
build
() → qctoolkit.pulses.instructions.InstructionBlock[source]¶ - Start the translation process. Translate all elements currently on the translation stacks into a sequence
- and return the InstructionBlock of the main sequence.
Processes all stacks (for each InstructionBlock) until each stack is either empty or its topmost element requires a stop. If build is called after a previous translation process where some elements required a stop (i.e., has_finished returned False), it will append new modify the previously generated and returned main InstructionBlock. Make sure not to rely on that being unchanged.
-
has_finished
() → bool[source]¶ Returns True, if all translation stacks are empty. Indicates that the translation is complete.
Note that has_finished that has_finished will return False, if there are stack elements that require a stop. In this case, calling build will only have an effect if these elements no longer require a stop, e.g. when required measurement results have been acquired since the last translation.
-
push
(sequencing_element: qctoolkit.pulses.sequencing.SequencingElement, parameters: typing.Dict[str, typing.Union[qctoolkit.pulses.parameters.Parameter, float]] = {}, conditions: typing.Dict[str, typing.Condition] = {}, target_block: qctoolkit.pulses.instructions.InstructionBlock = None) → None[source]¶ Add an element to the translation stack of the target_block with the given set of parameters.
The element will be on top of the stack, i.e., it is the first to be translated if no subsequent calls to push with the same target_block occur.
-
qctoolkit.pulses.table_pulse_template module¶
Module contents¶
qctoolkit.qcmatlab package¶
Submodules¶
qctoolkit.qcmatlab.manager module¶
qctoolkit.qcmatlab.pulse_control module¶
-
class
qctoolkit.qcmatlab.pulse_control.
PulseControlInterface
(sample_rate: float, time_scaling: float = 0.001) → None[source]¶ Bases:
object
-
Pulse
¶ alias of
Dict
-
PulseGroup
¶ alias of
Dict
-
create_pulse_group
(sequence: typing.List[qctoolkit.pulses.instructions.Instruction], name: str) → typing.Tuple[typing.PulseControlInterface.PulseGroup, typing.List[typing.PulseControlInterface.Pulse]][source]¶ Construct a dictionary adhering to the pulse group struct definition in pulse control.
All waveforms in the given InstructionSequence are converted to waveform pulse structs and returned as a list in the second component of the returned tuple. The first component of the result is a pulse group dictionary denoting the sequence of waveforms using their indices in the returned list. create_pulse_group detects multiple use of waveforms and sets up the pulse group dictionary accordingly.
Note that pulses are not registered in pulse control. To achieve this and update the pulse group struct accordingly, the dedicated MATLAB script has to be invoked.
The function will raise an Exception if the given InstructionBlock does contain branching instructions, which are not supported by pulse control.
Arguments: sequence – The InstructionSequence to convert. name – Value for the name field in the resulting pulse group dictionary.
-
create_waveform_struct
(waveform: qctoolkit.pulses.instructions.Waveform, name: str) → typing.Dict[str, typing.Any][source]¶ Construct a dictionary adhering to the waveform struct definition in pulse control.
Arguments: waveform – The Waveform object to convert. name – Value for the name field in the resulting waveform dictionary.
-
Module contents¶
qctoolkit.utils package¶
Submodules¶
qctoolkit.utils.type_check module¶
RELATED THIRD PARTY IMPORTS
-
exception
qctoolkit.utils.type_check.
MismatchingTypesException
(message) → None[source]¶ Bases:
Exception
Exception raised when a type error occurs in “type_check”
Has the behaviour of an default Exception due inheritance of the self.
-
qctoolkit.utils.type_check.
typecheck
(same_length: bool = False, raise_on_error: bool = False, log: bool = True)[source]¶ Decorator for functions, invokes typechecking on their annotation
This function invokes the typechecking on functions which uses the annotations of python3. This type check will happen in runtime.
For the “same_length” argument, please reference to “__equal_types”.
There are two ways, this function can react on type errors: 1. “raise_on_error” will raise an “MismatchingTypeException” defined below. 2. “log” will create a logging message on the warning level.
Usage: “@typecheck: ‘def f(x):”
Module contents¶
Submodules¶
qctoolkit.expressions module¶
qctoolkit.serialization module¶
-
class
qctoolkit.serialization.
CachingBackend
(backend: qctoolkit.serialization.StorageBackend) → None[source]¶
-
class
qctoolkit.serialization.
Serializable
(identifier: typing.Union[str, NoneType] = None) → None[source]¶ Bases:
object
-
static
deserialize
(serializer: qctoolkit.serialization.Serializer, **kwargs) → qctoolkit.serialization.Serializable[source]¶ Reconstruct the Serializable object from a dictionary containing all relevant information as obtained from get_serialization_data.
-
get_serialization_data
(serializer: qctoolkit.serialization.Serializer) → typing.Dict[str, typing.Any][source]¶ Return all data relevant for serialization as a dictionary containing only base types.
-
identifier
¶
-
static
-
class
qctoolkit.serialization.
Serializer
(storage_backend: qctoolkit.serialization.StorageBackend) → None[source]¶ Bases:
object
-
deserialize
(representation: typing.Union[str, typing.Dict[str, typing.Any]]) → qctoolkit.serialization.Serializable[source]¶
-