Exemple #1
0
    def test_overwrite(self):
        inner = DictScope(FrozenDict({
            'a': 0.5,
            'i': 3
        }),
                          volatile=frozenset(['a']))

        fscope = _ForLoopScope(inner, 'i', 4)
        self.assertIn('i', fscope)
        self.assertIn('a', fscope)
        self.assertNotIn('j', fscope)

        equivalent = {'a': 0.5, 'i': 4}
        self.assertEqual(equivalent, dict(fscope))
        self.assertEqual(equivalent.items(), fscope.items())
        self.assertEqual(equivalent.keys(), fscope.keys())
        self.assertEqual(set(equivalent.values()), set(fscope.values()))

        self.assertEqual(0.5, fscope['a'])
        self.assertEqual(4, fscope['i'])

        self.assertEqual(FrozenDict({'a': ExpressionScalar('a')}),
                         fscope.get_volatile_parameters())

        inner = DictScope(FrozenDict({
            'a': 0.5,
            'i': 3
        }),
                          volatile=frozenset(['a', 'i']))
        fscope = _ForLoopScope(inner, 'i', 4)
        self.assertEqual(FrozenDict({'a': ExpressionScalar('a')}),
                         fscope.get_volatile_parameters())
    def test_from_kwargs(self):
        m = {'a': 1, 'b': 2}
        volatile = {'a'}
        ds = DictScope.from_kwargs(a=1, b=2)
        self.assertEqual(DictScope(FrozenDict(m)), ds)

        ds = DictScope.from_kwargs(a=1, b=2, volatile=volatile.copy())
        self.assertEqual(DictScope(FrozenDict(m), frozenset(volatile)), ds)
 def test_volatile_parameters(self):
     ds = DictScope.from_kwargs(a=1, b=2, c=3, d=4, volatile={'c', 'd'})
     ms = MappedScope(ds, FrozenDict(x=ExpressionScalar('a * b'),
                                     c=ExpressionScalar('a - b'),
                                     y=ExpressionScalar('c - a')))
     expected_volatile = FrozenDict(d=ExpressionScalar('d'), y=ExpressionScalar('c - 1'))
     self.assertEqual(expected_volatile, ms.get_volatile_parameters())
     self.assertIs(ms.get_volatile_parameters(), ms.get_volatile_parameters())
    def test_from_mapping(self):
        m = {'a': 1, 'b': 2}
        volatile = {'a'}
        ds = DictScope.from_mapping(m.copy())
        self.assertEqual(m, dict(ds))
        self.assertEqual(FrozenDict(), ds.get_volatile_parameters())

        ds = DictScope.from_mapping(m.copy(), volatile=volatile.copy())
        self.assertEqual(DictScope(FrozenDict(m), frozenset(volatile)), ds)
Exemple #5
0
    def operation(cls, expression, **operands):
        expression = Expression(expression)
        assert set(expression.variables) == operands.keys()

        scope = JointScope(
            FrozenDict({
                operand_name:
                MappedScope(operand._scope,
                            FrozenDict({operand_name: operand._expression}))
                for operand_name, operand in operands.items()
            }))
        return cls(expression, scope)
    def test_init(self):
        with self.assertRaises(AssertionError):
            DictScope(dict())
        fd = FrozenDict({'a': 2})
        ds = DictScope(fd)
        self.assertIs(fd, ds._values)
        self.assertEqual(FrozenDict(), ds._volatile_parameters)

        vp = frozenset('a')
        ds = DictScope(fd, vp)
        self.assertIs(fd, ds._values)
        self.assertEqual(FrozenDict(a=ExpressionScalar('a')), ds._volatile_parameters)
    def test_change_constants(self):
        volatile = frozenset('b')
        volatile_dict = FrozenDict(b=ExpressionScalar('b'))
        ds = DictScope(FrozenDict({'a': 1, 'b': 2}), volatile=volatile)

        changes = {'b': 3, 'c': 4}
        ds2 = ds.change_constants(changes)
        self.assertEqual({'a': 1, 'b': 2}, dict(ds))
        self.assertEqual(volatile_dict, ds.get_volatile_parameters())
        self.assertEqual({'a': 1, 'b': 3}, dict(ds2))
        self.assertEqual(volatile_dict, ds2.get_volatile_parameters())

        with self.assertWarns(NonVolatileChange):
            ds.change_constants({'a': 2, 'b': 3, 'c': 4})
    def test_mapping(self):
        ds = DictScope.from_kwargs(a=1, b=2, c=3)
        ms = MappedScope(ds, FrozenDict(x=ExpressionScalar('a * b'),
                                        c=ExpressionScalar('a - b')))

        self.assertEqual(4, len(ms))
        self.assertEqual(set('abcx'), set(ms.keys()))
        self.assertEqual([-1, 1, 2, 2], sorted(ms.values()))
        self.assertEqual({('a', 1), ('b', 2), ('c', -1), ('x', 2)}, set(ms.items()))
        self.assertEqual(set(ms), set(ms.keys()))

        self.assertIn('a', ms)
        self.assertIn('c', ms)
        self.assertIn('x', ms)

        self.assertNotIn('d', ms)
        self.assertEqual(-1, ms['c'])
        self.assertEqual(1, ms['a'])
        self.assertEqual(2, ms['x'])

        with self.assertRaises(TypeError):
            ms['d'] = 9

        with self.assertRaisesRegex(KeyError, 'd'):
            _ = ms['d']
