Skip to content

Commit

Permalink
Merge 26e94ea into 7a139d2
Browse files Browse the repository at this point in the history
  • Loading branch information
terrorfisch committed Mar 8, 2021
2 parents 7a139d2 + 26e94ea commit 13b4184
Show file tree
Hide file tree
Showing 16 changed files with 3,763 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -11,3 +11,6 @@ doc/source/examples/.ipynb_checkpoints/*
*.orig
MATLAB/+qc/personalPaths.mat
/doc/source/_autosummary/*
.idea/
.mypy_cache/*
tests/hardware/WX2184C.exe
5 changes: 3 additions & 2 deletions qupulse/_program/tabor.py
Expand Up @@ -11,7 +11,6 @@
from qupulse.utils.types import ChannelID, TimeType
from qupulse.hardware.awgs.base import ProgramEntry
from qupulse.hardware.util import get_sample_times, voltage_to_uint16
from qupulse.pulses.parameters import Parameter
from qupulse._program.waveforms import Waveform
from qupulse._program._loop import Loop
from qupulse._program.volatile import VolatileRepetitionCount, VolatileProperty
Expand Down Expand Up @@ -389,7 +388,8 @@ def __init__(self,
offsets: Tuple[float, float],
voltage_transformations: Tuple[Optional[callable], Optional[callable]],
sample_rate: TimeType,
mode: TaborSequencing = None
mode: TaborSequencing = None,
repetition_mode: str = "infinite",
):
if len(channels) != device_properties['chan_per_part']:
raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part']))
Expand Down Expand Up @@ -420,6 +420,7 @@ def __init__(self,
self._parsed_program = None # type: Optional[ParsedProgram]
self._mode = None
self._device_properties = device_properties
self._repetition_mode = repetition_mode

assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode"
if mode == TaborSequencing.SINGLE:
Expand Down
Empty file.
187 changes: 187 additions & 0 deletions qupulse/hardware/feature_awg/base.py
@@ -0,0 +1,187 @@
from abc import ABC, abstractmethod
from typing import Optional
import weakref

from qupulse.hardware.awgs.base import AWG
from qupulse.hardware.feature_awg.base_features import Feature, FeatureAble
from qupulse.utils.types import Collection

__all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature",
"AWGChannelTupleFeature"]


class AWGDeviceFeature(Feature):
"""Base class for features that are used for `AWGDevice`s"""
def __init__(self):
super().__init__(AWGDevice)


class AWGChannelFeature(Feature):
"""Base class for features that are used for `AWGChannel`s"""
def __init__(self):
super().__init__(_BaseAWGChannel)


class AWGChannelTupleFeature(Feature):
"""Base class for features that are used for `AWGChannelTuple`s"""
def __init__(self, channel_tuple: 'AWGChannelTuple'):
super().__init__(AWGChannelTuple)
self._channel_tuple = weakref.proxy(channel_tuple)


class AWGDevice(FeatureAble[AWGDeviceFeature], ABC):
"""Base class for all drivers of all arbitrary waveform generators"""

def __init__(self, name: str):
"""
Args:
name: The name of the device as a String
"""
super().__init__()
self._name = name

@property
def name(self) -> str:
"""Returns the name of a Device as a String"""
return self._name

@abstractmethod
def cleanup(self) -> None:
"""Function for cleaning up the dependencies of the device"""
raise NotImplementedError()

@property
@abstractmethod
def channels(self) -> Collection["AWGChannel"]:
"""Returns a list of all channels of a Device"""
raise NotImplementedError()

@property
@abstractmethod
def marker_channels(self) -> Collection["AWGMarkerChannel"]:
"""Returns a list of all marker channels of a device. The collection may be empty"""
raise NotImplementedError()

@property
@abstractmethod
def channel_tuples(self) -> Collection["AWGChannelTuple"]:
"""Returns a list of all channel tuples of a list"""
raise NotImplementedError()


class AWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC):
"""Base class for all groups of synchronized channels of an AWG"""

def __init__(self, idn: int):
"""
Args:
idn: The identification number of a channel tuple
"""
super().__init__()

self._idn = idn

@property
@abstractmethod
def channel_tuple_adapter(self) -> AWG:
"""Return old interface adapter object. See channel_tuple_wrapper for details."""

@property
def idn(self) -> int:
"""Returns the identification number of a channel tuple"""
return self._idn

@property
def name(self) -> str:
"""Returns the name of a channel tuple"""
return "{dev}_CT{idn}".format(dev=self.device.name, idn=self.idn)

@property
@abstractmethod
def sample_rate(self) -> float:
"""Returns the sample rate of a channel tuple as a float"""
raise NotImplementedError()

@property
@abstractmethod
def device(self) -> AWGDevice:
"""Returns the device which the channel tuple belong to"""
raise NotImplementedError()

@property
@abstractmethod
def channels(self) -> Collection["AWGChannel"]:
"""Returns a list of all channels of the channel tuple"""
raise NotImplementedError()

@property
@abstractmethod
def marker_channels(self) -> Collection["AWGMarkerChannel"]:
"""Returns a list of all marker channels of the channel tuple. The collection may be empty"""
raise NotImplementedError()


class _BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC):
"""Base class for a single channel of an AWG"""

def __init__(self, idn: int):
"""
Args:
idn: The identification number of a channel
"""
super().__init__()
self._idn = idn

@property
def idn(self) -> int:
"""Returns the identification number of a channel"""
return self._idn

@property
@abstractmethod
def device(self) -> AWGDevice:
"""Returns the device which the channel belongs to"""
raise NotImplementedError()

@property
@abstractmethod
def channel_tuple(self) -> Optional[AWGChannelTuple]:
"""Returns the channel tuple which a channel belongs to"""
raise NotImplementedError()

@abstractmethod
def _set_channel_tuple(self, channel_tuple) -> None:
"""
Sets the channel tuple which a channel belongs to
Args:
channel_tuple: reference to the channel tuple
"""
raise NotImplementedError()


class AWGChannel(_BaseAWGChannel, ABC):
"""Base class for a single channel of an AWG"""
@property
def name(self) -> str:
"""Returns the name of a channel"""
return "{dev}_C{idn}".format(dev=self.device.name, idn=self.idn)


class AWGMarkerChannel(_BaseAWGChannel, ABC):
"""Base class for a single marker channel of an AWG"""
@property
def name(self) -> str:
"""Returns the name of a marker channel"""
return "{dev}_M{idn}".format(dev=self.device.name, idn=self.idn)


class ProgramOverwriteException(RuntimeError):

def __init__(self, name) -> None:
super().__init__()
self.name = name

def __str__(self) -> str:
return "A program with the given name '{}' is already present on the device." \
" Use force to overwrite.".format(self.name)
98 changes: 98 additions & 0 deletions qupulse/hardware/feature_awg/base_features.py
@@ -0,0 +1,98 @@
from types import MappingProxyType
from typing import Callable, Generic, Mapping, Optional, Type, TypeVar
from abc import ABC

__all__ = ["Feature", "FeatureAble"]


class Feature:
"""
Base class for features of `FeatureAble`s.
"""
def __init__(self, target_type: Type["FeatureAble"]):
self._target_type = target_type

@property
def target_type(self) -> Type["FeatureAble"]:
return self._target_type


FeatureType = TypeVar("FeatureType", bound=Feature)
GetItemFeatureType = TypeVar("GetItemFeatureType", bound=Feature)


class FeatureAble(Generic[FeatureType]):
"""
Base class for all types that are able to handle features. The features are saved in a dictionary and the methods
can be called with the __getitem__-operator.
"""

def __init__(self):
super().__init__()
self._features = {}

def __getitem__(self, feature_type: Type[GetItemFeatureType]) -> GetItemFeatureType:
if isinstance(feature_type, str):
return self._features[feature_type]
if not isinstance(feature_type, type):
raise TypeError("Expected type-object as key, got \"{ftt}\" instead".format(
ftt=type(feature_type).__name__))
key_type = _get_base_feature_type(feature_type)
if key_type is None:
raise TypeError("Unexpected type of feature: {ft}".format(ft=feature_type.__name__))
if key_type not in self._features:
raise KeyError("Could not get feature for type: {ft}".format(ft=feature_type.__name__))
return self._features[key_type]

def add_feature(self, feature: FeatureType) -> None:
"""
The method adds the feature to a Dictionary with all features
Args:
feature: A certain feature which functions should be added to the dictionary _features
"""
feature_type = _get_base_feature_type(type(feature))
if feature_type is None:
raise TypeError("Unexpected type of feature: {ft}".format(ft=type(feature).__name__))
if not isinstance(self, feature.target_type):
raise TypeError("Features with type \"{ft}\" belong to \"{tt}\"-objects".format(
ft=type(feature).__name__, tt=feature.target_type.__name__))
if feature_type in self._features:
raise KeyError(self, "Feature with type \"{ft}\" already exists".format(ft=feature_type.__name__))
self._features[feature_type] = feature
# Also adding the feature with the string as the key. With this you can you the name as a string for __getitem__
self._features[feature_type.__name__] = feature

@property
def features(self) -> Mapping[FeatureType, Callable]:
"""Returns the dictionary with all features of a FeatureAble"""
return MappingProxyType(self._features)


def _get_base_feature_type(feature_type: Type[Feature]) -> Type[Optional[Feature]]:
"""
This function searches for the second inheritance level under `Feature` (i.e. level under `AWGDeviceFeature`,
`AWGChannelFeature` or `AWGChannelTupleFeature`). This is done to ensure, that nobody adds the same feature
twice, but with a type of a different inheritance level as key.
Args:
feature_type: Type of the feature
Returns:
Base type of the feature_type, two inheritance levels under `Feature`
"""
if not issubclass(feature_type, Feature):
return type(None)

# Search for base class on the inheritance line of Feature
for base in feature_type.__bases__:
if issubclass(base, Feature):
result_type = base
break
else:
return type(None)

if Feature in result_type.__bases__:
return feature_type
else:
return _get_base_feature_type(result_type)
62 changes: 62 additions & 0 deletions qupulse/hardware/feature_awg/channel_tuple_wrapper.py
@@ -0,0 +1,62 @@
from typing import Tuple, Optional, Callable, Set

from qupulse import ChannelID
from qupulse._program._loop import Loop
from qupulse.hardware.feature_awg.base import AWGChannelTuple
from qupulse.hardware.feature_awg.features import ProgramManagement, VolatileParameters
from qupulse.hardware.awgs.base import AWG


class ChannelTupleAdapter(AWG):
"""
This class serves as an adapter between the old Class AWG and the new driver abstraction. It routes all the methods
the AWG class to the corresponding methods of the new driver.
"""
def __copy__(self) -> None:
pass

def __init__(self, channel_tuple: AWGChannelTuple):
super().__init__(channel_tuple.name)
self._channel_tuple = channel_tuple

@property
def num_channels(self) -> int:
return len(self._channel_tuple.channels)

@property
def num_markers(self) -> int:
return len(self._channel_tuple.marker_channels)

def upload(self, name: str,
program: Loop,
channels: Tuple[Optional[ChannelID], ...],
markers: Tuple[Optional[ChannelID], ...],
voltage_transformation: Tuple[Optional[Callable], ...],
force: bool = False) -> None:
return self._channel_tuple[ProgramManagement].upload(name=name, program=program,
channels=channels,
marker_channels=markers,
voltage_transformation=voltage_transformation,
repetition_mode=None,
force=force)

def remove(self, name: str) -> None:
return self._channel_tuple[ProgramManagement].remove(name)

def clear(self) -> None:
return self._channel_tuple[ProgramManagement].clear()

def arm(self, name: Optional[str]) -> None:
return self._channel_tuple[ProgramManagement].arm(name)

@property
def programs(self) -> Set[str]:
return self._channel_tuple[ProgramManagement].programs

@property
def sample_rate(self) -> float:
return self._channel_tuple.sample_rate

def set_volatile_parameters(self, program_name: str, parameters):
self._channel_tuple[VolatileParameters].set_volatile_parameters(program_name, parameters)

0 comments on commit 13b4184

Please sign in to comment.