2.6. 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 notebook
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)
<IPython.core.display.Javascript object>
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 0x0000000007D381D0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007D382B0>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x0000000007D38390>]
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 0x0000000007D381D0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007D382B0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007D38908>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007D38898>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x0000000007D38940>]
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>`__).