# SPDX-FileCopyrightText: 2014-2024 Quantum Technology Group and Chair of Software Engineering, RWTH Aachen University
#
# SPDX-License-Identifier: LGPL-3.0-or-later
from typing import Set, Optional, Dict, Any, cast
from functools import partial, partialmethod
import warnings
from qupulse import ChannelID
from qupulse.expressions import ExpressionScalar
from qupulse.pulses.metadata import TemplateMetadata
from qupulse.serialization import PulseRegistryType
from qupulse.pulses.pulse_template import PulseTemplate
__all__ = ["AbstractPulseTemplate", "UnlinkWarning"]
[docs]
class AbstractPulseTemplate(PulseTemplate):
_PROPERTY_DOC = """Abstraction of :py:attr:`.PulseTemplate.{name}`. Raises :class:`.NotSpecifiedError` if the
abstract template is unlinked or the property was not specified."""
def __init__(self, identifier: str,
*,
defined_channels: Optional[Set[ChannelID]]=None,
parameter_names: Optional[Set[str]]=None,
measurement_names: Optional[Set[str]]=None,
integral: Optional[Dict[ChannelID, ExpressionScalar]]=None,
duration: Optional[ExpressionScalar]=None,
registry: Optional[PulseRegistryType]=None):
"""This pulse template can be used as a placeholder for a pulse template with a defined interface. Pulse
template properties like :func:`defined_channels` can be passed on initialization to declare those properties who make
up the interface. Omitted properties raise an :class:`.NotSpecifiedError` exception if accessed. Properties
which have been accessed are marked as "frozen".
The abstract pulse template can be linked to another pulse template by calling the `link_to` member. The target
has to have the same properties for all properties marked as "frozen". This ensures a property always returns
the same value.
Example:
>>> abstract_readout = AbstractPulseTemplate('readout', defined_channels={'X', 'Y'})
>>> assert abstract_readout.defined_channels == {'X', 'Y'}
>>> # This will raise an exception because duration is not specified
>>> print(abstract_readout.duration)
Args:
identifier: Mandatory property
defined_channels: Optional property
parameter_names: Optional property
measurement_names: Optional property
integral: Optional property
duration: Optional property
registry: Instance is registered here if specified
"""
super().__init__(identifier=identifier)
self._declared_properties = {}
self._frozen_properties = set()
if defined_channels is not None:
self._declared_properties['defined_channels'] = set(defined_channels)
if parameter_names is not None:
self._declared_properties['parameter_names'] = set(map(str, parameter_names))
if measurement_names is not None:
self._declared_properties['measurement_names'] = set(map(str, measurement_names))
if integral is not None:
if defined_channels is not None and integral.keys() != defined_channels:
raise ValueError('Integral does not fit to defined channels', integral.keys(), defined_channels)
self._declared_properties['integral'] = {channel: ExpressionScalar(value)
for channel, value in integral.items()}
if duration:
self._declared_properties['duration'] = ExpressionScalar(duration)
self._linked_target = None
self.serialize_linked = False
self._register(registry=registry)
@property
def metadata(self) -> TemplateMetadata:
raise NotImplementedError('AbstractPulseTemplate does not support metadata yet. '
'Please file an issue if you need this feature.')
[docs]
def link_to(self, target: PulseTemplate, serialize_linked: bool=None):
"""Link to another pulse template.
Args:
target: Forward all getattr calls to this pulse template
serialize_linked: If true, serialization will be forwarded. Otherwise, serialization will ignore the link
"""
if self._linked_target:
raise RuntimeError('Cannot is already linked. If you REALLY need to relink call unlink() first.')
for frozen_property in self._frozen_properties:
if self._declared_properties[frozen_property] != getattr(target, frozen_property):
raise RuntimeError('Cannot link to target. Wrong value of property "%s"' % frozen_property)
if serialize_linked is not None:
self.serialize_linked = serialize_linked
self._linked_target = target
[docs]
def unlink(self):
"""Unlink a linked target. This might lead to unexpected behaviour as forwarded get attributes are not frozen"""
if self._linked_target:
warnings.warn("This might lead to unexpected behaviour as forwarded attributes are not frozen. Parent pulse"
" templates might rely on certain properties to be constant (for example due to caching).",
UnlinkWarning)
self._linked_target = None
def __getattr__(self, item: str) -> Any:
"""Forward all unknown attribute accesses."""
return getattr(self._linked_target, item)
[docs]
def get_serialization_data(self, serializer=None) -> Dict:
if self._linked_target and self.serialize_linked:
return self._linked_target.get_serialization_data(serializer=serializer)
if serializer:
raise RuntimeError('Old serialization not supported in new class')
data = super().get_serialization_data()
data.update(self._declared_properties)
return data
def _get_property(self, property_name: str) -> Any:
if self._linked_target:
return getattr(self._linked_target, property_name)
elif property_name in self._declared_properties:
self._frozen_properties.add(property_name)
return self._declared_properties[property_name]
else:
raise NotSpecifiedError(self.identifier, property_name)
def _forward_if_linked(self, method_name: str, *args, **kwargs) -> Any:
if self._linked_target:
return getattr(self._linked_target, method_name)(*args, **kwargs)
else:
raise RuntimeError('Cannot call "%s". No linked target to refer to', method_name)
_create_program = partialmethod(_forward_if_linked, '_create_program')
_build_program = partialmethod(_forward_if_linked, '_build_program')
defined_channels = property(partial(_get_property, property_name='defined_channels'),
doc=_PROPERTY_DOC.format(name='defined_channels'))
duration = property(partial(_get_property, property_name='duration'),
doc=_PROPERTY_DOC.format(name='duration'))
measurement_names = property(partial(_get_property, property_name='measurement_names'),
doc=_PROPERTY_DOC.format(name='measurement_names'))
integral = property(partial(_get_property, property_name='integral'),
doc=_PROPERTY_DOC.format(name='integral'))
parameter_names = property(partial(_get_property, property_name='parameter_names'),
doc=_PROPERTY_DOC.format(name='parameter_names'))
initial_values = property(partial(_get_property, property_name='initial_values'),
doc=_PROPERTY_DOC.format(name='initial_values'))
final_values = property(partial(_get_property, property_name='final_values'),
doc=_PROPERTY_DOC.format(name='final_values'))
__hash__ = None
class NotSpecifiedError(RuntimeError):
pass
[docs]
class UnlinkWarning(UserWarning):
pass