Exemple #9
0
 def as_dict(self) -> FrozenDict[str, Number]:
     if self._as_dict is None:
         self._as_dict = FrozenDict(
             (parameter_name, self.get_parameter(parameter_name))
             for parameter_name in self.keys())
         self._cache = self._as_dict
     return self._as_dict
 def as_dict(self) -> FrozenMapping[str, Number]:
     if self._as_dict is None:
         self._as_dict = FrozenDict({
             **self._inner.as_dict(), self._index_name:
             self._index_value
         })
     return self._as_dict
Exemple #11
0
    def test_body_scope_generator(self):
        dt = DummyPulseTemplate(parameter_names={'i', 'k'})
        flt = ForLoopPulseTemplate(body=dt,
                                   loop_index='i',
                                   loop_range=('a', 'b', 'c'))

        expected_range = range(2, 17, 3)
        outer_scope = DictScope.from_kwargs(k=5,
                                            a=expected_range.start,
                                            b=expected_range.stop,
                                            c=expected_range.step,
                                            volatile={'i', 'j'})

        forward_scopes = list(
            flt._body_scope_generator(outer_scope, forward=True))
        backward_scopes = list(
            flt._body_scope_generator(outer_scope, forward=False))
        volatile_dict = FrozenDict(j=ExpressionScalar('j'))

        self.assertEqual(forward_scopes, list(reversed(backward_scopes)))

        for scope, i in zip(forward_scopes, expected_range):
            self.assertEqual(volatile_dict, scope.get_volatile_parameters())

            expected_dict_equivalent = dict(k=5,
                                            i=i,
                                            a=expected_range.start,
                                            b=expected_range.stop,
                                            c=expected_range.step)
            self.assertEqual(expected_dict_equivalent, dict(scope.items()))
Exemple #12
0
    def test_eq(self):
        ds1 = DictScope.from_kwargs(a=1, b=2, c=3, d=4)
        ds2 = DictScope.from_kwargs(a=1, b=2, c=3, d=5)

        mapping1 = FrozenDict(x=ExpressionScalar('a * b'),
                              c=ExpressionScalar('a - b'),
                              y=ExpressionScalar('c - a'))

        mapping2 = FrozenDict(x=ExpressionScalar('a * b'),
                              c=ExpressionScalar('a - b'),
                              y=ExpressionScalar('d - a'))

        self.assertEqual(MappedScope(ds1, mapping1), MappedScope(ds1, mapping1))
        self.assertNotEqual(MappedScope(ds1, mapping1), MappedScope(ds1, mapping2))
        self.assertNotEqual(MappedScope(ds2, mapping1), MappedScope(ds1, mapping1))
        self.assertEqual(MappedScope(ds1, mapping2), MappedScope(ds1, mapping2))
Exemple #13
0
    def test_eq(self):
        ds = DictScope(FrozenDict({'a': 1, 'b': 2}), volatile=frozenset('a'))

        ds1 = DictScope(FrozenDict({'a': 1, 'b': 2}), volatile=frozenset())
        ds2 = DictScope(FrozenDict({'a': 1, 'b': 2}), volatile=frozenset('a'))
        ds3 = DictScope(FrozenDict({'a': 1, 'b': 2}), volatile=frozenset('ab'))
        ds4 = DictScope(FrozenDict({'a': 1, 'b': 2, 'c': 3}), volatile=frozenset('a'))

        self.assertNotEqual(ds, ds1)
        self.assertNotEqual(ds, ds3)
        self.assertNotEqual(ds, ds4)
        self.assertEqual(ds, ds2)
        self.assertEqual(hash(ds), hash(ds2))

        self.assertNotEqual(ds1, ds2)
        self.assertNotEqual(ds2, ds3)
        self.assertNotEqual(ds3, ds4)
Exemple #14
0
 def volatile_property(self) -> VolatileProperty:
     dependencies = self._scope.get_volatile_parameters()
     dependencies = FrozenDict({
         parameter_name: dependencies[parameter_name]
         for parameter_name in self._expression.variables
         if parameter_name in dependencies
     })
     return VolatileProperty(expression=self._expression,
                             dependencies=dependencies)
Exemple #15
0
    def test_get_parameter(self):
        ds = DictScope(FrozenDict({'a': 1, 'b': 2}))

        self.assertEqual(1, ds.get_parameter('a'))
        self.assertEqual(2, ds.get_parameter('b'))

        with self.assertRaises(ParameterNotProvidedException) as cm:
            ds.get_parameter('c')
        self.assertEqual('c', cm.exception.parameter_name)
