Skip to content

Commit

Permalink
First part of program builder based pt translation
Browse files Browse the repository at this point in the history
  • Loading branch information
terrorfisch committed Aug 20, 2023
1 parent 19e23df commit 8bd76be
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 71 deletions.
40 changes: 23 additions & 17 deletions qupulse/pulses/loop_pulse_template.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""This module defines LoopPulseTemplate, a higher-order hierarchical pulse template that loops
another PulseTemplate based on a condition."""
import dataclasses
import functools
import itertools
from abc import ABC
Expand All @@ -13,7 +14,7 @@
from qupulse.parameter_scope import Scope, MappedScope, DictScope
from qupulse.utils.types import FrozenDict, FrozenMapping

from qupulse._program._loop import Loop
from qupulse.program import ProgramBuilder

from qupulse.expressions import ExpressionScalar, ExpressionVariableMissingException, Expression
from qupulse.utils import checked_int_cast, cached_property
Expand Down Expand Up @@ -149,26 +150,25 @@ def _internal_create_program(self, *,
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional['Transformation'],
to_single_waveform: Set[Union[str, 'PulseTemplate']],
parent_loop: Loop) -> None:
program_builder: ProgramBuilder) -> None:
self.validate_scope(scope=scope)

try:
duration = self.duration.evaluate_in_scope(scope)
except ExpressionVariableMissingException as err:
raise ParameterNotProvidedException(err.variable) from err
measurements = self.get_measurement_windows(scope, measurement_mapping)
loop_range = self._loop_range.to_range(scope)
loop_index_name = self._loop_index

with program_builder.with_iteration(loop_index_name, loop_range) as iteration_program_builder:
iteration_program_builder.measure(measurements)

if duration > 0:
measurements = self.get_measurement_windows(scope, measurement_mapping)
if measurements:
parent_loop.add_measurements(measurements)
# todo: create specialized scope?
inner_scope = MappedScope(scope, {loop_index_name: _ForLoopIndexValue(loop_index_name, loop_range)})

for local_scope in self._body_scope_generator(scope, forward=True):
self.body._create_program(scope=local_scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
parent_loop=parent_loop)
self.body._create_program(scope=inner_scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
program_builder=iteration_program_builder)

def build_waveform(self, parameter_scope: Scope) -> ForLoopWaveform:
return ForLoopWaveform([self.body.build_waveform(local_scope)
Expand Down Expand Up @@ -255,3 +255,9 @@ def __str__(self) -> str:


_ForLoopScope = RangeScope


@dataclasses.dataclass
class _ForLoopIndexValue:
name: str
rng: range
54 changes: 22 additions & 32 deletions qupulse/pulses/pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from qupulse.pulses.measurement import MeasurementDefiner, MeasurementDeclaration
from qupulse.parameter_scope import Scope, DictScope

from qupulse.program import ProgramBuilder

__all__ = ["PulseTemplate", "AtomicPulseTemplate", "DoubleParameterNameException", "MappingTuple",
"UnknownVolatileParameter"]

Expand Down Expand Up @@ -117,7 +119,8 @@ def create_program(self, *,
channel_mapping: Optional[Mapping[ChannelID, Optional[ChannelID]]]=None,
global_transformation: Optional[Transformation]=None,
to_single_waveform: Set[Union[str, 'PulseTemplate']]=None,
volatile: Union[Set[str], str] = None) -> Optional['Loop']:
volatile: Union[Set[str], str] = None,
program_builder: ProgramBuilder) -> Optional['Loop']:
"""Translates this PulseTemplate into a program Loop.
The returned Loop represents the PulseTemplate with all parameter values instantiated provided as dictated by
Expand Down Expand Up @@ -178,19 +181,20 @@ def create_program(self, *,
category=UnknownVolatileParameter,
stacklevel=2)

root_loop = Loop()
if program_builder is None:
program_builder = Loop()

# call subclass specific implementation
self._create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=complete_channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
parent_loop=root_loop)
program_builder=program_builder)

if root_loop.waveform is None and len(root_loop.children) == 0:
if program_builder.waveform is None and len(program_builder.children) == 0:
return None # return None if no program
return root_loop
return program_builder.to_program()

