Skip to content

Commit

Permalink
Merge pull request #802 from qutech/issues/801_pad_to
Browse files Browse the repository at this point in the history
Add PulseTemplate.pad_to
  • Loading branch information
terrorfisch authored Oct 12, 2023
2 parents 7e13c34 + f06cd64 commit bcab024
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 2 deletions.
2 changes: 2 additions & 0 deletions changes.d/801.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``PulseTemplate.pad_to`` method to help padding to minimal lengths or multiples of given durations.

41 changes: 41 additions & 0 deletions qupulse/pulses/pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,47 @@ def with_appended(self, *appended: 'PulseTemplate'):
else:
return self

def pad_to(self, to_new_duration: Union[ExpressionLike, Callable[[Expression], ExpressionLike]],
pt_kwargs: Mapping[str, Any] = None) -> 'PulseTemplate':
"""Pad this pulse template to the given duration.
The target duration can be numeric, symbolic or a callable that returns a new duration from the current
duration.
Examples:
# pad to a fixed duration
>>> padded_1 = my_pt.pad_to(1000)
# pad to a fixed sample coun
>>> padded_2 = my_pt.pad_to('sample_rate * 1000')
# pad to the next muliple of 16 samples with a symbolic sample rate
>>> padded_3 = my_pt.pad_to(to_next_multiple('sample_rate', 16))
# pad to the next muliple of 16 samples with a fixed sample rate of 1 GHz
>>> padded_4 = my_pt.pad_to(to_next_multiple(1, 16))
Args:
to_new_duration: Duration or callable that maps the current duration to the new duration
pt_kwargs: Keyword arguments for the newly created sequence pulse template.
Returns:
A pulse template that has the duration given by ``to_new_duration``. It can be ``self`` if the duration is
already as required. It is never ``self`` if ``pt_kwargs`` is non-empty.
"""
from qupulse.pulses import ConstantPT, SequencePT
current_duration = self.duration
if callable(to_new_duration):
new_duration = to_new_duration(current_duration)
else:
new_duration = ExpressionScalar(to_new_duration)
pad_duration = new_duration - current_duration
if not pt_kwargs and pad_duration == 0:
return self
pad_pt = ConstantPT(pad_duration, self.final_values)
if pt_kwargs:
return SequencePT(self, pad_pt, **pt_kwargs)
else:
return self @ pad_pt

def __format__(self, format_spec: str):
if format_spec == '':
format_spec = self._DEFAULT_FORMAT_SPEC
Expand Down
79 changes: 77 additions & 2 deletions tests/pulses/pulse_template_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from unittest import mock

from typing import Optional, Dict, Set, Any, Union

import frozendict
import sympy

from qupulse.parameter_scope import Scope, DictScope
Expand All @@ -23,19 +25,22 @@

class PulseTemplateStub(PulseTemplate):
"""All abstract methods are stubs that raise NotImplementedError to catch unexpected calls. If a method is needed in
a test one should use mock.patch or mock.patch.object"""
a test one should use mock.patch or mock.patch.object.
Properties can be passed as init argument because mocking them is a pita."""
def __init__(self, identifier=None,
defined_channels=None,
duration=None,
parameter_names=None,
measurement_names=None,
final_values=None,
registry=None):
super().__init__(identifier=identifier)

self._defined_channels = defined_channels
self._duration = duration
self._parameter_names = parameter_names
self._measurement_names = set() if measurement_names is None else measurement_names
self._final_values = final_values
self.internal_create_program_args = []
self._register(registry=registry)

Expand Down Expand Up @@ -89,7 +94,10 @@ def initial_values(self) -> Dict[ChannelID, ExpressionScalar]:

@property
def final_values(self) -> Dict[ChannelID, ExpressionScalar]:
raise NotImplementedError()
if self._final_values is None:
raise NotImplementedError()
else:
return self._final_values


def get_appending_internal_create_program(waveform=DummyWaveform(),
Expand Down Expand Up @@ -343,6 +351,73 @@ def test_create_program_volatile(self):

_internal_create_program.assert_called_once_with(**expected_internal_kwargs, parent_loop=Loop())

def test_pad_to(self):
from qupulse.pulses import SequencePT

def to_multiple_of_192(x: Expression) -> Expression:
return (x + 191) // 192 * 192

final_values = frozendict.frozendict({'A': ExpressionScalar(0.1), 'B': ExpressionScalar('a')})
measurements = [('M', 0, 'y')]

pt = PulseTemplateStub(duration=ExpressionScalar(10))
padded = pt.pad_to(10)
self.assertIs(pt, padded)

pt = PulseTemplateStub(duration=ExpressionScalar('duration'))
padded = pt.pad_to('duration')
self.assertIs(pt, padded)

# padding with numeric durations

pt = PulseTemplateStub(duration=ExpressionScalar(10),
final_values=final_values,
defined_channels=final_values.keys())
padded = pt.pad_to(20)
self.assertEqual(padded.duration, 20)
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)

padded = pt.pad_to(20, pt_kwargs=dict(measurements=measurements))
self.assertEqual(padded.duration, 20)
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)
self.assertEqual(measurements, padded.measurement_declarations)

padded = pt.pad_to(10, pt_kwargs=dict(measurements=measurements))
self.assertEqual(padded.duration, 10)
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)
self.assertEqual(measurements, padded.measurement_declarations)

# padding with numeric duation and callable
padded = pt.pad_to(to_multiple_of_192)
self.assertEqual(padded.duration, 192)
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)

# padding with symbolic durations

pt = PulseTemplateStub(duration=ExpressionScalar('duration'),
final_values=final_values,
defined_channels=final_values.keys())
padded = pt.pad_to('new_duration')
self.assertEqual(padded.duration, 'new_duration')
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)

# padding symbolic durations with callable

padded = pt.pad_to(to_multiple_of_192)
self.assertEqual(padded.duration, '(duration + 191) // 192 * 192')
self.assertEqual(padded.final_values, final_values)
self.assertIsInstance(padded, SequencePT)
self.assertIs(padded.subtemplates[0], pt)

def test_create_program_none(self) -> None:
template = PulseTemplateStub(defined_channels={'A'}, parameter_names={'foo'})
Expand Down

0 comments on commit bcab024

Please sign in to comment.