Exemple #16
0
    def test_update_volatile_parameters_with_depth1(self):
        parameters = {'s': 10, 'not': 13}
        s = VolatileRepetitionCount(expression=ExpressionScalar('s'),
                                    scope=DictScope(values=FrozenDict(s=3),
                                                    volatile=set('s')))

        wf_1 = DummyWaveform(defined_channels={'A'}, duration=1)
        wf_2 = DummyWaveform(defined_channels={'A'}, duration=1)

        program = Loop(children=[
            Loop(waveform=wf_1, repetition_count=s),
            Loop(waveform=wf_2, repetition_count=4),
            Loop(waveform=wf_1, repetition_count=1)
        ],
                       repetition_count=1)

        t_program = TaborProgram(program,
                                 channels=(None, 'A'),
                                 markers=(None, None),
                                 device_properties=self.instr_props,
                                 **self.program_entry_kwargs)

        self.assertEqual(t_program.get_sequencer_tables(),
                         [[(TableDescription(3, 0, 0), s.volatile_property),
                           (TableDescription(4, 1, 0), None),
                           (TableDescription(1, 0, 0), None)]])
        self.assertEqual(t_program.get_advanced_sequencer_table(),
                         [TableDescription(1, 1, 0)])

        modifications = t_program.update_volatile_parameters(parameters)

        expected_seq = VolatileRepetitionCount(
            expression=ExpressionScalar('s'),
            scope=DictScope(values=FrozenDict(s=10), volatile=set('s')))
        expected_modifications = {(0, 0): TableDescription(10, 0, 0)}

        self.assertEqual(
            t_program.get_sequencer_tables(),
            [[(TableDescription(10, 0, 0), expected_seq.volatile_property),
              (TableDescription(4, 1, 0), None),
              (TableDescription(1, 0, 0), None)]])
        self.assertEqual(t_program.get_advanced_sequencer_table(),
                         [TableDescription(1, 1, 0)])
        self.assertEqual(modifications, expected_modifications)
Exemple #17
0
 def get_volatile_parameters(self) -> FrozenDict[str, Expression]:
     if self._volatile_parameters is None:
         volatile_parameters = {}
         for parameter_name, scope in self._lookup:
             inner_volatile = scope.get_volatile_parameters()
             if parameter_name in inner_volatile:
                 volatile_parameters[parameter_name] = inner_volatile[
                     parameter_name]
         self._volatile_parameters = FrozenDict(volatile_parameters)
     return self._volatile_parameters
    def get_volatile_parameters(self) -> FrozenMapping[str, Expression]:
        inner_volatile = self._inner.get_volatile_parameters()

        if self._index_name in inner_volatile:
            # TODO: use delete method of frozendict
            index_name = self._index_name
            return FrozenDict((name, value)
                              for name, value in inner_volatile.items()
                              if name != index_name)
        else:
            return inner_volatile
Exemple #19
0
    def test_update_constants(self):
        ds = DictScope.from_kwargs(a=1, b=2, c=3, volatile={'c'})
        ds2 = DictScope.from_kwargs(a=1, b=2, c=4, volatile={'c'})
        ms = MappedScope(ds, FrozenDict(x=ExpressionScalar('a * b'),
                                        c=ExpressionScalar('a - b')))
        ms2 = MappedScope(ds2, ms._mapping)

        self.assertIs(ms, ms.change_constants({'f': 1}))

        changes = {'c': 4}
        ms_result = ms.change_constants(changes)
        self.assertEqual(ms2, ms_result)
Exemple #20
0
    def change_constants(self, new_constants: Mapping[str, Number]) -> 'Scope':
        to_update = new_constants.keys() & self._values.keys()
        if to_update:
            updated_non_volatile = to_update - self.get_volatile_parameters(
            ).keys()
            if updated_non_volatile:
                warnings.warn(NonVolatileChange(updated_non_volatile))

            return DictScope(values=FrozenDict(
                (parameter_name, new_constants.get(parameter_name, old_value))
                for parameter_name, old_value in self._values.items()),
                             volatile=self._volatile_parameters)
        else:
            return self
Exemple #21
0
    def __init__(self,
                 values: FrozenMapping[str, Number],
                 volatile: AbstractSet[str] = frozenset()):
        super().__init__()
        assert getattr(values, '__hash__', None) is not None

        self._values = values
        self._as_dict = values
        self._volatile_parameters = FrozenDict(
            {v: Expression(v)
             for v in volatile})
        self.keys = self._values.keys
        self.items = self._values.items
        self.values = self._values.values
Exemple #22
0
    def test_mapping(self):
        ds = DictScope(FrozenDict({'a': 1, 'b': 2}))
        self.assertIn('a', ds)
        self.assertNotIn('c', ds)

        self.assertEqual(set('ab'), set(ds.keys()))
        self.assertEqual(set('ab'), set(ds))
        self.assertEqual({1, 2}, set(ds.values()))
        self.assertEqual({('a', 1), ('b', 2)}, set(ds.items()))
        self.assertEqual({'a': 1, 'b': 2}, dict(ds))

        self.assertEqual(1, ds['a'])

        with self.assertRaises(KeyError):
            ds['c']

        with self.assertRaises(TypeError):
            ds['a'] = 3
