From 9f10e44da8bf6f4141c5079f951c1754ea3ca2e9 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 26 Nov 2020 12:04:14 +0100 Subject: [PATCH 1/6] Add initial_values and final_values and test stubs --- qupulse/pulses/abstract_pulse_template.py | 4 + qupulse/pulses/arithmetic_pulse_template.py | 132 ++++++++++++------ qupulse/pulses/function_pulse_template.py | 10 ++ qupulse/pulses/loop_pulse_template.py | 18 +++ qupulse/pulses/mapping_pulse_template.py | 41 +++--- .../pulses/multi_channel_pulse_template.py | 26 ++++ qupulse/pulses/point_pulse_template.py | 16 +++ qupulse/pulses/pulse_template.py | 31 ++++ qupulse/pulses/repetition_pulse_template.py | 8 ++ qupulse/pulses/sequence_pulse_template.py | 9 ++ qupulse/pulses/table_pulse_template.py | 10 ++ .../pulses/arithmetic_pulse_template_tests.py | 20 ++- tests/pulses/function_pulse_tests.py | 6 + tests/pulses/loop_pulse_template_tests.py | 6 + tests/pulses/mapping_pulse_template_tests.py | 6 + .../multi_channel_pulse_template_tests.py | 12 +- tests/pulses/point_pulse_template_tests.py | 6 + tests/pulses/pulse_template_tests.py | 17 +++ .../pulses/repetition_pulse_template_tests.py | 6 + tests/pulses/sequence_pulse_template_tests.py | 6 + tests/pulses/sequencing_dummies.py | 10 +- tests/pulses/table_pulse_template_tests.py | 6 + 22 files changed, 331 insertions(+), 75 deletions(-) diff --git a/qupulse/pulses/abstract_pulse_template.py b/qupulse/pulses/abstract_pulse_template.py index 319ad7975..fd7007bdb 100644 --- a/qupulse/pulses/abstract_pulse_template.py +++ b/qupulse/pulses/abstract_pulse_template.py @@ -146,6 +146,10 @@ def _internal_create_program(self, **kwargs): doc=_PROPERTY_DOC.format(name='integral')) parameter_names = property(partial(_get_property, property_name='parameter_names'), doc=_PROPERTY_DOC.format(name='parameter_names')) + initial_values = property(partial(_get_property, property_name='initial_values'), + doc=_PROPERTY_DOC.format(name='initial_values')) + final_values = property(partial(_get_property, property_name='final_values'), + doc=_PROPERTY_DOC.format(name='final_values')) class NotSpecifiedError(RuntimeError): diff --git a/qupulse/pulses/arithmetic_pulse_template.py b/qupulse/pulses/arithmetic_pulse_template.py index ae685f26f..675c17ed7 100644 --- a/qupulse/pulses/arithmetic_pulse_template.py +++ b/qupulse/pulses/arithmetic_pulse_template.py @@ -2,6 +2,7 @@ from typing import Any, Dict, List, Set, Optional, Union, Mapping, FrozenSet, cast, Callable from numbers import Real import warnings +import operator import sympy import cached_property @@ -18,15 +19,17 @@ IdentityTransformation -def _apply_operation_to_channel_dict(operator: str, - lhs: Mapping[ChannelID, Any], - rhs: Mapping[ChannelID, Any]) -> Dict[ChannelID, Any]: +def _apply_operation_to_channel_dict(lhs: Mapping[ChannelID, Any], + rhs: Mapping[ChannelID, Any], + operator_both: Optional[Callable[[Any, Any], Any]], + rhs_only: Optional[Callable[[Any], Any]] + ) -> Dict[ChannelID, Any]: result = dict(lhs) for channel, rhs_value in rhs.items(): if channel in result: - result[channel] = ArithmeticWaveform.operator_map[operator](result[channel], rhs_value) + result[channel] = operator_both(result[channel], rhs_value) else: - result[channel] = ArithmeticWaveform.rhs_only_map[operator](rhs_value) + result[channel] = rhs_only(rhs_value) return result @@ -106,14 +109,30 @@ def duration(self) -> ExpressionScalar: """Duration of the lhs operand if it is larger zero. Else duration of the rhs.""" return ExpressionScalar(sympy.Max(self.lhs.duration, self.rhs.duration)) + def _apply_operation(self, lhs: Mapping[str, Any], rhs: Mapping[str, Any]) -> Dict[str, Any]: + operator_both = ArithmeticWaveform.operator_map[self._arithmetic_operator] + rhs_only = ArithmeticWaveform.rhs_only_map[self._arithmetic_operator] + return _apply_operation_to_channel_dict(lhs, rhs, + operator_both=operator_both, + rhs_only=rhs_only) + @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: - return _apply_operation_to_channel_dict(self._arithmetic_operator, self.lhs.integral, self.rhs.integral) + # this is a guard for possible future changes + assert self._arithmetic_operator in ('+', '-'), \ + f"Integral not correctly implemented for '{self._arithmetic_operator}'" + return self._apply_operation(self.lhs.integral, self.rhs.integral) def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: - return _apply_operation_to_channel_dict(self._arithmetic_operator, - self.lhs._as_expression(), - self.rhs._as_expression()) + return self._apply_operation(self.lhs._as_expression(), self.rhs._as_expression()) + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_operation(self.lhs.initial_values, self.rhs.initial_values) + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_operation(self.lhs.final_values, self.rhs.final_values) def build_waveform(self, parameters: Dict[str, Real], @@ -169,14 +188,21 @@ def deserialize(cls, serializer: Optional[Serializer]=None, **kwargs) -> 'Arithm class ArithmeticPulseTemplate(PulseTemplate): - """""" def __init__(self, lhs: Union[PulseTemplate, ExpressionLike, Mapping[ChannelID, ExpressionLike]], arithmetic_operator: str, rhs: Union[PulseTemplate, ExpressionLike, Mapping[ChannelID, ExpressionLike]], *, identifier: Optional[str] = None): - """ + """Allowed oeprations + + scalar + pulse_template + scalar - pulse_template + scalar * pulse_template + pulse_template + scalar + pulse_template - scalar + pulse_template * scalar + pulse_template / scalar Args: lhs: Left hand side operand @@ -380,47 +406,67 @@ def defined_channels(self): def duration(self) -> ExpressionScalar: return self._pulse_template.duration + def _scalar_as_dict(self) -> Dict[ChannelID, ExpressionScalar]: + if isinstance(self._scalar, ExpressionScalar): + return {channel: self._scalar + for channel in self.defined_channels} + else: + return dict(self._scalar) + @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: integral = {channel: value.sympified_expression for channel, value in self._pulse_template.integral.items()} + scalar = self._scalar_as_dict() - if isinstance(self._scalar, ExpressionScalar): - scalar = {channel: self._scalar.sympified_expression - for channel in self.defined_channels} + if self._arithmetic_operator in ('+', '-'): + for ch, value in scalar.items(): + scalar[ch] = value * self.duration.sympified_expression + + return self._apply_operation_to_channel_dict(integral, scalar) + + def _apply_operation_to_channel_dict(self, + pt_values: Dict[ChannelID, ExpressionScalar], + scalar_values: Dict[ChannelID, ExpressionScalar]): + operator_map = { + '+': operator.add, + '-': operator.sub, + '/': operator.truediv, + '*': operator.mul + } + + rhs_only_map = { + '+': operator.pos, + '-': operator.neg, + '*': lambda x: x, + '/': lambda x: 1 / x + } + + if self._pulse_template is self.lhs: + lhs, rhs = pt_values, scalar_values else: - scalar = {channel: value.sympified_expression - for channel, value in self._scalar.items()} + lhs, rhs = scalar_values, pt_values + # cannot divide by pulse templates + operator_map.pop('/') + rhs_only_map.pop('/') - if self._arithmetic_operator == '+': - for channel, value in scalar.items(): - integral[channel] = integral[channel] + (value * self.duration.sympified_expression) + operator_both = operator_map.get(self._arithmetic_operator, None) + rhs_only = rhs_only_map.get(self._arithmetic_operator, None) - elif self._arithmetic_operator == '*': - for channel, value in scalar.items(): - integral[channel] = integral[channel] * value + return _apply_operation_to_channel_dict(lhs, rhs, operator_both=operator_both, rhs_only=rhs_only) - elif self._arithmetic_operator == '/': - assert self._pulse_template is self.lhs - for channel, value in scalar.items(): - integral[channel] = integral[channel] / value + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_operation_to_channel_dict( + self._pulse_template.initial_values, + self._scalar_as_dict() + ) - else: - assert self._arithmetic_operator == '-' - if self._pulse_template is self.rhs: - # we need to negate all existing values - for channel, inner_value in integral.items(): - if channel in scalar: - integral[channel] = scalar[channel] * self.duration.sympified_expression - inner_value - else: - integral[channel] = -inner_value - - else: - for channel, value in scalar.items(): - integral[channel] = integral[channel] - value * self.duration.sympified_expression - - for channel, value in integral.items(): - integral[channel] = ExpressionScalar(value) - return integral + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_operation_to_channel_dict( + self._pulse_template.final_values, + self._scalar_as_dict() + ) @property def measurement_names(self) -> Set[str]: diff --git a/qupulse/pulses/function_pulse_template.py b/qupulse/pulses/function_pulse_template.py index 1913c8697..b2ca3c332 100644 --- a/qupulse/pulses/function_pulse_template.py +++ b/qupulse/pulses/function_pulse_template.py @@ -151,4 +151,14 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: expr = ExpressionScalar.make(self.__expression.underlying_expression.subs({'t': self._AS_EXPRESSION_TIME})) return {self.__channel: expr} + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + expr = ExpressionScalar.make(self.__expression.underlying_expression.subs('t', 0)) + return {self.__channel: expr} + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + expr = ExpressionScalar.make(self.__expression.underlying_expression.subs('t', self.__duration_expression.underlying_expression)) + return {self.__channel: expr} + diff --git a/qupulse/pulses/loop_pulse_template.py b/qupulse/pulses/loop_pulse_template.py index 8ba6925a8..a8718bf43 100644 --- a/qupulse/pulses/loop_pulse_template.py +++ b/qupulse/pulses/loop_pulse_template.py @@ -283,6 +283,24 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: return body_integrals + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self.body.initial_values + initial_idx = self._loop_range.start + for ch, value in values.items(): + values[ch] = ExpressionScalar(value.underlying_expression.subs(self._loop_index, initial_idx)) + return values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self.body.initial_values + start, step, stop = self._loop_range.start.sympified_expression, self._loop_range.step.sympified_expression, self._loop_range.stop.sympified_expression + n = (stop - start) // step + final_idx = start + sympy.Max(n - 1, 0) * step + for ch, value in values.items(): + values[ch] = ExpressionScalar(value.underlying_expression.subs(self._loop_index, final_idx)) + return values + class LoopIndexNotUsedException(Exception): def __init__(self, loop_index: str, body_parameter_names: Set[str]): diff --git a/qupulse/pulses/mapping_pulse_template.py b/qupulse/pulses/mapping_pulse_template.py index af5b64e00..f235b28ec 100644 --- a/qupulse/pulses/mapping_pulse_template.py +++ b/qupulse/pulses/mapping_pulse_template.py @@ -344,38 +344,31 @@ def get_measurement_windows(self, measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping=measurement_mapping) ) - @property - def integral(self) -> Dict[ChannelID, ExpressionScalar]: - internal_integral = self.__template.integral - expressions = dict() - - # sympy.subs() does not work if one of the mappings in the provided dict is an Expression object - # the following is an ugly workaround - # todo: make Expressions compatible with sympy.subs() - parameter_mapping = {parameter_name: expression.underlying_expression - for parameter_name, expression in self.__parameter_mapping.items()} - for channel, ch_integral in internal_integral.items(): - channel_out = self.__channel_mapping.get(channel, channel) - if channel_out is None: - continue - - expressions[channel_out] = ExpressionScalar( - ch_integral.sympified_expression.subs(parameter_mapping, simultaneous=True) - ) - - return expressions - - def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: + def _apply_mapping_to_inner_channel_dict(self, to_map: Dict[ChannelID, ExpressionScalar]) -> Dict[ChannelID, ExpressionScalar]: parameter_mapping = {parameter_name: expression.underlying_expression for parameter_name, expression in self.__parameter_mapping.items()} - inner = self.__template._as_expression() return { self.__channel_mapping.get(ch, ch): ExpressionScalar(ch_expr.sympified_expression.subs(parameter_mapping, simultaneous=True)) - for ch, ch_expr in inner.items() + for ch, ch_expr in to_map.items() if self.__channel_mapping.get(ch, ch) is not None } + @property + def integral(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_mapping_to_inner_channel_dict(self.__template.integral) + + def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_mapping_to_inner_channel_dict(self.__template._as_expression()) + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_mapping_to_inner_channel_dict(self.__template.initial_values) + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self._apply_mapping_to_inner_channel_dict(self.__template.final_values) + class MissingMappingException(Exception): """Indicates that no mapping was specified for some parameter declaration of a diff --git a/qupulse/pulses/multi_channel_pulse_template.py b/qupulse/pulses/multi_channel_pulse_template.py index 264b342c7..246a64e7e 100644 --- a/qupulse/pulses/multi_channel_pulse_template.py +++ b/qupulse/pulses/multi_channel_pulse_template.py @@ -205,6 +205,20 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: expressions.update(subtemplate._as_expression()) return expressions + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = {} + for subtemplate in self._subtemplates: + values.update(subtemplate.initial_values) + return values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = {} + for subtemplate in self._subtemplates: + values.update(subtemplate.final_values) + return values + class ParallelConstantChannelPulseTemplate(PulseTemplate): def __init__(self, @@ -286,6 +300,18 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: integral[channel] = value * duration return integral + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self._template.initial_values + values.update(self._overwritten_channels) + return values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self._template.final_values + values.update(self._overwritten_channels) + return values + def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: if serializer: raise NotImplementedError('Legacy serialization not implemented for new class') diff --git a/qupulse/pulses/point_pulse_template.py b/qupulse/pulses/point_pulse_template.py index 43cc3d40e..1489ec13b 100644 --- a/qupulse/pulses/point_pulse_template.py +++ b/qupulse/pulses/point_pulse_template.py @@ -171,6 +171,22 @@ def value_trafo(v): expressions[channel] = pw return expressions + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + shape = (len(self._channels),) + return { + ch: IndexedBroadcast(self._entries[0].v, shape, ch_idx) + for ch_idx, ch in enumerate(self._channels) + } + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + shape = (len(self._channels),) + return { + ch: IndexedBroadcast(self._entries[-1].v, shape, ch_idx) + for ch_idx, ch in enumerate(self._channels) + } + class InvalidPointDimension(Exception): def __init__(self, expected, received): diff --git a/qupulse/pulses/pulse_template.py b/qupulse/pulses/pulse_template.py index aa558f561..efdd2f6f3 100644 --- a/qupulse/pulses/pulse_template.py +++ b/qupulse/pulses/pulse_template.py @@ -94,6 +94,16 @@ def __rmatmul__(self, other: MappingTuple) -> 'SequencePulseTemplate': def integral(self) -> Dict[ChannelID, ExpressionScalar]: """Returns an expression giving the integral over the pulse.""" + @property + @abstractmethod + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + """Values of defined channels at t == 0""" + + @property + @abstractmethod + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + """Values of defined channels at t == self.duration""" + def create_program(self, *, parameters: Optional[Mapping[str, Union[Expression, str, Number, ConstantParameter]]]=None, measurement_mapping: Optional[Mapping[str, Optional[str]]]=None, @@ -354,6 +364,27 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: """Helper function to allow integral calculation in case of truncation. AtomicPulseTemplate._AS_EXPRESSION_TIME is by convention the time variable.""" + @property + def integral(self) -> Dict[ChannelID, ExpressionScalar]: + # this default implementation uses _as_expression + return {ch: ExpressionScalar(sympy.integrate(expr.sympified_expression, + (self._AS_EXPRESSION_TIME, 0, self.duration.sympified_expression))) + for ch, expr in self._as_expression().items()} + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self._as_expression() + for ch, value in values.items(): + values[ch] = value.evaluate_symbolic({self._AS_EXPRESSION_TIME: 0}) + return values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + values = self._as_expression() + for ch, value in values.items(): + values[ch] = value.evaluate_symbolic({self._AS_EXPRESSION_TIME: self.duration}) + return values + class DoubleParameterNameException(Exception): diff --git a/qupulse/pulses/repetition_pulse_template.py b/qupulse/pulses/repetition_pulse_template.py index 964a2202d..a08d144fa 100644 --- a/qupulse/pulses/repetition_pulse_template.py +++ b/qupulse/pulses/repetition_pulse_template.py @@ -162,6 +162,14 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: body_integral = self.body.integral return [self.repetition_count * c for c in body_integral] + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.body.initial_values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.body.final_values + class ParameterNotIntegerException(Exception): """Indicates that the value of the parameter given as repetition count was not an integer.""" diff --git a/qupulse/pulses/sequence_pulse_template.py b/qupulse/pulses/sequence_pulse_template.py index 36c69cd27..5829ce68f 100644 --- a/qupulse/pulses/sequence_pulse_template.py +++ b/qupulse/pulses/sequence_pulse_template.py @@ -195,3 +195,12 @@ def add_dicts(x, y): return {k: x[k] + y[k] for k in x} return functools.reduce(add_dicts, [sub.integral for sub in self.__subtemplates], expressions) + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.__subtemplates[0].initial_values + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.__subtemplates[-1].final_values + diff --git a/qupulse/pulses/table_pulse_template.py b/qupulse/pulses/table_pulse_template.py index c1fd07a52..1bdf59158 100644 --- a/qupulse/pulses/table_pulse_template.py +++ b/qupulse/pulses/table_pulse_template.py @@ -442,6 +442,16 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: post_value=post_value) return expressions + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return {ch: entries[0].v + for ch, entries in self._entries.items()} + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return {ch: entries[-1].v + for ch, entries in self._entries.items()} + def concatenate(*table_pulse_templates: TablePulseTemplate, **kwargs) -> TablePulseTemplate: """Concatenate two or more table pulse templates""" diff --git a/tests/pulses/arithmetic_pulse_template_tests.py b/tests/pulses/arithmetic_pulse_template_tests.py index 11b7d65d7..78e3e79d6 100644 --- a/tests/pulses/arithmetic_pulse_template_tests.py +++ b/tests/pulses/arithmetic_pulse_template_tests.py @@ -104,9 +104,9 @@ def test_integral(self): integrals_lhs = dict(a=ExpressionScalar('a_lhs'), b=ExpressionScalar('b')) integrals_rhs = dict(a=ExpressionScalar('a_rhs'), c=ExpressionScalar('c')) - lhs = DummyPulseTemplate(duration=4, defined_channels={'a', 'b'}, + lhs = DummyPulseTemplate(duration='t_dur', defined_channels={'a', 'b'}, parameter_names={'x', 'y'}, integrals=integrals_lhs) - rhs = DummyPulseTemplate(duration=4, defined_channels={'a', 'c'}, + rhs = DummyPulseTemplate(duration='t_dur', defined_channels={'a', 'c'}, parameter_names={'x', 'z'}, integrals=integrals_rhs) expected_plus = dict(a=ExpressionScalar('a_lhs + a_rhs'), @@ -118,14 +118,20 @@ def test_integral(self): self.assertEqual(expected_plus, (lhs + rhs).integral) self.assertEqual(expected_minus, (lhs - rhs).integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_as_expression(self): integrals_lhs = dict(a=ExpressionScalar('a_lhs'), b=ExpressionScalar('b')) integrals_rhs = dict(a=ExpressionScalar('a_rhs'), c=ExpressionScalar('c')) duration = 4 t = DummyPulseTemplate._AS_EXPRESSION_TIME - expr_lhs = {ch: i * t / duration for ch, i in integrals_lhs.items()} - expr_rhs = {ch: i * t / duration for ch, i in integrals_rhs.items()} + expr_lhs = {ch: i * t / duration**2 * 2 for ch, i in integrals_lhs.items()} + expr_rhs = {ch: i * t / duration**2 * 2 for ch, i in integrals_rhs.items()} lhs = DummyPulseTemplate(duration=duration, defined_channels={'a', 'b'}, parameter_names={'x', 'y'}, integrals=integrals_lhs) @@ -471,6 +477,12 @@ def test_integral(self): w=ExpressionScalar('wi')) self.assertEqual(expected, ArithmeticPulseTemplate(pt, '/', mapping).integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_simple_attributes(self): lhs = DummyPulseTemplate(defined_channels={'a', 'b'}, duration=ExpressionScalar('t_dur'), measurement_names={'m1'}) diff --git a/tests/pulses/function_pulse_tests.py b/tests/pulses/function_pulse_tests.py index ba4d214da..ab8666af2 100644 --- a/tests/pulses/function_pulse_tests.py +++ b/tests/pulses/function_pulse_tests.py @@ -84,6 +84,12 @@ def test_integral(self) -> None: pulse = FunctionPulseTemplate('sin(0.5*t+b)', '2*Tmax') self.assertEqual({'default': Expression('2.0*cos(b) - 2.0*cos(1.0*Tmax+b)')}, pulse.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_as_expression(self): pulse = FunctionPulseTemplate('sin(0.5*t+b)', '2*Tmax') expr = sympy.sin(0.5 * pulse._AS_EXPRESSION_TIME + sympy.sympify('b')) diff --git a/tests/pulses/loop_pulse_template_tests.py b/tests/pulses/loop_pulse_template_tests.py index ce7adb501..5ed289f2e 100644 --- a/tests/pulses/loop_pulse_template_tests.py +++ b/tests/pulses/loop_pulse_template_tests.py @@ -177,6 +177,12 @@ def test_integral(self) -> None: 'B': ExpressionScalar('Sum((1+2*i), (i, 0, 3))') } self.assertEqual(expected, pulse.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + class ForLoopTemplateSequencingTests(MeasurementWindowTestCase): def test_create_program_constraint_on_loop_var_exception(self): diff --git a/tests/pulses/mapping_pulse_template_tests.py b/tests/pulses/mapping_pulse_template_tests.py index 2c9d4355f..872b081fd 100644 --- a/tests/pulses/mapping_pulse_template_tests.py +++ b/tests/pulses/mapping_pulse_template_tests.py @@ -253,6 +253,12 @@ def test_integral(self) -> None: self.assertEqual({'a': Expression('2*f'), 'B': Expression('-3.2*f+2.3')}, pulse.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_as_expression(self): from sympy.abc import f, k, b duration = 5 diff --git a/tests/pulses/multi_channel_pulse_template_tests.py b/tests/pulses/multi_channel_pulse_template_tests.py index 8e8be1318..d5840ebe6 100644 --- a/tests/pulses/multi_channel_pulse_template_tests.py +++ b/tests/pulses/multi_channel_pulse_template_tests.py @@ -200,9 +200,9 @@ def test_as_expression(self): DummyPulseTemplate(duration='t1', defined_channels={'B', 'C'}, integrals={'B': ExpressionScalar('t1-t0*3.1'), 'C': ExpressionScalar('l')})] pulse = AtomicMultiChannelPulseTemplate(*sts) - self.assertEqual({'A': ExpressionScalar(sympify('(2+k) / t1') * pulse._AS_EXPRESSION_TIME), - 'B': ExpressionScalar(sympify('(t1-t0*3.1)/t1') * pulse._AS_EXPRESSION_TIME), - 'C': ExpressionScalar(sympify('l/t1') * pulse._AS_EXPRESSION_TIME)}, + self.assertEqual({'A': sts[0]._as_expression()['A'], + 'B': sts[1]._as_expression()['B'], + 'C': sts[1]._as_expression()['C']}, pulse._as_expression()) @@ -386,6 +386,12 @@ def test_integral(self): 'Z': ExpressionScalar('a*t1')} self.assertEqual(expected_integral, pccpt.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_get_overwritten_channels_values(self): template = DummyPulseTemplate(duration='t1', defined_channels={'X', 'Y'}, parameter_names={'a', 'b'}, measurement_names={'M'}) diff --git a/tests/pulses/point_pulse_template_tests.py b/tests/pulses/point_pulse_template_tests.py index 3962af181..6f60863d6 100644 --- a/tests/pulses/point_pulse_template_tests.py +++ b/tests/pulses/point_pulse_template_tests.py @@ -104,6 +104,12 @@ def test_integral(self) -> None: 'Y': 2 * (4.1 + 4) / 2 + (5 - 2) * 4}, integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + class PointPulseTemplateSequencingTests(unittest.TestCase): def test_build_waveform_empty(self): diff --git a/tests/pulses/pulse_template_tests.py b/tests/pulses/pulse_template_tests.py index d765ea8ef..882cad114 100644 --- a/tests/pulses/pulse_template_tests.py +++ b/tests/pulses/pulse_template_tests.py @@ -81,6 +81,14 @@ def measurement_names(self): def integral(self) -> Dict[ChannelID, ExpressionScalar]: raise NotImplementedError() + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + raise NotImplementedError() + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + raise NotImplementedError() + def get_appending_internal_create_program(waveform=DummyWaveform(), always_append=False, @@ -442,3 +450,12 @@ def test_internal_create_program_volatile(self): global_transformation=None) self.assertEqual(Loop(), program) + def test_integral(self): + raise NotImplementedError() + + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + diff --git a/tests/pulses/repetition_pulse_template_tests.py b/tests/pulses/repetition_pulse_template_tests.py index dbc83dd58..3c7ee25c3 100644 --- a/tests/pulses/repetition_pulse_template_tests.py +++ b/tests/pulses/repetition_pulse_template_tests.py @@ -87,6 +87,12 @@ def test_integral(self) -> None: template = RepetitionPulseTemplate(dummy, Expression('2+m')) self.assertEqual([Expression('(2+m)*(foo+2)'), Expression('(2+m)*(k*3+x**2)')], template.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_parameter_names_param_only_in_constraint(self) -> None: pt = RepetitionPulseTemplate(DummyPulseTemplate(parameter_names={'a'}), 'n', parameter_constraints=['a None: self.assertEqual({'A': ExpressionScalar('k+2*b+7*(b-f)'), 'B': ExpressionScalar('0.24*f')}, pulse.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_concatenate(self): a = DummyPulseTemplate(parameter_names={'foo'}, defined_channels={'A'}) b = DummyPulseTemplate(parameter_names={'bar'}, defined_channels={'A'}) diff --git a/tests/pulses/sequencing_dummies.py b/tests/pulses/sequencing_dummies.py index d22952bed..2e8b05065 100644 --- a/tests/pulses/sequencing_dummies.py +++ b/tests/pulses/sequencing_dummies.py @@ -271,5 +271,13 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: assert self.duration != 0 t = self._AS_EXPRESSION_TIME duration = self.duration.underlying_expression - return {ch: ExpressionScalar(integral.underlying_expression*t/duration) + return {ch: ExpressionScalar(integral.underlying_expression*t/duration**2 * 2) for ch, integral in self.integral.items()} + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.integral + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return self.integral diff --git a/tests/pulses/table_pulse_template_tests.py b/tests/pulses/table_pulse_template_tests.py index 14b9f0d1f..eeea30da7 100644 --- a/tests/pulses/table_pulse_template_tests.py +++ b/tests/pulses/table_pulse_template_tests.py @@ -475,6 +475,12 @@ def test_integral(self) -> None: self.assertEqual(expected, pulse.integral) + def test_initial_values(self): + raise NotImplementedError() + + def test_final_values(self): + raise NotImplementedError() + def test_as_expression(self): pulse = TablePulseTemplate(entries={0: [(0, 0), (1, 2), (3, 0, 'linear'), (4, 2, 'jump'), (5, 8, 'hold')], 'other_channel': [(0, 7), (2, 0, 'linear'), (10, 0)], From 1abdf5ef559884c16bf7e2229b951856040c2d0a Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Tue, 4 Oct 2022 19:25:06 +0200 Subject: [PATCH 2/6] Implement runtime error for non existing properties --- qupulse/pulses/pulse_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qupulse/pulses/pulse_template.py b/qupulse/pulses/pulse_template.py index 20056510e..2a0bb60ba 100644 --- a/qupulse/pulses/pulse_template.py +++ b/qupulse/pulses/pulse_template.py @@ -97,14 +97,14 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: """Returns an expression giving the integral over the pulse.""" @property - @abstractmethod def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: """Values of defined channels at t == 0""" + raise NotImplementedError(f"The pulse template of type {type(self)} does not implement `initial_values`") @property - @abstractmethod def final_values(self) -> Dict[ChannelID, ExpressionScalar]: """Values of defined channels at t == self.duration""" + raise NotImplementedError(f"The pulse template of type {type(self)} does not implement `final_values`") def create_program(self, *, parameters: Optional[Mapping[str, Union[Expression, str, Number, ConstantParameter]]]=None, From f41ae4bb922da42ca3341ffd5aa10290bfb2a3e3 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Tue, 4 Oct 2022 19:25:29 +0200 Subject: [PATCH 3/6] Implement more tests --- tests/pulses/arithmetic_pulse_template_tests.py | 14 ++++++++++---- tests/pulses/constant_pulse_template_tests.py | 3 +++ tests/pulses/function_pulse_tests.py | 6 ++++-- tests/pulses/repetition_pulse_template_tests.py | 8 ++++++-- tests/pulses/sequencing_dummies.py | 16 ++++++++++++++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/pulses/arithmetic_pulse_template_tests.py b/tests/pulses/arithmetic_pulse_template_tests.py index ee86a4436..d22267f0d 100644 --- a/tests/pulses/arithmetic_pulse_template_tests.py +++ b/tests/pulses/arithmetic_pulse_template_tests.py @@ -118,11 +118,17 @@ def test_integral(self): self.assertEqual(expected_plus, (lhs + rhs).integral) self.assertEqual(expected_minus, (lhs - rhs).integral) - def test_initial_values(self): - raise NotImplementedError() + def test_initial_final_values(self): + lhs = DummyPulseTemplate(initial_values={'A': .1, 'B': 'b*2'}, final_values={'A': .2, 'B': 'b / 2'}) + rhs = DummyPulseTemplate(initial_values={'A': -4, 'B': 'b*2 + 1'}, final_values={'A': .2, 'B': '-b / 2 + c'}) - def test_final_values(self): - raise NotImplementedError() + minus = lhs - rhs + plus = lhs + rhs + self.assertEqual({'A': 4.1, 'B': -1}, minus.initial_values) + self.assertEqual({'A': 0, 'B': 'b - c'}, minus.final_values) + + self.assertEqual({'A': -3.9, 'B': 'b*4 + 1'}, plus.initial_values) + self.assertEqual({'A': .4, 'B': 'c'}, plus.final_values) def test_as_expression(self): integrals_lhs = dict(a=ExpressionScalar('a_lhs'), b=ExpressionScalar('b')) diff --git a/tests/pulses/constant_pulse_template_tests.py b/tests/pulses/constant_pulse_template_tests.py index f670654c5..143afe526 100644 --- a/tests/pulses/constant_pulse_template_tests.py +++ b/tests/pulses/constant_pulse_template_tests.py @@ -25,6 +25,9 @@ def test_ConstantPulseTemplate(self): self.assertIn('ConstantPulseTemplate', str(pt)) self.assertIn('ConstantPulseTemplate', repr(pt)) + self.assertEqual({'P1': .5, 'P2': .25}, pt.initial_values) + self.assertEqual({'P1': .5, 'P2': .25}, pt.final_values) + def test_zero_duration(self): p1 = ConstantPulseTemplate(10, {'P1': 1.}) p2 = ConstantPulseTemplate(0, {'P1': 1.}) diff --git a/tests/pulses/function_pulse_tests.py b/tests/pulses/function_pulse_tests.py index 73bebb392..27a78ba5b 100644 --- a/tests/pulses/function_pulse_tests.py +++ b/tests/pulses/function_pulse_tests.py @@ -85,10 +85,12 @@ def test_integral(self) -> None: self.assertEqual({'default': Expression('2.0*cos(b) - 2.0*cos(1.0*Tmax+b)')}, pulse.integral) def test_initial_values(self): - raise NotImplementedError() + fpt = FunctionPulseTemplate('3 + exp(t * a)', 'pi', channel='A') + self.assertEqual({'A': 4}, fpt.initial_values) def test_final_values(self): - raise NotImplementedError() + fpt = FunctionPulseTemplate('3 + exp(t * a)', 'pi', channel='A') + self.assertEqual({'A': Expression('3 + exp(pi*a)')}, fpt.final_values) def test_as_expression(self): pulse = FunctionPulseTemplate('sin(0.5*t+b)', '2*Tmax') diff --git a/tests/pulses/repetition_pulse_template_tests.py b/tests/pulses/repetition_pulse_template_tests.py index ff6e20a0d..65be09327 100644 --- a/tests/pulses/repetition_pulse_template_tests.py +++ b/tests/pulses/repetition_pulse_template_tests.py @@ -88,10 +88,14 @@ def test_integral(self) -> None: self.assertEqual({'A': Expression('(2+m)*(foo+2)'), 'B': Expression('(2+m)*(k*3+x**2)')}, template.integral) def test_initial_values(self): - raise NotImplementedError() + dummy = DummyPulseTemplate(initial_values={'A': ExpressionScalar('a + 3')}) + rpt = RepetitionPulseTemplate(dummy, repetition_count='n') + self.assertEqual(dummy.initial_values, rpt.initial_values) def test_final_values(self): - raise NotImplementedError() + dummy = DummyPulseTemplate(final_values={'A': ExpressionScalar('a + 3')}) + rpt = RepetitionPulseTemplate(dummy, repetition_count='n') + self.assertEqual(dummy.final_values, rpt.final_values) def test_parameter_names_param_only_in_constraint(self) -> None: pt = RepetitionPulseTemplate(DummyPulseTemplate(parameter_names={'a'}), 'n', parameter_constraints=['a None: @@ -208,6 +210,16 @@ def __init__(self, self._program = program self._register(registry=registry) + if initial_values is None: + self._initial_values = {ch: ExpressionScalar(0) for ch in self.defined_channels} + else: + self._initial_values = {ch: ExpressionScalar(val) for ch, val in initial_values.items()} + + if final_values is None: + self._final_values = {ch: ExpressionScalar(0) for ch in self.defined_channels} + else: + self._final_values = {ch: ExpressionScalar(val) for ch, val in final_values.items()} + if integrals is not None: assert isinstance(integrals, Mapping) @@ -280,8 +292,8 @@ def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: @property def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: - return self.integral + return self._initial_values @property def final_values(self) -> Dict[ChannelID, ExpressionScalar]: - return self.integral + return self._final_values From cd14992194d18a3a027cf74f8a40a47df1c10770 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Tue, 4 Oct 2022 19:59:25 +0200 Subject: [PATCH 4/6] Fix implementations --- qupulse/pulses/constant_pulse_template.py | 8 ++++++++ qupulse/pulses/loop_pulse_template.py | 2 +- qupulse/pulses/point_pulse_template.py | 4 ++-- qupulse/pulses/pulse_template.py | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/qupulse/pulses/constant_pulse_template.py b/qupulse/pulses/constant_pulse_template.py index 7ab634d16..0266e3897 100644 --- a/qupulse/pulses/constant_pulse_template.py +++ b/qupulse/pulses/constant_pulse_template.py @@ -120,3 +120,11 @@ def build_waveform(self, if constant_values: return ConstantWaveform.from_mapping(duration, constant_values) return None + + @property + def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: + return {ch: ExpressionScalar(val) for ch, val in self._amplitude_dict.items()} + + @property + def final_values(self) -> Dict[ChannelID, ExpressionScalar]: + return {ch: ExpressionScalar(val) for ch, val in self._amplitude_dict.items()} diff --git a/qupulse/pulses/loop_pulse_template.py b/qupulse/pulses/loop_pulse_template.py index f0d10b900..d0ac0a8ed 100644 --- a/qupulse/pulses/loop_pulse_template.py +++ b/qupulse/pulses/loop_pulse_template.py @@ -300,7 +300,7 @@ def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: @property def final_values(self) -> Dict[ChannelID, ExpressionScalar]: - values = self.body.initial_values + values = self.body.final_values start, step, stop = self._loop_range.start.sympified_expression, self._loop_range.step.sympified_expression, self._loop_range.stop.sympified_expression n = (stop - start) // step final_idx = start + sympy.Max(n - 1, 0) * step diff --git a/qupulse/pulses/point_pulse_template.py b/qupulse/pulses/point_pulse_template.py index 23d520626..b5c5cfa03 100644 --- a/qupulse/pulses/point_pulse_template.py +++ b/qupulse/pulses/point_pulse_template.py @@ -172,7 +172,7 @@ def value_trafo(v): def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: shape = (len(self._channels),) return { - ch: IndexedBroadcast(self._entries[0].v, shape, ch_idx) + ch: ExpressionScalar(IndexedBroadcast(self._entries[0].v, shape, ch_idx)) for ch_idx, ch in enumerate(self._channels) } @@ -180,7 +180,7 @@ def initial_values(self) -> Dict[ChannelID, ExpressionScalar]: def final_values(self) -> Dict[ChannelID, ExpressionScalar]: shape = (len(self._channels),) return { - ch: IndexedBroadcast(self._entries[-1].v, shape, ch_idx) + ch: ExpressionScalar(IndexedBroadcast(self._entries[-1].v, shape, ch_idx)) for ch_idx, ch in enumerate(self._channels) } diff --git a/qupulse/pulses/pulse_template.py b/qupulse/pulses/pulse_template.py index 2a0bb60ba..1ed0786e4 100644 --- a/qupulse/pulses/pulse_template.py +++ b/qupulse/pulses/pulse_template.py @@ -369,7 +369,8 @@ def build_waveform(self, def _as_expression(self) -> Dict[ChannelID, ExpressionScalar]: """Helper function to allow integral calculation in case of truncation. AtomicPulseTemplate._AS_EXPRESSION_TIME is by convention the time variable.""" - raise NotImplementedError(f"_as_expression is not implemented for {type(self)} which means it cannot be truncated and integrated over.") + raise NotImplementedError(f"_as_expression is not implemented for {type(self)} " + f"which means it cannot be truncated and integrated over.") @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: From b9f5e8cf31e24fae225ad22de1f024234229493d Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Tue, 4 Oct 2022 19:59:38 +0200 Subject: [PATCH 5/6] Fix tests --- .../pulses/arithmetic_pulse_template_tests.py | 8 ++++-- tests/pulses/loop_pulse_template_tests.py | 11 ++++++-- tests/pulses/mapping_pulse_template_tests.py | 19 +++++++------- .../multi_channel_pulse_template_tests.py | 8 ++++-- tests/pulses/point_pulse_template_tests.py | 25 ++++++++++++++++--- tests/pulses/pulse_template_tests.py | 10 -------- tests/pulses/sequence_pulse_template_tests.py | 11 ++++---- tests/pulses/table_pulse_template_tests.py | 11 ++++---- 8 files changed, 64 insertions(+), 39 deletions(-) diff --git a/tests/pulses/arithmetic_pulse_template_tests.py b/tests/pulses/arithmetic_pulse_template_tests.py index d22267f0d..87c1f65b6 100644 --- a/tests/pulses/arithmetic_pulse_template_tests.py +++ b/tests/pulses/arithmetic_pulse_template_tests.py @@ -484,10 +484,14 @@ def test_integral(self): self.assertEqual(expected, ArithmeticPulseTemplate(pt, '/', mapping).integral) def test_initial_values(self): - raise NotImplementedError() + lhs = DummyPulseTemplate(initial_values={'A': .3, 'B': 'b'}, defined_channels={'A', 'B'}) + apt = lhs + 'a' + self.assertEqual({'A': 'a + 0.3', 'B': 'b + a'}, apt.initial_values) def test_final_values(self): - raise NotImplementedError() + lhs = DummyPulseTemplate(final_values={'A': .3, 'B': 'b'}, defined_channels={'A', 'B'}) + apt = lhs - 'a' + self.assertEqual({'A': '-a + .3', 'B': 'b - a'}, apt.final_values) def test_simple_attributes(self): lhs = DummyPulseTemplate(defined_channels={'a', 'b'}, duration=ExpressionScalar('t_dur'), diff --git a/tests/pulses/loop_pulse_template_tests.py b/tests/pulses/loop_pulse_template_tests.py index 2fb4d8651..65341dbd7 100644 --- a/tests/pulses/loop_pulse_template_tests.py +++ b/tests/pulses/loop_pulse_template_tests.py @@ -178,10 +178,17 @@ def test_integral(self) -> None: self.assertEqual(expected, pulse.integral) def test_initial_values(self): - raise NotImplementedError() + dpt = DummyPulseTemplate(initial_values={'A': 'a + 3 + i', 'B': 7}, parameter_names={'i', 'a'}) + fpt = ForLoopPulseTemplate(dpt, 'i', (1, 'n', 2)) + self.assertEqual({'A': 'a+4', 'B': 7}, fpt.initial_values) def test_final_values(self): - raise NotImplementedError() + dpt = DummyPulseTemplate(final_values={'A': 'a + 3 + i', 'B': 7}, parameter_names={'i', 'a'}) + fpt = ForLoopPulseTemplate(dpt, 'i', 'n') + self.assertEqual({'A': 'a+3+Max(0, floor(n) - 1)', 'B': 7}, fpt.final_values) + + fpt_fin = ForLoopPulseTemplate(dpt, 'i', (1, 'n', 2)).final_values + self.assertEqual('a + 10', fpt_fin['A'].evaluate_symbolic({'n': 8})) class ForLoopTemplateSequencingTests(MeasurementWindowTestCase): diff --git a/tests/pulses/mapping_pulse_template_tests.py b/tests/pulses/mapping_pulse_template_tests.py index b95ebdd73..2fb5e8af7 100644 --- a/tests/pulses/mapping_pulse_template_tests.py +++ b/tests/pulses/mapping_pulse_template_tests.py @@ -255,19 +255,20 @@ def test_integral(self) -> None: self.assertEqual({'a': Expression('2*f'), 'B': Expression('-3.2*f+2.3')}, pulse.integral) - def test_initial_values(self): - raise NotImplementedError() - - def test_final_values(self): - raise NotImplementedError() + def test_initial_final_values(self): + dpt = DummyPulseTemplate(initial_values={'A': 'a', 'B': 'b'}, final_values={'A': 'a + c', 'B': 'b - 3'}, + parameter_names=set('abc')) + mapped = MappingPulseTemplate(dpt, parameter_mapping={'a': 'c'}, allow_partial_parameter_mapping=True) + self.assertEqual({'A': 'c', 'B': 'b'}, mapped.initial_values) + self.assertEqual({'A': 'c+c', 'B': 'b-3'}, mapped.final_values) def test_as_expression(self): from sympy.abc import f, k, b duration = 5 dummy = DummyPulseTemplate(defined_channels={'A', 'B', 'C'}, parameter_names={'k', 'f', 'b'}, - integrals={'A': Expression(2 * k), - 'B': Expression(-3.2*f+b), + integrals={'A': Expression(k), + 'B': Expression(f+b), 'C': Expression(1)}, duration=duration) t = DummyPulseTemplate._AS_EXPRESSION_TIME dummy_expr = {ch: i * t / duration for ch, i in dummy._integrals.items()} @@ -276,8 +277,8 @@ def test_as_expression(self): allow_partial_parameter_mapping=True) expected = { - 'a': Expression(2*f*t/duration), - 'B': Expression((-3.2*f + 2.3)*t/duration), + 'a': Expression(t*f/duration**2 * 2), + 'B': Expression((f + 2.3)*t/duration**2 * 2), } self.assertEqual(expected, pulse._as_expression()) diff --git a/tests/pulses/multi_channel_pulse_template_tests.py b/tests/pulses/multi_channel_pulse_template_tests.py index e08b7de5f..84904a8de 100644 --- a/tests/pulses/multi_channel_pulse_template_tests.py +++ b/tests/pulses/multi_channel_pulse_template_tests.py @@ -377,10 +377,14 @@ def test_integral(self): self.assertEqual(expected_integral, pccpt.integral) def test_initial_values(self): - raise NotImplementedError() + dpt = DummyPulseTemplate(initial_values={'A': 'a', 'B': 'b'}) + par = ParallelConstantChannelPulseTemplate(dpt, {'B': 'b2', 'C': 'c'}) + self.assertEqual({'A': 'a', 'B': 'b2', 'C': 'c'}, par.initial_values) def test_final_values(self): - raise NotImplementedError() + dpt = DummyPulseTemplate(final_values={'A': 'a', 'B': 'b'}) + par = ParallelConstantChannelPulseTemplate(dpt, {'B': 'b2', 'C': 'c'}) + self.assertEqual({'A': 'a', 'B': 'b2', 'C': 'c'}, par.final_values) def test_get_overwritten_channels_values(self): template = DummyPulseTemplate(duration='t1', defined_channels={'X', 'Y'}, parameter_names={'a', 'b'}, diff --git a/tests/pulses/point_pulse_template_tests.py b/tests/pulses/point_pulse_template_tests.py index 831ce1603..a259246e0 100644 --- a/tests/pulses/point_pulse_template_tests.py +++ b/tests/pulses/point_pulse_template_tests.py @@ -104,11 +104,28 @@ def test_integral(self) -> None: 'Y': 2 * (4.1 + 4) / 2 + (5 - 2) * 4}, integral) - def test_initial_values(self): - raise NotImplementedError() + def test_initial_final_values(self): + pulse = PointPulseTemplate( + [(1, (2, 'b'), 'hold'), + (3, (0, 0), 'linear'), + (4, (2, 'c'), 'jump'), + (5, (8, 'd'), 'hold')], + [0, 'other_channel'] + ) + self.assertEqual({0: 2, 'other_channel': 'b'}, pulse.initial_values) + self.assertEqual({0: 8, 'other_channel': 'd'}, pulse.final_values) - def test_final_values(self): - raise NotImplementedError() + pulse = PointPulseTemplate( + [(1, 'b', 'hold'), + (3, (0, 0), 'linear'), + (4, (2, 'c'), 'jump'), + (5, 'd', 'hold')], + [0, 'other_channel'] + ) + self.assertEqual({0: 'IndexedBroadcast(b, (2,), 0)', 'other_channel': 'IndexedBroadcast(b, (2,), 1)'}, + pulse.initial_values) + self.assertEqual({0: 'IndexedBroadcast(d, (2,), 0)', 'other_channel': 'IndexedBroadcast(d, (2,), 1)'}, + pulse.final_values) class PointPulseTemplateSequencingTests(unittest.TestCase): diff --git a/tests/pulses/pulse_template_tests.py b/tests/pulses/pulse_template_tests.py index 65bb33538..0e8210bf2 100644 --- a/tests/pulses/pulse_template_tests.py +++ b/tests/pulses/pulse_template_tests.py @@ -453,13 +453,3 @@ def test_internal_create_program_volatile(self): to_single_waveform=set(), global_transformation=None) self.assertEqual(Loop(), program) - - def test_integral(self): - raise NotImplementedError() - - def test_initial_values(self): - raise NotImplementedError() - - def test_final_values(self): - raise NotImplementedError() - diff --git a/tests/pulses/sequence_pulse_template_tests.py b/tests/pulses/sequence_pulse_template_tests.py index 82533c9e1..57f8d81ed 100644 --- a/tests/pulses/sequence_pulse_template_tests.py +++ b/tests/pulses/sequence_pulse_template_tests.py @@ -110,11 +110,12 @@ def test_integral(self) -> None: self.assertEqual({'A': ExpressionScalar('k+2*b+7*(b-f)'), 'B': ExpressionScalar('0.24*f')}, pulse.integral) - def test_initial_values(self): - raise NotImplementedError() - - def test_final_values(self): - raise NotImplementedError() + def test_initial_final_values(self): + pt1 = DummyPulseTemplate(initial_values={'A': 'a'}) + pt2 = DummyPulseTemplate(final_values={'A': 'b'}) + spt = pt1 @ pt2 + self.assertEqual(pt1.initial_values, spt.initial_values) + self.assertEqual(pt2.final_values, spt.final_values) def test_concatenate(self): a = DummyPulseTemplate(parameter_names={'foo'}, defined_channels={'A'}) diff --git a/tests/pulses/table_pulse_template_tests.py b/tests/pulses/table_pulse_template_tests.py index 43d120fcf..aedc371f0 100644 --- a/tests/pulses/table_pulse_template_tests.py +++ b/tests/pulses/table_pulse_template_tests.py @@ -475,11 +475,12 @@ def test_integral(self) -> None: self.assertEqual(expected, pulse.integral) - def test_initial_values(self): - raise NotImplementedError() - - def test_final_values(self): - raise NotImplementedError() + def test_initial_final_values(self): + pulse = TablePulseTemplate(entries={0: [(1, 2), (3, 0, 'linear'), (4, 2, 'jump'), (5, 8, 'hold')], + 'other_channel': [(0, 7), (2, 0, 'linear'), (10, 0)], + 'symbolic': [(3, 'a'), ('b', 4, 'hold'), ('c', Expression('d'), 'linear')]}) + self.assertEqual({0: 2, 'other_channel': 7, 'symbolic': 'a'}, pulse.initial_values) + self.assertEqual({0: 8, 'other_channel': 0, 'symbolic': 'd'}, pulse.final_values) def test_as_expression(self): pulse = TablePulseTemplate(entries={0: [(0, 0), (1, 2), (3, 0, 'linear'), (4, 2, 'jump'), (5, 8, 'hold')], From ac42b1584b1920c71b42b47ef8a5685852973e96 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Wed, 5 Oct 2022 11:13:45 +0200 Subject: [PATCH 6/6] Add newspiece --- changes.d/549.feature | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changes.d/549.feature diff --git a/changes.d/549.feature b/changes.d/549.feature new file mode 100644 index 000000000..8850b4f0d --- /dev/null +++ b/changes.d/549.feature @@ -0,0 +1,4 @@ +Add `initial_values` and `final_values` attributes to `PulseTemplate`. + +This allows pulse template construction that depends on features of arbitrary existing pulses i.e. like extension until +a certain length.