Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to (_internal_)create_program() #327

Merged
41 changes: 22 additions & 19 deletions qctoolkit/pulses/loop_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from qctoolkit.expressions import ExpressionScalar
from qctoolkit.utils import checked_int_cast
from qctoolkit.pulses.parameters import Parameter, ConstantParameter, InvalidParameterNameException, ParameterConstrainer
from qctoolkit.pulses.parameters import Parameter, ConstantParameter, InvalidParameterNameException, ParameterConstrainer, ParameterNotProvidedException
from qctoolkit.pulses.pulse_template import PulseTemplate, ChannelID
from qctoolkit.pulses.conditions import Condition, ConditionMissingException
from qctoolkit._program.instructions import InstructionBlock
Expand Down Expand Up @@ -228,28 +228,30 @@ def build_sequence(self,
channel_mapping=channel_mapping,
target_block=instruction_block)

def _internal_create_program(self,
def _internal_create_program(self, *,
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]:
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
self.validate_parameter_constraints(parameters=parameters)

measurement_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.measurement_parameters}
try:
measurement_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.measurement_parameters}
duration_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.duration.variables}
except KeyError as e:
raise ParameterNotProvidedException(str(e)) from e
measurements = self.get_measurement_windows(measurement_parameters, measurement_mapping)
if self.duration.evaluate_numeric(**duration_parameters) > 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a better way. But this is an optimization for the future.

if measurements:
parent_loop.add_measurements(measurements)

subprograms = []
for local_parameters in self._body_parameter_generator(parameters, forward=True):
subprogram = self.body.create_program(local_parameters,
measurement_mapping,
channel_mapping)
if subprogram:
subprograms.append(subprogram)

if subprograms or measurements:
return Loop(children=subprograms, measurements=measurements)
else:
return None
for local_parameters in self._body_parameter_generator(parameters, forward=True):
self.body._internal_create_program(parameters=local_parameters,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
parent_loop=parent_loop)