Exemple #23
0
    def _collect_volatile_parameters(self) -> FrozenDict[str, Expression]:
        inner_volatile = self._scope.get_volatile_parameters()
        if inner_volatile:
            volatile = inner_volatile.to_dict()
            for mapped_parameter, expression in self._mapping.items():
                volatile_expr_dep = inner_volatile.keys(
                ) & expression.variables
                if volatile_expr_dep:
                    subs_vals = {}
                    for variable in expression.variables:
                        if variable in volatile_expr_dep:
                            subs_vals[variable] = inner_volatile[variable]
                        else:
                            subs_vals[variable] = self[variable]
                    volatile[mapped_parameter] = expression.evaluate_symbolic(
                        subs_vals)
                else:
                    volatile.pop(mapped_parameter, None)

            return FrozenDict(volatile)
        else:
            return inner_volatile
Exemple #24
0
    def test_parameter(self):
        mock_a = mock.Mock(wraps=1)
        mock_result = mock.Mock()

        ds = DictScope.from_kwargs(a=mock_a, b=2, c=3)
        ms = MappedScope(ds, FrozenDict(x=ExpressionScalar('a * b'),
                                        c=ExpressionScalar('a - b'),
                                        d=ExpressionScalar('y')))

        self.assertIs(mock_a, ms._calc_parameter('a'))

        with self.assertRaises(ParameterNotProvidedException):
            ms._calc_parameter('d')

        with mock.patch.object(ms._mapping['x'], 'evaluate_in_scope', return_value=mock_result) as evaluate_in_scope:
            self.assertIs(mock_result, ms._calc_parameter('x'))
            evaluate_in_scope.assert_called_once_with(ds)

        # effective caching tests
        with mock.patch.object(ms._mapping['x'], 'evaluate_in_scope', return_value=mock_result) as evaluate_in_scope:
            self.assertIs(mock_result, ms.get_parameter('x'))
            self.assertIs(mock_result, ms.get_parameter('x'))
            evaluate_in_scope.assert_called_once_with(ds)
Exemple #25
0
    def create_program(
            self,
            *,
            parameters: Optional[Mapping[str,
                                         Union[Expression, str, Number,
                                               ConstantParameter]]] = None,
            measurement_mapping: Optional[Mapping[str, Optional[str]]] = None,
            channel_mapping: Optional[Mapping[ChannelID,
                                              Optional[ChannelID]]] = None,
            global_transformation: Optional[Transformation] = None,
            to_single_waveform: Set[Union[str, 'PulseTemplate']] = None,
            volatile: Set[str] = None) -> Optional['Loop']:
        """Translates this PulseTemplate into a program Loop.

        The returned Loop represents the PulseTemplate with all parameter values instantiated provided as dictated by
        the parameters argument. Optionally, channels and measurements defined in the PulseTemplate can be renamed/mapped
        via the channel_mapping and measurement_mapping arguments.

        Args:
            parameters: A mapping of parameter names to Parameter objects.
            measurement_mapping: A mapping of measurement window names. Windows that are mapped to None are omitted.
            channel_mapping: A mapping of channel names. Channels that are mapped to None are omitted.
            global_transformation: This transformation is applied to every waveform
            to_single_waveform: A set of pulse templates (or identifiers) which are directly translated to a
                waveform. This might change how transformations are applied. TODO: clarify
            volatile: Everything in the final program that depends on these parameters is marked as volatile
        Returns:
             A Loop object corresponding to this PulseTemplate.
        """
        if parameters is None:
            parameters = dict()
        if measurement_mapping is None:
            measurement_mapping = {
                name: name
                for name in self.measurement_names
            }
        if channel_mapping is None:
            channel_mapping = dict()
        if to_single_waveform is None:
            to_single_waveform = set()
        if volatile is None:
            volatile = set()

        # make sure all channels are mapped
        complete_channel_mapping = {
            channel: channel
            for channel in self.defined_channels
        }
        complete_channel_mapping.update(channel_mapping)

        non_unique_targets = {
            channel
            for channel, count in collections.Counter(
                channel_mapping.values()).items()
            if count > 1 and channel is not None
        }
        if non_unique_targets:
            raise ValueError('The following channels are mapped to twice',
                             non_unique_targets)

        # make sure all values in the parameters dict are numbers
        if isinstance(parameters, Scope):
            assert not volatile
            scope = parameters
        else:
            parameters = dict(parameters)
            for parameter_name, value in parameters.items():
                if isinstance(value, Parameter):
                    parameters[parameter_name] = value.get_value()
                elif not isinstance(value, Number):
                    parameters[parameter_name] = Expression(
                        value).evaluate_numeric()

            scope = DictScope(values=FrozenDict(parameters), volatile=volatile)

        root_loop = 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)

        if root_loop.waveform is None and len(root_loop.children) == 0:
            return None  # return None if no program
        return root_loop
Exemple #26
0
 def change_constants(self, new_constants: Mapping[str,
                                                   Number]) -> 'JointScope':
     # TODO: Inefficient if the same scope is present multiple times
     return JointScope(
         FrozenDict((parameter_name, scope.change_constants(new_constants))
                    for parameter_name, scope in self._lookup.items()))
