def test_map_parameters_not_provided(self) -> None: map = PulseTemplateParameterMapping({'bar', 'barbar'}) dummy = DummyPulseTemplate(parameter_names={'foo', 'hugo'}) map.add(dummy, 'hugo', '4*bar') map.add(dummy, 'foo', Expression('barbar')) with self.assertRaises(ParameterNotProvidedException): map.map_parameters(dummy, dict(bar=ConstantParameter(3)))
def test_map_parameters(self) -> None: map = PulseTemplateParameterMapping({'bar', 'barbar'}) dummy = DummyPulseTemplate(parameter_names={'foo', 'hugo'}) map.add(dummy, 'hugo', '4*bar') map.add(dummy, 'foo', Expression('barbar')) mapped = map.map_parameters( dummy, dict(bar=ConstantParameter(3), barbar=ConstantParameter(5))) self.assertEqual( dict(hugo=MappedParameter(Expression('4*bar'), dict(bar=ConstantParameter(3))), foo=MappedParameter(Expression('barbar'), dict(barbar=ConstantParameter(5)))), mapped)
class MultiChannelPulseTemplate(AtomicPulseTemplate): """A multi-channel group of several AtomicPulseTemplate objects. While SequencePulseTemplate combines several subtemplates (with an identical number of channels) for subsequent execution, MultiChannelPulseTemplate combines several subtemplates into a new template defined for the sum of the channels of the subtemplates. A constraint is that the subtemplates must only be AtomicPulseTemplate objects, i.e., not define any changes in the control flow and be directly translatable into waveforms. Additionally, for each possible set of parameter value assignments, the waveforms resulting from the templates must be of the same duration. However, this cannot be enforced during the construction of the MultiChannelPulseTemplate object. Instead, if subtemplate misbehave, an exception will be raised during translation in the build_waveform and build_sequence methods. MultiChannelPulseTemplate allows an arbitrary mapping of channels defined by the subtemplates and the channels it defines. For example, if the MultiChannelPulseTemplate consists of a two subtemplates A and B which define two channels each, then the channels of the MultiChannelPulseTemplate may be 0: A.1, 1: B.0, 2: B.1, 3: A.0 where A.0 means channel 0 of A. The channel mapping must be sane, i.e., to no channel of the MultiChannelPulseTemplate must be assigned more than one channel of any subtemplate. Finally, MultiChannelPulseTemplate allows a mapping of parameter in the same way and with the same constraints as SequencePulseTemplate. See Also: - SequencePulseTemplate - AtomicPulseTemplate - MultiChannelWaveform """ Subtemplate = Tuple[AtomicPulseTemplate, Dict[str, str], List[int]] def __init__(self, subtemplates: Iterable[Subtemplate], external_parameters: Set[str], identifier: str=None) -> None: """Creates a new MultiChannelPulseTemplate instance. Requires a list of subtemplates in the form (PulseTemplate, Dict(str -> str), List(int)) where the dictionary is a mapping between the external parameters exposed by this SequencePulseTemplate to the parameters declared by the subtemplates, specifying how the latter are derived from the former, i.e., the mapping is subtemplate_parameter_name -> mapping_expression (as str) where the free variables in the mapping_expression are parameters declared by this MultiChannelPulseTemplate. The list defines the channel mapping, i.e., a value y at index x in the list means that channel x of the subtemplate will be mapped to channel y of this MultiChannelPulseTemplate. Args: subtemplates (List(Subtemplate)): The list of subtemplates of this MultiChannelPulseTemplate as tuples of the form (PulseTemplate, Dict(str -> str), List(int)). external_parameters (List(str)): A set of names for external parameters of this MultiChannelPulseTemplate. identifier (str): A unique identifier for use in serialization. (optional) Raises: ValueError, if a channel mapping is out of bounds of the channels defined by this MultiChannelPulseTemplate. ValueError, if several subtemplate channels are assigned to a single channel of this MultiChannelPulseTemplate. MissingMappingException, if a parameter of a subtemplate is not mapped to the external parameters of this MultiChannelPulseTemplate. MissingParameterDeclarationException, if a parameter mapping requires a parameter that was not declared in the external parameters of this MultiChannelPulseTemplate. """ super().__init__(identifier=identifier) self.__parameter_mapping = PulseTemplateParameterMapping(external_parameters) self.__subtemplates = [(template, channel_mapping) for (template, _, channel_mapping) in subtemplates] assigned_channels = set() num_channels = self.num_channels for template, mapping_functions, channel_mapping in subtemplates: # Consistency checks for parameter, mapping_function in mapping_functions.items(): self.__parameter_mapping.add(template, parameter, mapping_function) remaining = self.__parameter_mapping.get_remaining_mappings(template) if remaining: raise MissingMappingException(template, remaining.pop()) # ensure that channel mappings stay within bounds out_of_bounds_channel = [channel for channel in channel_mapping if channel >= num_channels or channel < 0] if out_of_bounds_channel: raise ValueError( "The channel mapping {}, assigned to a channel waveform, is not valid (must be " "greater than 0 and less than {}).".format(out_of_bounds_channel.pop(), num_channels) ) # ensure that only a single waveform is mapped to each channel for channel in channel_mapping: if channel in assigned_channels: raise ValueError("The channel {} has multiple channel waveform assignments" .format(channel)) else: assigned_channels.add(channel) @property def parameter_names(self) -> Set[str]: return self.__parameter_mapping.external_parameters @property def parameter_declarations(self) -> Set[ParameterDeclaration]: # TODO: min, max, default values not mapped (required?) return {ParameterDeclaration(parameter_name) for parameter_name in self.parameter_names} def get_measurement_windows(self, parameters: Dict[str, Parameter] = None) \ -> List['MeasurementWindow']: raise NotImplementedError() @property def is_interruptable(self) -> bool: return all(t.is_interruptable for t, _ in self.__subtemplates) @property def num_channels(self) -> int: return sum(t.num_channels for t, _ in self.__subtemplates) def requires_stop(self, parameters: Dict[str, Parameter], conditions: Dict[str, Condition]) -> bool: return any(t.requires_stop(self.__parameter_mapping.map_parameters(t, parameters), conditions) for t, _ in self.__subtemplates) def build_waveform(self, parameters: Dict[str, Parameter]) -> Optional[Waveform]: missing = self.parameter_names - parameters.keys() if missing: raise ParameterNotProvidedException(missing.pop()) channel_waveforms = [] for template, channel_mapping in self.__subtemplates: inner_parameters = self.__parameter_mapping.map_parameters(template, parameters) waveform = template.build_waveform(inner_parameters) channel_waveforms.append((waveform, channel_mapping)) waveform = MultiChannelWaveform(channel_waveforms) return waveform def get_serialization_data(self, serializer: Serializer) -> Dict[str, Any]: subtemplates = [] for subtemplate, channel_mapping in self.__subtemplates: mapping_functions = self.__parameter_mapping.get_template_map(subtemplate) mapping_function_strings = \ {k: serializer.dictify(m) for k, m in mapping_functions.items()} subtemplate = serializer.dictify(subtemplate) subtemplates.append(dict(template=subtemplate, parameter_mappings=mapping_function_strings, channel_mappings=channel_mapping)) return dict(subtemplates=subtemplates, external_parameters=sorted(list(self.parameter_names))) @staticmethod def deserialize(serializer: Serializer, subtemplates: Iterable[Dict[str, Any]], external_parameters: Iterable[str], identifier: Optional[str]=None) -> 'MultiChannelPulseTemplate': subtemplates = \ [(serializer.deserialize(subt['template']), {k: str(serializer.deserialize(m)) for k, m in subt['parameter_mappings'].items()}, subt['channel_mappings']) for subt in subtemplates] template = MultiChannelPulseTemplate(subtemplates, external_parameters, identifier=identifier) return template
class SequencePulseTemplate(PulseTemplate): """A sequence of different PulseTemplates. SequencePulseTemplate allows to group several PulseTemplates (subtemplates) into one larger sequence, i.e., when instantiating a pulse from a SequencePulseTemplate all pulses instantiated from the subtemplates are queued for execution right after one another. SequencePulseTemplate requires to specify a mapping of parameter declarations from its subtemplates to its own, enabling renaming and mathematical transformation of parameters. """ # a subtemplate consists of a pulse template and mapping functions for its "internal" parameters Subtemplate = Tuple[PulseTemplate, Dict[str, str]] # pylint: disable=invalid-name def __init__( self, subtemplates: List[Subtemplate], external_parameters: List[str], # pylint: disable=invalid-sequence-index identifier: Optional[str] = None ) -> None: """Create a new SequencePulseTemplate instance. Requires a (correctly ordered) list of subtemplates in the form (PulseTemplate, Dict(str -> str)) where the dictionary is a mapping between the external parameters exposed by this SequencePulseTemplate to the parameters declared by the subtemplates, specifying how the latter are derived from the former, i.e., the mapping is subtemplate_parameter_name -> mapping_expression (as str) where the free variables in the mapping_expression are parameters declared by this SequencePulseTemplate. The following requirements must be satisfied: - for each parameter declared by a subtemplate, a mapping expression must be provided - each free variable in a mapping expression must be declared as an external parameter of this SequencePulseTemplate Args: subtemplates (List(Subtemplate)): The list of subtemplates of this SequencePulseTemplate as tuples of the form (PulseTemplate, Dict(str -> str)). external_parameters (List(str)): A set of names for external parameters of this SequencePulseTemplate. identifier (str): A unique identifier for use in serialization. (optional) Raises: MissingMappingException, if a parameter of a subtemplate is not mapped to the external parameters of this SequencePulseTemplate. MissingParameterDeclarationException, if a parameter mapping requires a parameter that was not declared in the external parameters of this SequencePulseTemplate. """ super().__init__(identifier) num_channels = 0 if subtemplates: num_channels = subtemplates[0][0].num_channels self.__parameter_mapping = PulseTemplateParameterMapping( external_parameters) for template, mapping_functions in subtemplates: # Consistency checks if template.num_channels != num_channels: raise ValueError( "Subtemplates have different number of channels!") for parameter, mapping_function in mapping_functions.items(): self.__parameter_mapping.add(template, parameter, mapping_function) remaining = self.__parameter_mapping.get_remaining_mappings( template) if remaining: raise MissingMappingException(template, remaining.pop()) self.__subtemplates = [template for (template, _) in subtemplates] self.__is_interruptable = True @property def parameter_names(self) -> Set[str]: return self.__parameter_mapping.external_parameters @property def parameter_declarations(self) -> Set[ParameterDeclaration]: # TODO: min, max, default values not mapped (required?) return {ParameterDeclaration(name) for name in self.parameter_names} @property def subtemplates(self) -> List[Subtemplate]: return [(template, self.__parameter_mapping.get_template_map(template)) for template in self.__subtemplates] def get_measurement_windows( self, parameters: Dict[str, Parameter] = None) -> List[MeasurementWindow]: raise NotImplementedError( ) # will be computed by Sequencer #TODO: IMPORTANT @property def is_interruptable(self) -> bool: return self.__is_interruptable @is_interruptable.setter def is_interruptable(self, new_value: bool) -> None: self.__is_interruptable = new_value @property def num_channels(self) -> int: return self.__subtemplates[0].num_channels def requires_stop(self, parameters: Dict[str, Parameter], conditions: Dict[str, 'Condition']) -> bool: return False def build_sequence(self, sequencer: Sequencer, parameters: Dict[str, Parameter], conditions: Dict[str, Condition], instruction_block: InstructionBlock) -> None: # todo: currently ignores is_interruptable # detect missing or unnecessary parameters missing = self.parameter_names - parameters.keys() if missing: raise ParameterNotProvidedException(missing.pop()) # push subtemplates to sequencing stack with mapped parameters for template in reversed(self.__subtemplates): inner_parameters = self.__parameter_mapping.map_parameters( template, parameters) sequencer.push(template, inner_parameters, conditions, instruction_block) def get_serialization_data(self, serializer: Serializer) -> Dict[str, Any]: data = dict() data['external_parameters'] = sorted(list(self.parameter_names)) data['is_interruptable'] = self.is_interruptable subtemplates = [] for subtemplate in self.__subtemplates: mapping_functions = self.__parameter_mapping.get_template_map( subtemplate) mapping_functions_strings = \ {k: serializer.dictify(m) for k, m in mapping_functions.items()} subtemplate = serializer.dictify(subtemplate) subtemplates.append( dict(template=subtemplate, mappings=mapping_functions_strings)) data['subtemplates'] = subtemplates data['type'] = serializer.get_type_identifier(self) return data @staticmethod def deserialize( serializer: Serializer, is_interruptable: bool, subtemplates: Iterable[Dict[str, Union[str, Dict[str, Any]]]], external_parameters: Iterable[str], identifier: Optional[str] = None) -> 'SequencePulseTemplate': subtemplates = \ [(serializer.deserialize(d['template']), {k: str(serializer.deserialize(m)) for k, m in d['mappings'].items()}) for d in subtemplates] template = SequencePulseTemplate(subtemplates, external_parameters, identifier=identifier) template.is_interruptable = is_interruptable return template def __matmult__(self, other) -> 'SequencePulseTemplate': """Like in the general PulseTemplate implementation this method enables using the @-operator for concatenating pulses. We need an overloaded method for SequencePulseTemplate to avoid creating unnecessarily nested pulse structures.""" if not type(other) == SequencePulseTemplate: return SequencePulseTemplate.__matmult__(self, other) else: # this section is copy-pasted from the PulseTemplate implementation double_parameters = self.parameter_names & other.parameter_names # intersection if double_parameters: raise DoubleParameterNameException(self, other, double_parameters) else: # this branch differs from what happens in PulseTemplate subtemplates = self.subtemplates + other.subtemplates # the check for conflicting external parameters has already been carried out external_parameters = self.parameter_names | other.parameter_names # union return SequencePulseTemplate( subtemplates, external_parameters) # no identifier