@abstractmethod
def _internal_create_program(self, *,
Expand All @@ -199,7 +203,7 @@ def _internal_create_program(self, *,
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional[Transformation],
to_single_waveform: Set[Union[str, 'PulseTemplate']],
parent_loop: Loop) -> None:
program_builder: ProgramBuilder) -> None:
"""The subclass specific implementation of create_program().
Receives a Loop instance parent_loop to which it should append measurements and its own Loops as children.
Expand All @@ -220,44 +224,30 @@ def _create_program(self, *,
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional[Transformation],
to_single_waveform: Set[Union[str, 'PulseTemplate']],
parent_loop: Loop):
program_builder: ProgramBuilder):
"""Generic part of create program. This method handles to_single_waveform and the configuration of the
transformer."""
if self.identifier in to_single_waveform or self in to_single_waveform:
root = Loop()
with program_builder.new_subprogram() as inner_program_builder:

if not scope.get_volatile_parameters().keys().isdisjoint(self.parameter_names):
raise NotImplementedError('A pulse template that has volatile parameters cannot be transformed into a '
'single waveform yet.')

self._internal_create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=None,
to_single_waveform=to_single_waveform,
parent_loop=root)
if not scope.get_volatile_parameters().keys().isdisjoint(self.parameter_names):
raise NotImplementedError('A pulse template that has volatile parameters cannot be transformed into a '
'single waveform yet.')

waveform = to_waveform(root)

if global_transformation:
waveform = TransformingWaveform.from_transformation(waveform, global_transformation)

# convert the nicely formatted measurement windows back into the old format again :(
measurements = root.get_measurement_windows()
measurement_window_list = []
for measurement_name, (begins, lengths) in measurements.items():
measurement_window_list.extend(zip(itertools.repeat(measurement_name), begins, lengths))

parent_loop.add_measurements(measurement_window_list)
parent_loop.append_child(waveform=waveform)
self._internal_create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
program_builder=inner_program_builder)

else:
self._internal_create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
to_single_waveform=to_single_waveform,
global_transformation=global_transformation,
parent_loop=parent_loop)
program_builder=program_builder)

def with_parallel_channels(self, values: Mapping[ChannelID, ExpressionLike]) -> 'PulseTemplate':
"""Create a new pulse template that sets the given channels to the corresponding values.
Expand Down
27 changes: 12 additions & 15 deletions qupulse/pulses/repetition_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from qupulse.serialization import Serializer, PulseRegistryType
from qupulse._program._loop import Loop, VolatileRepetitionCount
from qupulse.program import ProgramBuilder
from qupulse.parameter_scope import Scope

from qupulse.utils.types import ChannelID
Expand Down Expand Up @@ -118,33 +119,29 @@ def _internal_create_program(self, *,
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional['Transformation'],
to_single_waveform: AbstractSet[Union[str, 'PulseTemplate']],
parent_loop: Loop) -> None:
program_builder: ProgramBuilder) -> None:
self.validate_scope(scope)

repetition_count = max(0, self.get_repetition_count_value(scope))

# todo (2018-07-19): could in some circumstances possibly just multiply subprogram repetition count?
# could be tricky if any repetition count is volatile ? check later and optimize if necessary
if repetition_count > 0:
if scope.get_volatile_parameters().keys() & self.repetition_count.variables:
repetition_definition = VolatileRepetitionCount(self.repetition_count, scope)
assert int(repetition_definition) == repetition_count
else:
repetition_definition = repetition_count

repj_loop = Loop(repetition_count=repetition_definition)
self.body._create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
parent_loop=repj_loop)
if repj_loop.waveform is not None or len(repj_loop.children) > 0:
measurements = self.get_measurement_windows(scope, measurement_mapping)
if measurements:
parent_loop.add_measurements(measurements)
measurements = self.get_measurement_windows(scope, measurement_mapping)

parent_loop.append_child(loop=repj_loop)
with program_builder.with_repetition(repetition_definition) as repetition_program_builder:
if measurements:
repetition_program_builder.measure(measurements)
self.body._create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
program_builder=repetition_program_builder)

def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]:
data = super().get_serialization_data(serializer)
Expand Down
13 changes: 6 additions & 7 deletions qupulse/pulses/sequence_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import warnings

from qupulse.serialization import Serializer, PulseRegistryType
from qupulse._program._loop import Loop
from qupulse.program import ProgramBuilder
from qupulse.parameter_scope import Scope
from qupulse.utils import cached_property
from qupulse.utils.types import MeasurementWindow, ChannelID, TimeType
Expand Down Expand Up @@ -133,21 +133,20 @@ def _internal_create_program(self, *,
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional['Transformation'],
to_single_waveform: Set[Union[str, 'PulseTemplate']],
parent_loop: Loop) -> None:
program_builder: ProgramBuilder) -> None:
self.validate_scope(scope)

if self.duration.evaluate_in_scope(scope) > 0:
measurements = self.get_measurement_windows(scope, measurement_mapping)
measurements = self.get_measurement_windows(scope, measurement_mapping)
with program_builder.with_sequence() as sequence_program_builder:
if measurements:
parent_loop.add_measurements(measurements)

sequence_program_builder.measure(measurements)
for subtemplate in self.subtemplates:
subtemplate._create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform,
parent_loop=parent_loop)
program_builder=sequence_program_builder)

def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]:
data = super().get_serialization_data(serializer)
Expand Down

0 comments on commit 8bd76be

Please sign in to comment.