Exemple #27
0
    def __init__(self,
                 template: PulseTemplate,
                 *,
                 identifier: Optional[str] = None,
                 parameter_mapping: Optional[Dict[str, str]] = None,
                 measurement_mapping: Optional[Dict[str, str]] = None,
                 channel_mapping: Optional[Dict[ChannelID, ChannelID]] = None,
                 parameter_constraints: Optional[List[str]] = None,
                 allow_partial_parameter_mapping: bool = None,
                 registry: PulseRegistryType = None) -> None:
        """Standard constructor for the MappingPulseTemplate.

        Mappings that are not specified are defaulted to identity mappings. Channels and measurement names of the
        encapsulated template can be mapped partially by default. F.i. if channel_mapping only contains one of two
        channels the other channel name is mapped to itself. Channels that are mapped to None are dropped.
        However, if a parameter mapping is specified and one or more parameters are not mapped a MissingMappingException
        is raised. To allow partial mappings and enable the same behaviour as for the channel and measurement name
        mapping allow_partial_parameter_mapping must be set to True.
        Furthermore parameter constrains can be specified.
        
        :param template: The encapsulated pulse template whose parameters, measurement names and channels are mapped
        :param parameter_mapping: if not none, mappings for all parameters must be specified
        :param measurement_mapping: mappings for other measurement names are inserted
        :param channel_mapping: mappings for other channels are auto inserted. Mapping to None drops the channel.
        :param parameter_constraints:
        :param allow_partial_parameter_mapping: If None the value of the class variable ALLOW_PARTIAL_PARAMETER_MAPPING
        """
        PulseTemplate.__init__(self, identifier=identifier)
        ParameterConstrainer.__init__(
            self, parameter_constraints=parameter_constraints)

        if allow_partial_parameter_mapping is None:
            allow_partial_parameter_mapping = self.ALLOW_PARTIAL_PARAMETER_MAPPING

        if parameter_mapping is None:
            parameter_mapping = dict(
                (par, par) for par in template.parameter_names)
        else:
            mapped_internal_parameters = set(parameter_mapping.keys())
            internal_parameters = template.parameter_names
            missing_parameter_mappings = internal_parameters - mapped_internal_parameters
            if mapped_internal_parameters - internal_parameters:
                raise UnnecessaryMappingException(
                    template, mapped_internal_parameters - internal_parameters)
            elif missing_parameter_mappings:
                if allow_partial_parameter_mapping:
                    parameter_mapping.update(
                        {p: p
                         for p in missing_parameter_mappings})
                else:
                    raise MissingMappingException(
                        template,
                        internal_parameters - mapped_internal_parameters)
        parameter_mapping = dict(
            (k, Expression(v)) for k, v in parameter_mapping.items())

        measurement_mapping = dict(
        ) if measurement_mapping is None else measurement_mapping
        internal_names = template.measurement_names
        mapped_internal_names = set(measurement_mapping.keys())
        if mapped_internal_names - internal_names:
            raise UnnecessaryMappingException(
                template, mapped_internal_names - internal_names)
        missing_name_mappings = internal_names - mapped_internal_names
        measurement_mapping = dict(
            itertools.chain(((name, name) for name in missing_name_mappings),
                            measurement_mapping.items()))

        # we copy to modify in place
        channel_mapping = dict(
        ) if channel_mapping is None else channel_mapping.copy()
        internal_channels = template.defined_channels
        mapped_internal_channels = set(channel_mapping.keys())
        if mapped_internal_channels - internal_channels:
            raise UnnecessaryMappingException(
                template, mapped_internal_channels - internal_channels)

        # fill up implicit mappings (unchanged channels)
        missing_channel_mappings = internal_channels - mapped_internal_channels
        for name in missing_channel_mappings:
            channel_mapping[name] = name

        # None is an allowed overlapping target as it marks dropped channels
        overlapping_targets = {
            channel
            for channel, n in collections.Counter(
                channel_mapping.values()).items()
            if n > 1 and channel is not None
        }
        if overlapping_targets:
            raise ValueError(
                'Cannot map multiple channels to the same target(s) %r' %
                overlapping_targets, channel_mapping)

        if isinstance(template,
                      MappingPulseTemplate) and template.identifier is None:
            # avoid nested mappings
            parameter_mapping = {
                p: Expression(expr.evaluate_symbolic(parameter_mapping))
                for p, expr in template.parameter_mapping.items()
            }
            measurement_mapping = {
                k: measurement_mapping[v]
                for k, v in template.measurement_mapping.items()
            }
            channel_mapping = {
                k: channel_mapping[v]
                for k, v in template.channel_mapping.items()
            }
            template = template.template

        self.__template = template
        self.__parameter_mapping = FrozenDict(parameter_mapping)
        self.__external_parameters = set(
            itertools.chain(*(expr.variables
                              for expr in self.__parameter_mapping.values())))
        self.__external_parameters |= self.constrained_parameters
        self.__measurement_mapping = measurement_mapping
        self.__channel_mapping = channel_mapping
        self._register(registry=registry)
