Пример #1
0
    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
 def test_get_remaining_mappings(self) -> None:
     map = PulseTemplateParameterMapping({'bar', 'barbar'})
     dummy = DummyPulseTemplate(parameter_names={'foo', 'hugo'})
     self.assertEqual({'foo', 'hugo'}, map.get_remaining_mappings(dummy))
     map.add(dummy, 'hugo', '4*bar')
     self.assertEqual({'foo'}, map.get_remaining_mappings(dummy))
     map.add(dummy, 'foo', Expression('barbar'))
     self.assertEqual(set(), map.get_remaining_mappings(dummy))
 def test_is_template_mapped(self) -> None:
     map = PulseTemplateParameterMapping({'bar'})
     dummy1 = DummyPulseTemplate(parameter_names={'foo', 'hugo'})
     dummy2 = DummyPulseTemplate(parameter_names={'grr'})
     map.add(dummy1, 'foo', '4*bar')
     self.assertFalse(map.is_template_mapped(dummy1))
     map.add(dummy1, 'hugo', 'bar + 1')
     self.assertTrue(map.is_template_mapped(dummy1))
     self.assertFalse(map.is_template_mapped(dummy2))
 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_set_external_parameters(self) -> None:
     map = PulseTemplateParameterMapping({'foo'})
     self.assertEqual({'foo'}, map.external_parameters)
     map.set_external_parameters(None)
     self.assertEqual({'foo'}, map.external_parameters)
     map.set_external_parameters({'bar'})
     self.assertEqual({'bar'}, map.external_parameters)
     map.set_external_parameters(set())
     self.assertEqual(set(), map.external_parameters)
 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)
    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)
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
 def test_get_template_map_no_key(self) -> None:
     map = PulseTemplateParameterMapping()
     dummy = DummyPulseTemplate()
     self.assertEqual(dict(), map.get_template_map(dummy))
 def test_add(self) -> None:
     map = PulseTemplateParameterMapping({'bar'})
     dummy1 = DummyPulseTemplate(parameter_names={'foo', 'hugo'})
     dummy2 = DummyPulseTemplate(parameter_names={'grr'})
     map.add(dummy1, 'foo', '4*bar')
     map.add(dummy2, 'grr', Expression('bar ** 2'))
     map.add(dummy1, 'hugo', '3')
     map.add(dummy2, 'grr', Expression('sin(bar)'))
     self.assertEqual(dict(foo=Expression('4*bar'), hugo=Expression('3')),
                      map.get_template_map(dummy1))
     self.assertEqual(dict(grr=Expression('sin(bar)')),
                      map.get_template_map(dummy2))
 def test_add_missing_external_parameter(self) -> None:
     map = PulseTemplateParameterMapping()
     dummy = DummyPulseTemplate(parameter_names={'foo'})
     with self.assertRaises(MissingParameterDeclarationException):
         map.add(dummy, 'foo', 'bar')
 def test_add_unnecessary_mapping(self) -> None:
     map = PulseTemplateParameterMapping()
     dummy = DummyPulseTemplate(parameter_names={'foo'})
     with self.assertRaises(UnnecessaryMappingException):
         map.add(dummy, 'bar', '2')
 def test_empty_init(self) -> None:
     map = PulseTemplateParameterMapping()
     self.assertEqual(set(), map.external_parameters)
Пример #14
0
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