2.11. Arithmetic with Pulse Templates¶
Pulse templates support some simple arithmetic operations that make it nearly a vector space. It is implemented via the two classes ArithmeticPulseTemplate
and ArithmeticAtomicPulseTemplate
. This notebook demonstrates the direct and indirect (via invoking operators) usage of these two pulse template classes. We start by defining some building blocks.
[1]:
import math
from qupulse.pulses import TablePT, FunctionPT, RepetitionPT, AtomicMultiChannelPT, plotting
# define some building blocks
sin_pt = FunctionPT('sin(omega*t)', 't_duration', channel='X')
cos_pt = FunctionPT('sin(omega*t)', 't_duration', channel='Y')
exp_pt = AtomicMultiChannelPT(sin_pt, cos_pt)
tpt = TablePT({'X': [(0, 0), (3., 4.), ('t_duration', 2., 'linear')],
'Y': [(0, 1.), ('t_y', 5.), ('t_duration', 0., 'linear')]})
complex_pt = RepetitionPT(tpt, 5) @ exp_pt
parameters = dict(t_duration=10, omega=math.pi*2/10, t_y=3.4)
_ = plotting.plot(complex_pt, parameters, show=False)
2.11.1. Operations with pulse templates and scalars¶
Operations between a pulse template and a scalar are implemented via ArithmeticAtomicPulseTemplate
.
2.11.1.1. Scale¶
Given an arbitrary pulse template \(P\) and a scalar \(\alpha\) we can scale the amplitude of all channels by multiplying \(P\) with \(\alpha\). Multiplying with \(\alpha^{-1}\) (some people call it dividing by \(\alpha\)) is also implemented.
[2]:
from qupulse.expressions import Expression
scaled = 'alpha' * complex_pt
# casts alpha implicitly to ExpressionScalar
multiplied = complex_pt * 'x + y'
divided = complex_pt / 4.4
_ = plotting.plot(scaled, {**parameters, 'alpha': 2}, show=False)
2.11.1.2. Offset¶
You can add and subtract expression like objects to/from arbitrary pulse templates.
[3]:
offset_pt = scaled + 'offset * alpha'
diff_pt = 4 - complex_pt
2.11.1.3. Channel specific operands¶
If you only want to apply an operation to a specific subset of a pulse template’s channels you can do this by using a dictionary as the other operand. Channels not in the dictionary are treated as if the dictionary contains the neutral element of the operation i.e. 0 for addition and subtraction and 1 for multiplication and division.
[4]:
scaled_x = {'X': 'x_scale'} * complex_pt
scaled_x_y = {'X': 'x_scale', 'Y': 2} * complex_pt
offset_x = complex_pt + {'X': 2}
2.11.2. Adding and subtracting pulse templates¶
Addition and subtraction of pulse templates returns an atomic pulse template, the ArithmeticAtomicPulseTemplate
- Both have the same length - Both are atomic. Otherwise they are interpreted as atomic. - Channels defined in only one of the two operands are implicitly defined as 0 in the other
[5]:
_ = plotting.plot(exp_pt + tpt, parameters, show=False)
_ = plotting.plot(exp_pt - tpt, parameters, show=False)
combined = exp_pt + tpt
2.11.3. Manual creation¶
For exact control what is needed we can use the classes directly instead of implicitly via the operators
[6]:
from qupulse.pulses import ArithmeticPT, ArithmeticAtomicPT
scaled_x = ArithmeticPT({'X': 'x_scale'}, '*', complex_pt, identifier='scaled_x')
# this raises a warning because complex_pt is treated as atomic
complex_added_1 = ArithmeticAtomicPT(complex_pt, '+', complex_pt)
# this raises a warning because complex_pt is treated as atomic
complex_added_2 = ArithmeticAtomicPT(complex_pt, '+', complex_pt, silent_atomic=True)
C:\Users\humpohl\Documents\git\qupulse\qupulse\pulses\arithmetic_pulse_template.py:60: ImplicitAtomicityInArithmeticPT: ArithmeticAtomicPulseTemplate treats all operands as if they are atomic. You can silence this warning by passing `silent_atomic=True` or by ignoring this category.
category=ImplicitAtomicityInArithmeticPT)