Exemple #28
0
class MappingPulseTemplate(PulseTemplate, ParameterConstrainer):
    """This class can be used to remap parameters, the names of measurement windows and the names of channels. Besides
    the standard constructor, there is a static member function from_tuple for convenience. The class also allows
    constraining parameters by deriving from ParameterConstrainer"""

    ALLOW_PARTIAL_PARAMETER_MAPPING = True
    """Default value for allow_partial_parameter_mapping of the __init__ method."""
    def __init__(self,
                 template: PulseTemplate,
                 *,
                 identifier: Optional[str] = None,
                 parameter_mapping: Optional[Dict[str, str]] = None,
                 measurement_mapping: Optional[Dict[str, str]] = None,
                 channel_mapping: Optional[Dict[ChannelID, ChannelID]] = None,
                 parameter_constraints: Optional[List[str]] = None,
                 allow_partial_parameter_mapping: bool = None,
                 registry: PulseRegistryType = None) -> None:
        """Standard constructor for the MappingPulseTemplate.

        Mappings that are not specified are defaulted to identity mappings. Channels and measurement names of the
        encapsulated template can be mapped partially by default. F.i. if channel_mapping only contains one of two
        channels the other channel name is mapped to itself. Channels that are mapped to None are dropped.
        However, if a parameter mapping is specified and one or more parameters are not mapped a MissingMappingException
        is raised. To allow partial mappings and enable the same behaviour as for the channel and measurement name
        mapping allow_partial_parameter_mapping must be set to True.
        Furthermore parameter constrains can be specified.
        
        :param template: The encapsulated pulse template whose parameters, measurement names and channels are mapped
        :param parameter_mapping: if not none, mappings for all parameters must be specified
        :param measurement_mapping: mappings for other measurement names are inserted
        :param channel_mapping: mappings for other channels are auto inserted. Mapping to None drops the channel.
        :param parameter_constraints:
        :param allow_partial_parameter_mapping: If None the value of the class variable ALLOW_PARTIAL_PARAMETER_MAPPING
        """
        PulseTemplate.__init__(self, identifier=identifier)
        ParameterConstrainer.__init__(
            self, parameter_constraints=parameter_constraints)

        if allow_partial_parameter_mapping is None:
            allow_partial_parameter_mapping = self.ALLOW_PARTIAL_PARAMETER_MAPPING

        if parameter_mapping is None:
            parameter_mapping = dict(
                (par, par) for par in template.parameter_names)
        else:
            mapped_internal_parameters = set(parameter_mapping.keys())
            internal_parameters = template.parameter_names
            missing_parameter_mappings = internal_parameters - mapped_internal_parameters
            if mapped_internal_parameters - internal_parameters:
                raise UnnecessaryMappingException(
                    template, mapped_internal_parameters - internal_parameters)
            elif missing_parameter_mappings:
                if allow_partial_parameter_mapping:
                    parameter_mapping.update(
                        {p: p
                         for p in missing_parameter_mappings})
                else:
                    raise MissingMappingException(
                        template,
                        internal_parameters - mapped_internal_parameters)
        parameter_mapping = dict(
            (k, Expression(v)) for k, v in parameter_mapping.items())

        measurement_mapping = dict(
        ) if measurement_mapping is None else measurement_mapping
        internal_names = template.measurement_names
        mapped_internal_names = set(measurement_mapping.keys())
        if mapped_internal_names - internal_names:
            raise UnnecessaryMappingException(
                template, mapped_internal_names - internal_names)
        missing_name_mappings = internal_names - mapped_internal_names
        measurement_mapping = dict(
            itertools.chain(((name, name) for name in missing_name_mappings),
                            measurement_mapping.items()))

        # we copy to modify in place
        channel_mapping = dict(
        ) if channel_mapping is None else channel_mapping.copy()
        internal_channels = template.defined_channels
        mapped_internal_channels = set(channel_mapping.keys())
        if mapped_internal_channels - internal_channels:
            raise UnnecessaryMappingException(
                template, mapped_internal_channels - internal_channels)

        # fill up implicit mappings (unchanged channels)
        missing_channel_mappings = internal_channels - mapped_internal_channels
        for name in missing_channel_mappings:
            channel_mapping[name] = name

        # None is an allowed overlapping target as it marks dropped channels
        overlapping_targets = {
            channel
            for channel, n in collections.Counter(
                channel_mapping.values()).items()
            if n > 1 and channel is not None
        }
        if overlapping_targets:
            raise ValueError(
                'Cannot map multiple channels to the same target(s) %r' %
                overlapping_targets, channel_mapping)

        if isinstance(template,
                      MappingPulseTemplate) and template.identifier is None:
            # avoid nested mappings
            parameter_mapping = {
                p: Expression(expr.evaluate_symbolic(parameter_mapping))
                for p, expr in template.parameter_mapping.items()
            }
            measurement_mapping = {
                k: measurement_mapping[v]
                for k, v in template.measurement_mapping.items()
            }
            channel_mapping = {
                k: channel_mapping[v]
                for k, v in template.channel_mapping.items()
            }
            template = template.template

        self.__template = template
        self.__parameter_mapping = FrozenDict(parameter_mapping)
        self.__external_parameters = set(
            itertools.chain(*(expr.variables
                              for expr in self.__parameter_mapping.values())))
        self.__external_parameters |= self.constrained_parameters
        self.__measurement_mapping = measurement_mapping
        self.__channel_mapping = channel_mapping
        self._register(registry=registry)

    @classmethod
    def from_tuple(cls, mapping_tuple: MappingTuple) -> 'MappingPulseTemplate':
        """Construct a MappingPulseTemplate from a tuple of mappings. The mappings are automatically assigned to the
        mapped elements based on their content.
        :param mapping_tuple: A tuple of mappings
        :return: Constructed MappingPulseTemplate
        """
        template, *mappings = mapping_tuple

        if not mappings:
            return template

        parameter_mapping = None
        measurement_mapping = None
        channel_mapping = None

        for mapping in mappings:
            if len(mapping) == 0:
                continue

            mapped = set(mapping.keys())
            if sum((mapped <= template.parameter_names,
                    mapped <= template.measurement_names,
                    mapped <= template.defined_channels)) > 1:
                raise AmbiguousMappingException(template, mapping)

            if mapped <= template.parameter_names:
                if parameter_mapping:
                    raise MappingCollisionException(
                        template,
                        object_type='parameter',
                        mapped=template.parameter_names,
                        mappings=(parameter_mapping, mapping))
                parameter_mapping = mapping
            elif mapped <= template.measurement_names:
                if measurement_mapping:
                    raise MappingCollisionException(
                        template,
                        object_type='measurement',
                        mapped=template.measurement_names,
                        mappings=(measurement_mapping, mapping))
                measurement_mapping = mapping
            elif mapped <= template.defined_channels:
                if channel_mapping:
                    raise MappingCollisionException(
                        template,
                        object_type='channel',
                        mapped=template.defined_channels,
                        mappings=(channel_mapping, mapping))
                channel_mapping = mapping
            else:
                raise ValueError(
                    'Could not match mapping to mapped objects: {}'.format(
                        mapping))
        return cls(template,
                   parameter_mapping=parameter_mapping,
                   measurement_mapping=measurement_mapping,
                   channel_mapping=channel_mapping)

    @property
    def template(self) -> PulseTemplate:
        return self.__template

    @property
    def measurement_mapping(self) -> Dict[str, str]:
        return self.__measurement_mapping

    @property
    def parameter_mapping(self) -> FrozenMapping[str, Expression]:
        return self.__parameter_mapping

    @property
    def channel_mapping(self) -> Dict[ChannelID, Optional[ChannelID]]:
        return self.__channel_mapping

    @property
    def parameter_names(self) -> Set[str]:
        return self.__external_parameters

    @property
    def measurement_names(self) -> Set[str]:
        return set(self.__measurement_mapping.values())

    @property
    def defined_channels(self) -> Set[ChannelID]:
        return {
            self.__channel_mapping[k]
            for k in self.template.defined_channels
        } - {None}

    @property
    def duration(self) -> Expression:
        return self.__template.duration.evaluate_symbolic({
            parameter_name: expression.underlying_expression
            for parameter_name, expression in self.__parameter_mapping.items()
        })

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

        if serializer:  # compatibility to old serialization routines, deprecated
            parameter_mapping_dict = dict(
                (key, str(expression))
                for key, expression in self.__parameter_mapping.items())
            data = dict(template=serializer.dictify(self.template),
                        parameter_mapping=parameter_mapping_dict,
                        measurement_mapping=self.__measurement_mapping,
                        channel_mapping=self.__channel_mapping)

        else:
            data['template'] = self.template
            if self.__parameter_mapping:
                data['parameter_mapping'] = self.__parameter_mapping
            if self.__measurement_mapping:
                data['measurement_mapping'] = self.__measurement_mapping
            if self.__channel_mapping:
                data['channel_mapping'] = self.__channel_mapping

        if self.parameter_constraints:
            data['parameter_constraints'] = [
                str(c) for c in self.parameter_constraints
            ]

        return data

    @classmethod
    def deserialize(
        cls,
        serializer: Optional[
            Serializer] = None,  # compatibility to old serialization routines, deprecated
        **kwargs
    ) -> 'MappingPulseTemplate':
        if serializer:  # compatibility to old serialization routines, deprecated
            kwargs['template'] = serializer.deserialize(kwargs["template"])
        return cls(**kwargs, allow_partial_parameter_mapping=True)
        # return MappingPulseTemplate(template=serializer.deserialize(template),
        #                             **kwargs)

    def _validate_parameters(self, parameters: Dict[str, Union[Parameter,
                                                               numbers.Real]],
                             volatile: Set[str]):
        missing = set(self.__external_parameters) - set(parameters.keys())
        if missing:
            raise ParameterNotProvidedException(missing.pop())
        self.validate_parameter_constraints(parameters=parameters,
                                            volatile=volatile)

    def map_parameter_values(
        self,
        parameters: Dict[str, numbers.Real],
        volatile: Set[str] = frozenset()
    ) -> Dict[str, numbers.Real]:
        """Map parameter values according to the defined mappings.

        Args:
            parameters: Dictionary with numeric values
            volatile(Optional): Forwarded to `validate_parameter_constraints`
        Returns:
            A new dictionary with mapped numeric values.
        """
        self._validate_parameters(parameters=parameters, volatile=volatile)
        return {
            parameter: mapping_function.evaluate_numeric(**parameters)
            for parameter, mapping_function in
            self.__parameter_mapping.items()
        }

    def map_parameter_objects(
        self,
        parameters: Dict[str, Parameter],
        volatile: Set[str] = frozenset()
    ) -> Dict[str, Parameter]:
        """Map parameter objects (instances of Parameter class) according to the defined mappings.

        Args:
            parameters: Dictionary with parameter objects
            volatile(Optional): Forwarded to `validate_parameter_constraints`
        Returns:
            A new dictionary with mapped parameter objects
        """
        self._validate_parameters(parameters=parameters, volatile=volatile)
        return {
            parameter: MappedParameter(mapping_function, {
                name: parameters[name]
                for name in mapping_function.variables
            })
            for (parameter,
                 mapping_function) in self.__parameter_mapping.items()
        }

    def map_scope(self, scope: Scope) -> MappedScope:
        return MappedScope(scope=scope, mapping=self.__parameter_mapping)

    def map_parameters(
        self, parameters: Dict[str, Union[Parameter, numbers.Real]]
    ) -> Dict[str, Union[Parameter, numbers.Real]]:
        """Map parameter values according to the defined mappings.

        Args:
            parameters: A mapping of parameter names to parameter objects/values.
        Returns:
            A new dictionary which maps parameter names to parameter values which have been
            mapped according to the mappings defined for template.
        """
        if not parameters and self.__parameter_mapping:
            raise ValueError(
                'Cannot infer type of return value (numeric or symbolic)')

        elif all(
                isinstance(parameter, numbers.Real)
                for parameter in parameters.values()):
            return self.map_parameter_values(parameters=parameters)

        elif all(
                isinstance(parameter, Parameter)
                for parameter in parameters.values()):
            return self.map_parameter_objects(parameters=parameters)

        else:
            raise TypeError(
                'Values of parameter dict are neither all Parameter nor Real')

    def get_updated_measurement_mapping(
            self, measurement_mapping: Dict[str, str]) -> Dict[str, str]:
        return {
            k: measurement_mapping[v]
            for k, v in self.__measurement_mapping.items()
        }

    def get_updated_channel_mapping(
        self, channel_mapping: Dict[ChannelID, Optional[ChannelID]]
    ) -> Dict[ChannelID, Optional[ChannelID]]:
        # do not look up the mapped outer channel if it is None (this marks a deleted channel)
        return {
            inner_ch: None if outer_ch is None else channel_mapping[outer_ch]
            for inner_ch, outer_ch in self.__channel_mapping.items()
        }

    def _internal_create_program(
            self, *, scope: Scope, measurement_mapping: Dict[str,
                                                             Optional[str]],
            channel_mapping: Dict[ChannelID, Optional[ChannelID]],
            global_transformation: Optional['Transformation'],
            to_single_waveform: Set[Union[str, 'PulseTemplate']],
            parent_loop: Loop) -> None:
        self.validate_scope(scope)

        # parameters are validated in map_parameters() call, no need to do it here again explicitly
        self.template._create_program(
            scope=self.map_scope(scope),
            measurement_mapping=self.get_updated_measurement_mapping(
                measurement_mapping),
            channel_mapping=self.get_updated_channel_mapping(channel_mapping),
            global_transformation=global_transformation,
            to_single_waveform=to_single_waveform,
            parent_loop=parent_loop)

    def build_waveform(
            self, parameters: Dict[str, numbers.Real],
            channel_mapping: Dict[ChannelID, ChannelID]) -> Waveform:
        """This gets called if the parent is atomic"""
        return self.template.build_waveform(
            parameters=self.map_parameter_values(parameters),
            channel_mapping=self.get_updated_channel_mapping(channel_mapping))

    def get_measurement_windows(
            self, parameters: Dict[str, numbers.Real],
            measurement_mapping: Dict[str, Optional[str]]) -> List:
        return self.template.get_measurement_windows(
            parameters=self.map_parameter_values(parameters=parameters),
            measurement_mapping=self.get_updated_measurement_mapping(
                measurement_mapping=measurement_mapping))

    @property
    def integral(self) -> Dict[ChannelID, ExpressionScalar]:
        internal_integral = self.__template.integral
        expressions = dict()

        # sympy.subs() does not work if one of the mappings in the provided dict is an Expression object
        # the following is an ugly workaround
        # todo: make Expressions compatible with sympy.subs()
        parameter_mapping = {
            parameter_name: expression.underlying_expression
            for parameter_name, expression in self.__parameter_mapping.items()
        }

        for channel, ch_integral in internal_integral.items():
            channel_out = self.__channel_mapping.get(channel, channel)
            if channel_out is None:
                continue

            expressions[channel_out] = ExpressionScalar(
                ch_integral.sympified_expression.subs(parameter_mapping))

        return expressions
Exemple #29
0
 def overwrite(self, to_overwrite: Mapping[str, Number]) -> 'Scope':
     # TODO: replace with OverwritingScope
     return MappedScope(
         self,
         FrozenDict((name, Expression(value))
                    for name, value in to_overwrite.items()))
Exemple #30
0
 def as_dict(self) -> FrozenDict[str, Number]:
     if self._as_dict is None:
         self._as_dict = FrozenDict(self.items())
     return self._as_dict