def build_waveform(self, parameters: Dict[str, Parameter]) -> ForLoopWaveform:
return ForLoopWaveform([self.body.build_waveform(local_parameters)
Expand Down Expand Up @@ -374,10 +376,11 @@ def build_sequence(self,
channel_mapping,
instruction_block)

def _internal_create_program(self,
def _internal_create_program(self, *, # pragma: no cover
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]:
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
raise NotImplementedError("create_program() does not handle conditions/triggers right now and cannot "
"be meaningfully implemented for a WhileLoopPulseTemplate")

Expand Down
12 changes: 7 additions & 5 deletions qctoolkit/pulses/mapping_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,16 @@ def build_sequence(self,
channel_mapping=self.get_updated_channel_mapping(channel_mapping),
instruction_block=instruction_block)

def _internal_create_program(self,
def _internal_create_program(self, *,
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]:
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
# parameters are validated in map_parameters() call, no need to do it here again explicitly
return self.template.create_program(parameters=self.map_parameters(parameters),
measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping),
channel_mapping=self.get_updated_channel_mapping(channel_mapping))
self.template._internal_create_program(parameters=self.map_parameters(parameters),
measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping),
channel_mapping=self.get_updated_channel_mapping(channel_mapping),
parent_loop=parent_loop)

def build_waveform(self,
parameters: Dict[str, numbers.Real],
Expand Down
18 changes: 15 additions & 3 deletions qctoolkit/pulses/multi_channel_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from qctoolkit.pulses.pulse_template import PulseTemplate, AtomicPulseTemplate
from qctoolkit.pulses.mapping_pulse_template import MappingPulseTemplate, MappingTuple
from qctoolkit.pulses.parameters import Parameter, ParameterConstrainer
from qctoolkit.pulses.measurement import MeasurementDeclaration
from qctoolkit.pulses.measurement import MeasurementDeclaration, MeasurementWindow
from qctoolkit.expressions import Expression, ExpressionScalar

__all__ = ["AtomicMultiChannelPulseTemplate"]
Expand Down Expand Up @@ -85,7 +85,9 @@ def duration(self) -> Expression:

@property
def parameter_names(self) -> Set[str]:
return set.union(*(st.parameter_names for st in self._subtemplates)) | self.constrained_parameters
return set.union(self.measurement_parameters,
self.constrained_parameters,
*(st.parameter_names for st in self._subtemplates))

@property
def subtemplates(self) -> Sequence[AtomicPulseTemplate]:
Expand All @@ -97,7 +99,7 @@ def defined_channels(self) -> Set[ChannelID]:

@property
def measurement_names(self) -> Set[str]:
return set.union(*(st.measurement_names for st in self._subtemplates))
return super().measurement_names.union(*(st.measurement_names for st in self._subtemplates))

def build_waveform(self, parameters: Dict[str, numbers.Real],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional['MultiChannelWaveform']:
Expand All @@ -117,6 +119,16 @@ def build_waveform(self, parameters: Dict[str, numbers.Real],
else:
return MultiChannelWaveform(sub_waveforms)

def get_measurement_windows(self,
parameters: Dict[str, numbers.Real],
measurement_mapping: Dict[str, Optional[str]]) -> List[MeasurementWindow]:
measurements = super().get_measurement_windows(parameters=parameters,
measurement_mapping=measurement_mapping)
for st in self.subtemplates:
measurements.extend(st.get_measurement_windows(parameters=parameters,
measurement_mapping=measurement_mapping))
return measurements

def requires_stop(self,
parameters: Dict[str, Parameter],
conditions: Dict[str, 'Condition']) -> bool:
Expand Down
33 changes: 25 additions & 8 deletions qctoolkit/pulses/pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


from qctoolkit.pulses.conditions import Condition
from qctoolkit.pulses.parameters import Parameter, ConstantParameter
from qctoolkit.pulses.parameters import Parameter, ConstantParameter, ParameterNotProvidedException
from qctoolkit.pulses.sequencing import Sequencer, SequencingElement, InstructionBlock
from qctoolkit._program.waveforms import Waveform
from qctoolkit.pulses.measurement import MeasurementDefiner, MeasurementDeclaration
Expand Down Expand Up @@ -118,7 +118,7 @@ def create_program(self, *,
if parameters is None:
parameters = dict()
if measurement_mapping is None:
measurement_mapping = dict()
measurement_mapping = {name: name for name in self.measurement_names}
if channel_mapping is None:
channel_mapping = dict()

Expand All @@ -133,6 +133,9 @@ def create_program(self, *,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
parent_loop=root_loop)

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

@abstractmethod
Expand All @@ -149,7 +152,11 @@ def _internal_create_program(self, *,
is called by create_program().
Implementations should not call create_program() of any subtemplates to obtain Loop objects for them but
call subtemplate._internal_create_program() instead, providing an adequate parent_loop object to which
the subtemplate will append."""
the subtemplate will append. Implementations must make sure not to append invalid Loop objects (no waveform or no children).

In case of an error (e.g. invalid measurement mapping, missing parameters, violated parameter constraints, etc),
implementations of this method must throw an adequate exception. They do not have to ensure that the parent_loop
remains unchanged in this case."""


class AtomicPulseTemplate(PulseTemplate, MeasurementDefiner):
Expand Down Expand Up @@ -190,19 +197,25 @@ def build_sequence(self,
instruction_block.add_instruction_meas(measurements)
instruction_block.add_instruction_exec(waveform)

def _internal_create_program(self,
def _internal_create_program(self, *,
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
# todo (2018-07-05): why are parameter constraints not validated here?
parameters = {parameter_name: parameter_value.get_value()
for parameter_name, parameter_value in parameters.items()
if parameter_name in self.parameter_names}
try:
parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.parameter_names}

measurement_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.measurement_parameters}
except KeyError as e:
raise ParameterNotProvidedException(str(e)) from e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you create an exta dictionary for the measurement parameters? They should be a subset of parameter_names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be but currently aren't according to #230


measurements = self.get_measurement_windows(parameters=measurement_parameters, measurement_mapping=measurement_mapping)
waveform = self.build_waveform(parameters,
channel_mapping=channel_mapping)
if waveform:
measurements = self.get_measurement_windows(parameters=parameters, measurement_mapping=measurement_mapping)
parent_loop.append_child(waveform=waveform, measurements=measurements)


Expand All @@ -212,6 +225,10 @@ def build_waveform(self,
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Waveform]:
"""Translate this PulseTemplate into a waveform according to the given parameters.


Subclasses of AtomicPulseTemplate must check for ParameterConstraintViolation
errors in their build_waveform implementation and raise corresponding exceptions.

Args:
parameters (Dict(str -> Parameter)): A mapping of parameter names to real numbers.
channel_mapping (Dict(ChannelID -> ChannelID): A mapping of Channel IDs
Expand Down
34 changes: 18 additions & 16 deletions qctoolkit/pulses/repetition_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,32 +127,34 @@ def build_sequence(self,
sequencer.push(self.body, parameters=parameters, conditions=conditions,
window_mapping=measurement_mapping, channel_mapping=channel_mapping, target_block=body_block)

def _internal_create_program(self,
def _internal_create_program(self, *,
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]:
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
self.validate_parameter_constraints(parameters=parameters)
relevant_parames = set(self._repetition_count.variables).union(self.measurement_parameters)
relevant_params = set(self._repetition_count.variables).union(self.measurement_parameters)
try:
real_parameters = {v: parameters[v].get_value() for v in relevant_parames}
except KeyError:
raise ParameterNotProvidedException(next(v for v in self.repetition_count.variables if v not in parameters))
real_parameters = {v: parameters[v].get_value() for v in relevant_params}
except KeyError as e:
raise ParameterNotProvidedException(str(e)) from e

repetition_count = max(0, self.get_repetition_count_value(real_parameters))
measurements = self.get_measurement_windows(real_parameters, measurement_mapping)
subprograms = []

# todo (2018-07-19): could in some circumstances possibly just multiply subprogram repetition count
# 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:
subprogram = self.body.create_program(parameters,
measurement_mapping,
channel_mapping)
if subprogram is not None:
subprograms = [subprogram]
if measurements or (subprograms and repetition_count > 0):
return Loop(measurements=measurements, repetition_count=repetition_count, children=subprograms)
return None
repj_loop = Loop(repetition_count=repetition_count)
self.body._internal_create_program(parameters=parameters,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
parent_loop=repj_loop)
if repj_loop.waveform is not None or len(repj_loop.children) > 0:
if measurements:
parent_loop.add_measurements(measurements)

parent_loop.append_child(loop=repj_loop)

def requires_stop(self,
parameters: Dict[str, Parameter],
Expand Down
42 changes: 21 additions & 21 deletions qctoolkit/pulses/sequence_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from qctoolkit.utils.types import MeasurementWindow, ChannelID, TimeType
from qctoolkit.pulses.pulse_template import PulseTemplate
from qctoolkit.pulses.parameters import Parameter, ParameterConstrainer
from qctoolkit.pulses.parameters import Parameter, ParameterConstrainer, ParameterNotProvidedException
from qctoolkit.pulses.sequencing import InstructionBlock, Sequencer
from qctoolkit.pulses.conditions import Condition
from qctoolkit.pulses.mapping_pulse_template import MappingPulseTemplate, MappingTuple
Expand All @@ -23,9 +23,6 @@
__all__ = ["SequencePulseTemplate"]





class SequencePulseTemplate(PulseTemplate, ParameterConstrainer, MeasurementDefiner):
"""A sequence of different PulseTemplates.

Expand Down Expand Up @@ -145,28 +142,31 @@ def build_sequence(self,
channel_mapping=channel_mapping,
target_block=instruction_block)

def _internal_create_program(self,
def _internal_create_program(self, *,
parameters: Dict[str, Parameter],
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]:
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
parent_loop: Loop) -> None:
self.validate_parameter_constraints(parameters=parameters)

measurement_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.measurement_parameters}
measurements = self.get_measurement_windows(measurement_parameters, measurement_mapping)
try:
measurement_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.measurement_parameters}
duration_parameters = {parameter_name: parameters[parameter_name].get_value()
for parameter_name in self.duration.variables}
except KeyError as e:
raise ParameterNotProvidedException(e) from e

subprograms = []
for subtemplate in self.subtemplates:
subprogram = subtemplate.create_program(parameters,
measurement_mapping,
channel_mapping)
if subprogram:
subprograms.append(subprogram)

if subprograms or measurements:
return Loop(children=subprograms, measurements=measurements)
else:
return None
measurements = self.get_measurement_windows(measurement_parameters, measurement_mapping)
if self.duration.evaluate_numeric(**duration_parameters) > 0:
if measurements:
parent_loop.add_measurements(measurements)

for subtemplate in self.subtemplates:
subtemplate._internal_create_program(parameters=parameters,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
parent_loop=parent_loop)

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