From 28788c50760ff80718aaa7f6b7dff54e2ced6740 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 4 Nov 2019 10:02:36 +0100 Subject: [PATCH 001/107] Start der Implementierung der AWG-Treiber --- qupulse/hardware/awgs/AWGDriver.py | 99 ++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 qupulse/hardware/awgs/AWGDriver.py diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py new file mode 100644 index 000000000..72bdc2bf6 --- /dev/null +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -0,0 +1,99 @@ +from abc import ABC, abstractmethod + + +class AWGDevice(ABC): + channels = [] + channel_groups = [] + + @abstractmethod + def initialize(self): + pass + + @abstractmethod + def synchronoize_channels(self, group: int): + pass + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Wie wird der Return vom String umgesetzt + pass + +class AWGChannelTuple(ABC): + def __init__(self, channel_tuple_id, device: AWGDevice, channels, sample_rate, programs): + self._channel_tuptle_id = channel_tuple_id + self._device = device + self._channels = channels + self._sample_rate = sample_rate + self._programs = programs + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Wie wird der Return vom String umgesetzt + pass + + +class AWGChannel(ABC): + def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): + self._channel_id = channel_id + self._device = device + self._channel_tupel = channel_tupel + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Was passiert wenn was anderes als ein String returnt wird? + pass + + # muss die Methode dann auch abstrakt sein? + @property + def channel_id(self): + return self._channel_id + + # braucht channelId einen Setter? + +class AWGProgrammManager(ABC): + @abstractmethod + def add(self, program: Programm): + pass + + @abstractmethod + def get(self, name: str) -> Programm: + # Wie macht man die rückgabe von Programm? + pass + + @abstractmethod + def remove(self, name: str): + pass + + @abstractmethod + def clear(self): + pass + + +# Basiskalssen + +class BaseFeature(ABC): + pass + + +class AWGDeviceFeature(AWGDevice, ABC, BaseFeature): + pass + + +class AWGChannelFeature(AWGChannel, ABC, BaseFeature): + pass + + +class AWGChannelTupleFeature(AWGChannelTuple, ABC, BaseFeature): + pass From 6995a96e9089266bccf042794c66a04a83f7b58a Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 5 Nov 2019 12:41:53 +0100 Subject: [PATCH 002/107] Test push --- qupulse/hardware/awgs/AWGDriver.py | 106 +++++++++++++++++++---------- 1 file changed, 69 insertions(+), 37 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 72bdc2bf6..8b54770c6 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,9 +1,47 @@ from abc import ABC, abstractmethod +class BaseFeature(ABC): + pass + + +class AWGDeviceFeature(ABC, BaseFeature): + pass + + +class AWGChannelFeature(ABC, BaseFeature): + pass + + +class AWGChannelTupleFeature(ABC, BaseFeature): + pass + + +class AWGProgrammManager(ABC): + @abstractmethod + def add(self, program: Programm): + pass + + @abstractmethod + def get(self, name: str) -> Programm: + # Wie macht man die rückgabe von Programm? + pass + + @abstractmethod + def remove(self, name: str): + pass + + @abstractmethod + def clear(self): + pass + + class AWGDevice(ABC): - channels = [] - channel_groups = [] + def __init__(self): + self._channels = [] + self._channel_groups = [] + + self._featureList = [] @abstractmethod def initialize(self): @@ -22,14 +60,22 @@ def _send_querry(self, cmd: str) -> str: # Wie wird der Return vom String umgesetzt pass + @abstractmethod + def add_feature(self, feature: AWGDeviceFeature): + self._featureList.append(feature) + + class AWGChannelTuple(ABC): - def __init__(self, channel_tuple_id, device: AWGDevice, channels, sample_rate, programs): + def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, + programs: AWGProgrammManager): self._channel_tuptle_id = channel_tuple_id self._device = device self._channels = channels self._sample_rate = sample_rate self._programs = programs + self._featureList = [] + @abstractmethod def _send_cmd(self, cmd: str): pass @@ -39,6 +85,10 @@ def _send_querry(self, cmd: str) -> str: # Wie wird der Return vom String umgesetzt pass + @abstractmethod + def _add_feature(self, feature: AWGChannelFeature): + self._featureList.append(feature) + class AWGChannel(ABC): def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): @@ -46,6 +96,8 @@ def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannel self._device = device self._channel_tupel = channel_tupel + self._featureList = [] + @abstractmethod def _send_cmd(self, cmd: str): pass @@ -55,45 +107,25 @@ def _send_querry(self, cmd: str) -> str: # Was passiert wenn was anderes als ein String returnt wird? pass + @abstractmethod + def addFeature(self, feature: AWGChannelFeature): + self._featureList.append(feature) + # muss die Methode dann auch abstrakt sein? + + # Getter fuer alle Attribute @property def channel_id(self): return self._channel_id - # braucht channelId einen Setter? - -class AWGProgrammManager(ABC): - @abstractmethod - def add(self, program: Programm): - pass - - @abstractmethod - def get(self, name: str) -> Programm: - # Wie macht man die rückgabe von Programm? - pass - - @abstractmethod - def remove(self, name: str): - pass - - @abstractmethod - def clear(self): - pass - - -# Basiskalssen - -class BaseFeature(ABC): - pass - - -class AWGDeviceFeature(AWGDevice, ABC, BaseFeature): - pass - + @property + def device(self): + return self.device -class AWGChannelFeature(AWGChannel, ABC, BaseFeature): - pass + @property + def channel_tupel(self): + return self.channel_tupel + # braucht channelId einen Setter? -class AWGChannelTupleFeature(AWGChannelTuple, ABC, BaseFeature): - pass +# Basisklassen From 228a55aa2b8e40156b5418e271747d3a4b7bfa42 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Fri, 22 Nov 2019 08:01:43 +0100 Subject: [PATCH 003/107] Anfang der Umsetzung der Feature Strucktur --- qupulse/hardware/awgs/AWGDriver.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 8b54770c6..5ce54fa43 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -2,7 +2,8 @@ class BaseFeature(ABC): - pass + def __init__(self): + self._functionList = [] class AWGDeviceFeature(ABC, BaseFeature): @@ -17,21 +18,25 @@ class AWGChannelTupleFeature(ABC, BaseFeature): pass -class AWGProgrammManager(ABC): - @abstractmethod - def add(self, program: Programm): +class Program: + def __init__(self, name: str, program: Loop): + self._name = name + self._program = program + self._channel_ids = [] + self._marker_ids = [] + + +class AWGProgramManager: + def add(self, program: Program): pass - @abstractmethod - def get(self, name: str) -> Programm: + def get(self, name: str) -> Program: # Wie macht man die rückgabe von Programm? pass - @abstractmethod def remove(self, name: str): pass - @abstractmethod def clear(self): pass @@ -41,6 +46,7 @@ def __init__(self): self._channels = [] self._channel_groups = [] + # Code redundanz self._featureList = [] @abstractmethod @@ -63,11 +69,15 @@ def _send_querry(self, cmd: str) -> str: @abstractmethod def add_feature(self, feature: AWGDeviceFeature): self._featureList.append(feature) + # str is not a callable + for function in feature._functionList: + #in Liste einfügen + class AWGChannelTuple(ABC): def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, - programs: AWGProgrammManager): + programs: AWGProgramManager): self._channel_tuptle_id = channel_tuple_id self._device = device self._channels = channels From b897c679442ea91297460fa869e9c07346161091 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Fri, 6 Dec 2019 10:36:27 +0100 Subject: [PATCH 004/107] Erster Ansatz der Feature Strucktur fuers Device umgesetzt --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 03591aa92..3cf04d7a2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist/* doc/source/examples/.ipynb_checkpoints/* **.asv *.orig +.idea/ \ No newline at end of file From 89325290fff8734d8e072373b8ab0bb022f48447 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 9 Dec 2019 14:39:03 +0100 Subject: [PATCH 005/107] Erster Fertiger Entwurf - vor dem Testen --- qupulse/hardware/awgs/AWGDriver.py | 96 +++++++++++++++++++----------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 5ce54fa43..fc22acd4b 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -4,6 +4,27 @@ class BaseFeature(ABC): def __init__(self): self._functionList = [] + directory = dir(self) + tmp_list = [] + i = 0 + for attr in directory: + if callable(getattr(self, attr)) and attr[0] != "_": + tmp_list.append(attr) + + @property + def function_list(self): + return tuple(self._functionList) + + +class FeatureList(ABC): + def __init__(self): + self._featureList = [] + + def add_feature(self, feature: BaseFeature): + for function in feature.function_list: + setattr(self, function, getattr(feature, function)) + + self._featureList.append(feature) class AWGDeviceFeature(ABC, BaseFeature): @@ -18,36 +39,55 @@ class AWGChannelTupleFeature(ABC, BaseFeature): pass -class Program: +class Program(ABC): def __init__(self, name: str, program: Loop): self._name = name self._program = program self._channel_ids = [] self._marker_ids = [] + @property + def name(self): + return self._name + + @property + def program(self): + return self._program + + @property + def channel_ids(self): + return self._channel_ids + + @property + def marker_ids(self): + return self.marker_ids + -class AWGProgramManager: +class AWGProgramManager(ABC): + @abstractmethod def add(self, program: Program): pass + @abstractmethod def get(self, name: str) -> Program: - # Wie macht man die rückgabe von Programm? pass + @abstractmethod def remove(self, name: str): pass + @abstractmethod def clear(self): pass -class AWGDevice(ABC): +class AWGDevice(ABC, FeatureList): def __init__(self): self._channels = [] self._channel_groups = [] - # Code redundanz - self._featureList = [] + def add_feature(self, feature: AWGDeviceFeature): + super().add_feature(feature) @abstractmethod def initialize(self): @@ -63,19 +103,18 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Wie wird der Return vom String umgesetzt pass - @abstractmethod - def add_feature(self, feature: AWGDeviceFeature): - self._featureList.append(feature) - # str is not a callable - for function in feature._functionList: - #in Liste einfügen + @property + def channels(self): + return self._channels + @property + def channel_group(self): + return self._channel_groups -class AWGChannelTuple(ABC): +class AWGChannelTuple(ABC, FeatureList): def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, programs: AWGProgramManager): self._channel_tuptle_id = channel_tuple_id @@ -84,7 +123,8 @@ def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_ra self._sample_rate = sample_rate self._programs = programs - self._featureList = [] + def add_feature(self, feature: AWGChannelTupleFeature): + super().add_feature(feature) @abstractmethod def _send_cmd(self, cmd: str): @@ -92,21 +132,17 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Wie wird der Return vom String umgesetzt pass - @abstractmethod - def _add_feature(self, feature: AWGChannelFeature): - self._featureList.append(feature) - -class AWGChannel(ABC): +class AWGChannel(ABC, FeatureList): def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): self._channel_id = channel_id self._device = device self._channel_tupel = channel_tupel - self._featureList = [] + def add_feature(self, feature: AWGChannelFeature): + super().add_feature(feature) @abstractmethod def _send_cmd(self, cmd: str): @@ -114,28 +150,16 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Was passiert wenn was anderes als ein String returnt wird? pass - @abstractmethod - def addFeature(self, feature: AWGChannelFeature): - self._featureList.append(feature) - - # muss die Methode dann auch abstrakt sein? - - # Getter fuer alle Attribute @property def channel_id(self): return self._channel_id @property def device(self): - return self.device + return self._device @property def channel_tupel(self): - return self.channel_tupel - - # braucht channelId einen Setter? - -# Basisklassen + return self._channel_tupel From 25f35d80ab9be91f9ce66edf3aefb0348ead92da Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 14 Jan 2020 10:22:25 +0100 Subject: [PATCH 006/107] Erste Verbesserungen eingebaut --- qupulse/hardware/awgs/AWGDriver.py | 143 +++++++++++++--------- tests/hardware/awg_tests.py | 186 +++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 55 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index fc22acd4b..1800a70c7 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,46 +1,76 @@ from abc import ABC, abstractmethod +from qupulse._program._loop import Loop +from typing import TypeVar, Generic, List +# TODO: Abstrakte Getter und Setter + class BaseFeature(ABC): def __init__(self): - self._functionList = [] + super().__init__() + + self._function_list = {} + directory = dir(self) - tmp_list = [] i = 0 for attr in directory: if callable(getattr(self, attr)) and attr[0] != "_": - tmp_list.append(attr) + if not (attr in self._function_list): + self._function_list[attr] = getattr(self, attr) + # self._functionList.append(attr) + + # TODO: es heisst function_list aber ist ein Dictionary @property def function_list(self): - return tuple(self._functionList) + return tuple(self._function_list) -class FeatureList(ABC): +FeatureType = TypeVar(BaseFeature) + + +class FeatureList(Generic[FeatureType], ABC): def __init__(self): - self._featureList = [] + super().__init__() + + self._feature_list = {} - def add_feature(self, feature: BaseFeature): + @property + def feature_list(self): + return self._feature_list + + def add_feature(self, feature: FeatureType): for function in feature.function_list: setattr(self, function, getattr(feature, function)) - self._featureList.append(feature) + # self._feature_list.append(feature) + self._feature_list[type(feature).__name__] = feature + @abstractmethod + def _send_cmd(self, cmd: str): + pass -class AWGDeviceFeature(ABC, BaseFeature): + @abstractmethod + def _send_query(self, cmd: str) -> str: + pass + + +class AWGDeviceFeature(BaseFeature, ABC): pass -class AWGChannelFeature(ABC, BaseFeature): +class AWGChannelFeature(BaseFeature, ABC): pass -class AWGChannelTupleFeature(ABC, BaseFeature): +class AWGChannelTupleFeature(BaseFeature, ABC): pass class Program(ABC): - def __init__(self, name: str, program: Loop): + def __init__(self, name: str, program: "Loop"): + super().__init__() + self._name = name self._program = program self._channel_ids = [] @@ -81,76 +111,73 @@ def clear(self): pass -class AWGDevice(ABC, FeatureList): +class AWGDevice(FeatureList[AWGDeviceFeature], ABC): def __init__(self): - self._channels = [] - self._channel_groups = [] + super().__init__() - def add_feature(self, feature: AWGDeviceFeature): - super().add_feature(feature) + self._channels = List["AWGChannel"] # TODO: "AWGChannel" + self._channel_groups = List[AWGChannelTuple] @abstractmethod def initialize(self): pass @abstractmethod - def synchronoize_channels(self, group: int): - pass - - @abstractmethod - def _send_cmd(self, cmd: str): - pass - - @abstractmethod - def _send_querry(self, cmd: str) -> str: + def cleanup(self): pass @property def channels(self): return self._channels + # TODO: Ueberpruefung in " " + #@channels.setter + #@abstractmethod + #def channels(self, channels: List["AWGChannel"]): + # pass + @property def channel_group(self): return self._channel_groups -class AWGChannelTuple(ABC, FeatureList): - def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, - programs: AWGProgramManager): - self._channel_tuptle_id = channel_tuple_id +class AWGChannelTuple(FeatureList[AWGChannelTupleFeature], ABC): + def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): + super().__init__() + + self._channel_tuple_id = channel_tuple_id self._device = device self._channels = channels - self._sample_rate = sample_rate - self._programs = programs - def add_feature(self, feature: AWGChannelTupleFeature): - super().add_feature(feature) + #@property + #@abstractmethod + #def sample_rate(self): + # pass - @abstractmethod - def _send_cmd(self, cmd: str): - pass - - @abstractmethod - def _send_querry(self, cmd: str) -> str: - pass + #@sample_rate.setter + #@abstractmethod + #def channel_tuple(self, sample_rate: float): + # pass + @property + def channel_tuple_id(self): + return self._channel_tuple_id -class AWGChannel(ABC, FeatureList): - def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): - self._channel_id = channel_id - self._device = device - self._channel_tupel = channel_tupel + @property + def device(self): + return self._device - def add_feature(self, feature: AWGChannelFeature): - super().add_feature(feature) + @property + def channels(self): + return self._channels - @abstractmethod - def _send_cmd(self, cmd: str): - pass - @abstractmethod - def _send_querry(self, cmd: str) -> str: - pass +class AWGChannel(FeatureList[AWGChannelFeature], ABC): + def __init__(self, channel_id: int, device: AWGDevice): + super().__init__() + self._channel_id = channel_id + self._device = device + self._channel_tuple = None @property def channel_id(self): @@ -161,5 +188,11 @@ def device(self): return self._device @property - def channel_tupel(self): + def channel_tuple(self): return self._channel_tupel + + # TODO: @channel_tuple.setter da hat kann es keinen _ davor haben. -> Namensgebungs? + + @channel_tuple.setter + def channel_tuple(self, channel_tuple: AWGChannelTuple): + self._channel_tuple = channel_tuple diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index e69de29bb..d85607509 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -0,0 +1,186 @@ +# Warnungen ignorieren +import warnings + +warnings.simplefilter("ignore", UserWarning) + +from qupulse.hardware.awgs.AWGDriver import * +import unittest +from typing import TypeVar, Generic, List + +print("--Device Test:") + + +class TestDevice(AWGDevice): + def __init__(self): + super().__init__() + self.initialize() + self._send_cmd("TestCMD") + + def initialize(self): + print("initialize") + + #def synchronize_channels(self, group: int): + # print("synchronize channels") + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query") + return "test String" + + def cleanup(self): + pass + + +class DeviceTestFeature(AWGDeviceFeature): + def device_test_feature_methode1(self): + print("feature1methode1 works") + + def device_test_feature_methode2(self): + print("feature1methode2 works") + + +deviceTestFeature = DeviceTestFeature() +device = TestDevice() + +print("Dir(device): ") +print(dir(device)) +device.add_feature(deviceTestFeature) +print("Dir(device) mit Feature: ") +print(dir(device)) + +device.initialize() +device.device_test_feature_methode1() +device.device_test_feature_methode2() + +print("") + + +class Loop: + pass + + +testLoop = Loop() + + +class TestProgram(Program): + def __init__(self, name: str, loop: Loop): + super().__init__(name, loop) + + +testProgram = TestProgram("TestProgram", testLoop) + + +class TestProgramManager(AWGProgramManager): + def add(self, program: Program): + print("Test " + Program) + + def get(self): + pass + + def remove(self, name: str): + print("remove") + + def clear(self): + print("clear") + + +testProgramManager = TestProgramManager() + +print("--ChannelTupelTest:") + + +class TestChannelTuple(AWGChannelTuple): + def __init__(self): + super().__init__(1, device, 8) + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query") + return str + + def sample_rate(self, sample_rate: float): + pass + + +channelTupel = TestChannelTuple() + + +class ChannelTupelTestFeature(AWGChannelTupleFeature): + def channel_tupel_test_feature_methode1(self): + print("ChannelTupelTestFeatureMethode1 works") + + def channel_tupel_test_feature_methode2(self): + print("ChannelTupelTestFeatureMethode2 works") + + +channelTupelTestFeature = ChannelTupelTestFeature() + +channelTupel.add_feature(channelTupelTestFeature) +print("dir(channel):") +print(dir(channelTupel)) +channelTupel.add_feature(channelTupelTestFeature) +print("dir(channel):") +print(dir(channelTupel)) + +channelTupel.channel_tupel_test_feature_methode1() +channelTupel.channel_tupel_test_feature_methode2() + +print("") + + +class TestChannel(AWGChannel): + def __init__(self, channel_id: int): + super().__init__(channel_id, device) + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query: " + cmd) + return str + + +print("--ChannelTest:") + + +class ChannelTestFeature(AWGChannelFeature): + def channel_test_feature_methode1(self): + print("ChannelTestFeatureMethode1 works") + + def channel_test_feature_methode2(self): + print("ChannelTestFeatureMethode2 works") + + +class SynchronizeChannels(AWGChannelFeature): + def synchronize(self, test: List[AWGChannel]): + print("ChannelSynchronisieren") + + +channel = TestChannel(1) +channelTestFeature = ChannelTestFeature() + +print("dir(channel):") +print(dir(channel)) +channel.add_feature(channelTestFeature) +print("dir(channel):") +print(dir(channel)) +channel.channel_test_feature_methode1() +channel.channel_test_feature_methode2() + + +class TestAWGDriver(unittest.TestCase): + + def TestDeviceAddFeature(self): + pass + + +test_list = List[AWGChannel] + + +#--- + +testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), TestChannel(6), TestChannel(7)] \ No newline at end of file From a6c219f64235f4b1833b5d961c10e164cde15094 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 14 Jan 2020 13:18:46 +0100 Subject: [PATCH 007/107] Abstrakte Property ueberarbeitet --- qupulse/hardware/awgs/AWGDriver.py | 32 +++++++++--------------------- tests/hardware/awg_tests.py | 12 +++-------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 1800a70c7..82a6fecd0 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -2,9 +2,6 @@ from qupulse._program._loop import Loop from typing import TypeVar, Generic, List - -# TODO: Abstrakte Getter und Setter - class BaseFeature(ABC): def __init__(self): super().__init__() @@ -17,7 +14,6 @@ def __init__(self): if callable(getattr(self, attr)) and attr[0] != "_": if not (attr in self._function_list): self._function_list[attr] = getattr(self, attr) - # self._functionList.append(attr) # TODO: es heisst function_list aber ist ein Dictionary @@ -43,7 +39,6 @@ def add_feature(self, feature: FeatureType): for function in feature.function_list: setattr(self, function, getattr(feature, function)) - # self._feature_list.append(feature) self._feature_list[type(feature).__name__] = feature @abstractmethod @@ -130,12 +125,6 @@ def cleanup(self): def channels(self): return self._channels - # TODO: Ueberpruefung in " " - #@channels.setter - #@abstractmethod - #def channels(self, channels: List["AWGChannel"]): - # pass - @property def channel_group(self): return self._channel_groups @@ -149,15 +138,15 @@ def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): self._device = device self._channels = channels - #@property - #@abstractmethod - #def sample_rate(self): - # pass + @property + @abstractmethod + def sample_rate(self): + pass - #@sample_rate.setter - #@abstractmethod - #def channel_tuple(self, sample_rate: float): - # pass + @sample_rate.setter + @abstractmethod + def sample_rate(self, sample_rate: float): + pass @property def channel_tuple_id(self): @@ -191,8 +180,5 @@ def device(self): def channel_tuple(self): return self._channel_tupel - # TODO: @channel_tuple.setter da hat kann es keinen _ davor haben. -> Namensgebungs? - - @channel_tuple.setter - def channel_tuple(self, channel_tuple: AWGChannelTuple): + def _set_channel_tuple(self, channel_tuple: AWGChannelTuple): self._channel_tuple = channel_tuple diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index d85607509..1764a85dc 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -19,9 +19,6 @@ def __init__(self): def initialize(self): print("initialize") - #def synchronize_channels(self, group: int): - # print("synchronize channels") - def _send_cmd(self, cmd: str): print("send cmd: " + cmd) @@ -141,7 +138,7 @@ def _send_cmd(self, cmd: str): def _send_query(self, cmd: str) -> str: print("send query: " + cmd) - return str + return cmd print("--ChannelTest:") @@ -173,14 +170,11 @@ def synchronize(self, test: List[AWGChannel]): class TestAWGDriver(unittest.TestCase): - def TestDeviceAddFeature(self): pass test_list = List[AWGChannel] - -#--- - -testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), TestChannel(6), TestChannel(7)] \ No newline at end of file +testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), + TestChannel(6), TestChannel(7)] From 910d7c0721896b7eef6b9d654b058784a9bca447 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Fri, 17 Jan 2020 10:52:01 +0100 Subject: [PATCH 008/107] Finished AWG Driver --- qupulse/hardware/awgs/AWGDriver.py | 228 ++++++++++++++++------------- tests/hardware/awg_tests.py | 180 ----------------------- 2 files changed, 123 insertions(+), 285 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 82a6fecd0..1ecd95c89 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,184 +1,202 @@ +import warnings from abc import ABC, abstractmethod -from qupulse._program._loop import Loop -from typing import TypeVar, Generic, List +from copy import copy +from typing import TypeVar, Generic, List, Iterable, Dict, Callable + class BaseFeature(ABC): + """ + Base class for features of `FeatureAble`s. + + Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. + This ensures that all targets for the same feature are using the same signature for the feature's functions. All + public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` + dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. + """ + def __init__(self): super().__init__() - self._function_list = {} + self._functions = self._read_functions() + + def _read_functions(self) -> Dict[str, Callable]: + """ + Reads the functions of a feature and returns them as a dictionary + Return: + Returns dictionary with all functions of the feature + """ directory = dir(self) - i = 0 + function_list = {} for attr in directory: - if callable(getattr(self, attr)) and attr[0] != "_": - if not (attr in self._function_list): - self._function_list[attr] = getattr(self, attr) - - # TODO: es heisst function_list aber ist ein Dictionary + if callable(getattr(type(self), attr)) and attr[0] != "_": + if not (attr in function_list): + function_list[attr] = getattr(self, attr) + return function_list @property - def function_list(self): - return tuple(self._function_list) + def functions(self) -> Dict[str, Callable]: + """Returns a copy of the dictionary with all public functions of the feature""" + return copy(self._functions) FeatureType = TypeVar(BaseFeature) -class FeatureList(Generic[FeatureType], ABC): +class FeatureAble(Generic[FeatureType], ABC): + """Base class for all classes that are able to add features""" + def __init__(self): super().__init__() - self._feature_list = {} + self._features = {} @property - def feature_list(self): - return self._feature_list + def features(self) -> Dict[str, Callable]: + """Returns the dictionary with all features of a FeatureAble""" + return copy(self._features) - def add_feature(self, feature: FeatureType): - for function in feature.function_list: - setattr(self, function, getattr(feature, function)) + def add_feature(self, feature: FeatureType) -> None: + """ + The method adds all functions of feature to a dictionary with all functions - self._feature_list[type(feature).__name__] = feature + Args: + feature: A certain feature which functions should be added to the dictionary _features + """ + if not isinstance(feature, FeatureType): + raise TypeError("Invalid type for feature") - @abstractmethod - def _send_cmd(self, cmd: str): - pass + for function in feature.function_list: + if not hasattr(self, function): + setattr(self, function, getattr(feature, function)) + else: + warnings.warning( + f"Ommiting function \"{function}\": Another attribute with this name already exists.") - @abstractmethod - def _send_query(self, cmd: str) -> str: - pass + self._features[type(feature).__name__] = feature class AWGDeviceFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGDevice`s""" pass class AWGChannelFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGChannel`s""" pass class AWGChannelTupleFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGChannelTuple`s""" pass -class Program(ABC): - def __init__(self, name: str, program: "Loop"): - super().__init__() +class BaseAWG(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 - self._program = program - self._channel_ids = [] - self._marker_ids = [] - @property - def name(self): - return self._name + def __del__(self): + self.cleanup() - @property - def program(self): - return self._program + @abstractmethod + def cleanup(self) -> None: + """Function for cleaning up the dependencies of the device""" + pass @property - def channel_ids(self): - return self._channel_ids + def name(self) -> str: + """Returns the name of a Device as a String""" + return self._name @property - def marker_ids(self): - return self.marker_ids - - -class AWGProgramManager(ABC): @abstractmethod - def add(self, program: Program): - pass + def channels(self) -> Iterable["BaseAWGChannel"]: + """Returns a list of all channels of a Device""" + return self._channels + @property @abstractmethod - def get(self, name: str) -> Program: + def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + """Returns a list of all channel tuples of a list""" pass - @abstractmethod - def remove(self, name: str): - pass - - @abstractmethod - def clear(self): - pass +class BaseAWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): + """Base class for all groups of synchronized channels of an AWG""" -class AWGDevice(FeatureList[AWGDeviceFeature], ABC): - def __init__(self): + def __init__(self, idn: int): + """ + Args: + idn: The identification number of a channel tuple + """ super().__init__() - self._channels = List["AWGChannel"] # TODO: "AWGChannel" - self._channel_groups = List[AWGChannelTuple] - - @abstractmethod - def initialize(self): - pass + self._idn = idn + @property @abstractmethod - def cleanup(self): + def sample_rate(self) -> float: + """Returns the sample rate of a channel tuple as a float""" pass @property - def channels(self): - return self._channels - - @property - def channel_group(self): - return self._channel_groups - - -class AWGChannelTuple(FeatureList[AWGChannelTupleFeature], ABC): - def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): - super().__init__() - - self._channel_tuple_id = channel_tuple_id - self._device = device - self._channels = channels + def idn(self) -> int: + """Returns the identification number of a channel tuple""" + return self._idn @property @abstractmethod - def sample_rate(self): + def device(self) -> BaseAWG: + """Returns the device which the channel tuple belong to""" pass - @sample_rate.setter + @property @abstractmethod - def sample_rate(self, sample_rate: float): + def channels(self) -> Iterable["BaseAWGChannel"]: + """Returns a list of all channels of the channel tuple""" pass - @property - def channel_tuple_id(self): - return self._channel_tuple_id - - @property - def device(self): - return self._device - - @property - def channels(self): - return self._channels +class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): + """Base class for a single channel of an AWG""" -class AWGChannel(FeatureList[AWGChannelFeature], ABC): - def __init__(self, channel_id: int, device: AWGDevice): + def __init__(self, idn: int): + """ + Args: + idn: The identification number of a channel + """ super().__init__() - self._channel_id = channel_id - self._device = device - self._channel_tuple = None + self._idn = idn @property - def channel_id(self): - return self._channel_id + def idn(self) -> int: + """Returns the identification number of a channel""" + return self._idn @property - def device(self): - return self._device + @abstractmethod + def device(self) -> BaseAWG: + """Returns the device which the channel belongs to""" + pass @property - def channel_tuple(self): - return self._channel_tupel + @abstractmethod + def channel_tuple(self) -> BaseAWGChannelTuple: + """Returns the channel tuple which a channel belongs to""" + pass - def _set_channel_tuple(self, channel_tuple: AWGChannelTuple): - self._channel_tuple = channel_tuple + @abstractmethod + def _set_channel_tuple(self, channel_tuple: BaseAWGChannelTuple): + """ + Sets the channel tuple which a channel belongs to + Args: + channel_tuple: reference to the channel tuple + """ + pass diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index 1764a85dc..e69de29bb 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -1,180 +0,0 @@ -# Warnungen ignorieren -import warnings - -warnings.simplefilter("ignore", UserWarning) - -from qupulse.hardware.awgs.AWGDriver import * -import unittest -from typing import TypeVar, Generic, List - -print("--Device Test:") - - -class TestDevice(AWGDevice): - def __init__(self): - super().__init__() - self.initialize() - self._send_cmd("TestCMD") - - def initialize(self): - print("initialize") - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query") - return "test String" - - def cleanup(self): - pass - - -class DeviceTestFeature(AWGDeviceFeature): - def device_test_feature_methode1(self): - print("feature1methode1 works") - - def device_test_feature_methode2(self): - print("feature1methode2 works") - - -deviceTestFeature = DeviceTestFeature() -device = TestDevice() - -print("Dir(device): ") -print(dir(device)) -device.add_feature(deviceTestFeature) -print("Dir(device) mit Feature: ") -print(dir(device)) - -device.initialize() -device.device_test_feature_methode1() -device.device_test_feature_methode2() - -print("") - - -class Loop: - pass - - -testLoop = Loop() - - -class TestProgram(Program): - def __init__(self, name: str, loop: Loop): - super().__init__(name, loop) - - -testProgram = TestProgram("TestProgram", testLoop) - - -class TestProgramManager(AWGProgramManager): - def add(self, program: Program): - print("Test " + Program) - - def get(self): - pass - - def remove(self, name: str): - print("remove") - - def clear(self): - print("clear") - - -testProgramManager = TestProgramManager() - -print("--ChannelTupelTest:") - - -class TestChannelTuple(AWGChannelTuple): - def __init__(self): - super().__init__(1, device, 8) - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query") - return str - - def sample_rate(self, sample_rate: float): - pass - - -channelTupel = TestChannelTuple() - - -class ChannelTupelTestFeature(AWGChannelTupleFeature): - def channel_tupel_test_feature_methode1(self): - print("ChannelTupelTestFeatureMethode1 works") - - def channel_tupel_test_feature_methode2(self): - print("ChannelTupelTestFeatureMethode2 works") - - -channelTupelTestFeature = ChannelTupelTestFeature() - -channelTupel.add_feature(channelTupelTestFeature) -print("dir(channel):") -print(dir(channelTupel)) -channelTupel.add_feature(channelTupelTestFeature) -print("dir(channel):") -print(dir(channelTupel)) - -channelTupel.channel_tupel_test_feature_methode1() -channelTupel.channel_tupel_test_feature_methode2() - -print("") - - -class TestChannel(AWGChannel): - def __init__(self, channel_id: int): - super().__init__(channel_id, device) - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query: " + cmd) - return cmd - - -print("--ChannelTest:") - - -class ChannelTestFeature(AWGChannelFeature): - def channel_test_feature_methode1(self): - print("ChannelTestFeatureMethode1 works") - - def channel_test_feature_methode2(self): - print("ChannelTestFeatureMethode2 works") - - -class SynchronizeChannels(AWGChannelFeature): - def synchronize(self, test: List[AWGChannel]): - print("ChannelSynchronisieren") - - -channel = TestChannel(1) -channelTestFeature = ChannelTestFeature() - -print("dir(channel):") -print(dir(channel)) -channel.add_feature(channelTestFeature) -print("dir(channel):") -print(dir(channel)) -channel.channel_test_feature_methode1() -channel.channel_test_feature_methode2() - - -class TestAWGDriver(unittest.TestCase): - def TestDeviceAddFeature(self): - pass - - -test_list = List[AWGChannel] - -testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), - TestChannel(6), TestChannel(7)] From f212155dd0feef5410653d10f80c42b93be0cf97 Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:09:02 +0100 Subject: [PATCH 009/107] Rename hardware/awgs/base.py to old_base.py --- qupulse/hardware/awgs/{base.py => old_base.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename qupulse/hardware/awgs/{base.py => old_base.py} (100%) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/old_base.py similarity index 100% rename from qupulse/hardware/awgs/base.py rename to qupulse/hardware/awgs/old_base.py From 7e828de19c0137608843861791268dbcce854fba Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:09:28 +0100 Subject: [PATCH 010/107] Rename AWGDriver.py to base.py --- qupulse/hardware/awgs/{AWGDriver.py => base.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename qupulse/hardware/awgs/{AWGDriver.py => base.py} (100%) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/base.py similarity index 100% rename from qupulse/hardware/awgs/AWGDriver.py rename to qupulse/hardware/awgs/base.py From e0cde11e3cc839e8fcba0805613bd0a1f448d776 Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:10:44 +0100 Subject: [PATCH 011/107] Update tabor.py Import base classes from old_base --- qupulse/hardware/awgs/tabor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 90e7b1831..d42088d62 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -19,6 +19,8 @@ from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ make_combined_wave +assert(sys.byteorder == 'little') + __all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] From 2776a4c5023d50425ee57dbfe0317fa16cb9eaa4 Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:11:12 +0100 Subject: [PATCH 012/107] Update tektronix.py Import base classes from old_base --- qupulse/hardware/awgs/tektronix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qupulse/hardware/awgs/tektronix.py b/qupulse/hardware/awgs/tektronix.py index ab41e0a4f..e759fd4ed 100644 --- a/qupulse/hardware/awgs/tektronix.py +++ b/qupulse/hardware/awgs/tektronix.py @@ -14,7 +14,7 @@ "If you wish to use it execute qupulse.hardware.awgs.install_requirements('tektronix')") raise -from qupulse.hardware.awgs.base import AWG, AWGAmplitudeOffsetHandling, ProgramOverwriteException +from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling, ProgramOverwriteException from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible from qupulse._program.waveforms import Waveform as QuPulseWaveform From 1f8f77a85376b586210fc4336f46315894106651 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Fri, 17 Jan 2020 13:58:42 +0100 Subject: [PATCH 013/107] Minor bug fixes Refactoring of class/function names Modification of import statements for hardware.awgs.old_base --- qupulse/hardware/awgs/base.py | 129 +++++----------------- qupulse/hardware/awgs/base_features.py | 75 +++++++++++++ qupulse/hardware/setup.py | 2 +- tests/hardware/dummy_devices.py | 2 +- tests/hardware/tabor_dummy_based_tests.py | 3 +- 5 files changed, 107 insertions(+), 104 deletions(-) create mode 100644 qupulse/hardware/awgs/base_features.py diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 1ecd95c89..af35402c4 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,97 +1,25 @@ -import warnings from abc import ABC, abstractmethod -from copy import copy -from typing import TypeVar, Generic, List, Iterable, Dict, Callable +from typing import Iterable, Optional +from .base_features import BaseFeature, FeatureAble -class BaseFeature(ABC): - """ - Base class for features of `FeatureAble`s. - Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. - This ensures that all targets for the same feature are using the same signature for the feature's functions. All - public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` - dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. - """ - - def __init__(self): - super().__init__() - - self._functions = self._read_functions() - - def _read_functions(self) -> Dict[str, Callable]: - """ - Reads the functions of a feature and returns them as a dictionary - - Return: - Returns dictionary with all functions of the feature - """ - directory = dir(self) - function_list = {} - for attr in directory: - if callable(getattr(type(self), attr)) and attr[0] != "_": - if not (attr in function_list): - function_list[attr] = getattr(self, attr) - return function_list - - @property - def functions(self) -> Dict[str, Callable]: - """Returns a copy of the dictionary with all public functions of the feature""" - return copy(self._functions) - - -FeatureType = TypeVar(BaseFeature) - - -class FeatureAble(Generic[FeatureType], ABC): - """Base class for all classes that are able to add features""" - - def __init__(self): - super().__init__() - - self._features = {} - - @property - def features(self) -> Dict[str, Callable]: - """Returns the dictionary with all features of a FeatureAble""" - return copy(self._features) - - def add_feature(self, feature: FeatureType) -> None: - """ - The method adds all functions of feature to a dictionary with all functions - - Args: - feature: A certain feature which functions should be added to the dictionary _features - """ - if not isinstance(feature, FeatureType): - raise TypeError("Invalid type for feature") - - for function in feature.function_list: - if not hasattr(self, function): - setattr(self, function, getattr(feature, function)) - else: - warnings.warning( - f"Ommiting function \"{function}\": Another attribute with this name already exists.") - - self._features[type(feature).__name__] = feature - - -class AWGDeviceFeature(BaseFeature, ABC): +class BaseAWGFeature(BaseFeature, ABC): """Base class for features that are used for `AWGDevice`s""" pass -class AWGChannelFeature(BaseFeature, ABC): +class BaseAWGChannelFeature(BaseFeature, ABC): """Base class for features that are used for `AWGChannel`s""" pass -class AWGChannelTupleFeature(BaseFeature, ABC): +class BaseAWGChannelTupleFeature(BaseFeature, ABC): """Base class for features that are used for `AWGChannelTuple`s""" pass -class BaseAWG(FeatureAble[AWGDeviceFeature], ABC): +class BaseAWG(FeatureAble[BaseAWGFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -105,30 +33,30 @@ def __init__(self, name: str): def __del__(self): self.cleanup() - @abstractmethod - def cleanup(self) -> None: - """Function for cleaning up the dependencies of the device""" - pass - @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) -> Iterable["BaseAWGChannel"]: """Returns a list of all channels of a Device""" - return self._channels + raise NotImplementedError() @property @abstractmethod def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: """Returns a list of all channel tuples of a list""" - pass + raise NotImplementedError() -class BaseAWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): +class BaseAWGChannelTuple(FeatureAble[BaseAWGChannelTupleFeature], ABC): """Base class for all groups of synchronized channels of an AWG""" def __init__(self, idn: int): @@ -140,31 +68,31 @@ def __init__(self, idn: int): self._idn = idn - @property - @abstractmethod - def sample_rate(self) -> float: - """Returns the sample rate of a channel tuple as a float""" - pass - @property def idn(self) -> int: """Returns the identification number of a channel tuple""" return 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) -> BaseAWG: """Returns the device which the channel tuple belong to""" - pass + raise NotImplementedError() @property @abstractmethod def channels(self) -> Iterable["BaseAWGChannel"]: """Returns a list of all channels of the channel tuple""" - pass + raise NotImplementedError() -class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): +class BaseAWGChannel(FeatureAble[BaseAWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -184,19 +112,20 @@ def idn(self) -> int: @abstractmethod def device(self) -> BaseAWG: """Returns the device which the channel belongs to""" - pass + raise NotImplementedError() @property @abstractmethod - def channel_tuple(self) -> BaseAWGChannelTuple: + def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: """Returns the channel tuple which a channel belongs to""" - pass + raise NotImplementedError() @abstractmethod - def _set_channel_tuple(self, channel_tuple: BaseAWGChannelTuple): + 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 """ - pass + raise NotImplementedError() diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py new file mode 100644 index 000000000..7586668bf --- /dev/null +++ b/qupulse/hardware/awgs/base_features.py @@ -0,0 +1,75 @@ +import warnings +from abc import ABC +from copy import copy +from typing import TypeVar, Generic, Dict, Callable + + +class BaseFeature(ABC): + """ + Base class for features of `FeatureAble`s. + + Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. + This ensures that all targets for the same feature are using the same signature for the feature's functions. All + public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` + dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. + """ + + def __init__(self): + super().__init__() + + self._functions = self._read_functions() + + def _read_functions(self) -> Dict[str, Callable]: + """ + Reads the functions of a feature and returns them as a dictionary + + Return: + Returns dictionary with all functions of the feature + """ + directory = dir(self) + function_list: Dict[str, Callable] = {} + for attr in directory: + if callable(getattr(type(self), attr)) and attr[0] != "_": + if not (attr in function_list): + function_list[attr] = getattr(self, attr) + return function_list + + @property + def functions(self) -> Dict[str, Callable]: + """Returns a copy of the dictionary with all public functions of the feature""" + return copy(self._functions) + + +FeatureType = TypeVar("FeatureType", bound=BaseFeature) + + +class FeatureAble(Generic[FeatureType], ABC): + """Base class for all classes that are able to add features""" + + def __init__(self): + super().__init__() + + self._features = {} + + @property + def features(self) -> Dict[str, Callable]: + """Returns the dictionary with all features of a FeatureAble""" + return copy(self._features) + + def add_feature(self, feature: FeatureType) -> None: + """ + The method adds all functions of feature to a dictionary with all functions + + Args: + feature: A certain feature which functions should be added to the dictionary _features + """ + if not isinstance(feature, BaseFeature): + raise TypeError("Invalid type for feature") + + for function in feature.functions: + if not hasattr(self, function): + setattr(self, function, getattr(feature, function)) + else: + warnings.warn(f"Omitting function \"{function}\": Another attribute with this name already exists.") + + self._features[type(feature).__name__] = feature diff --git a/qupulse/hardware/setup.py b/qupulse/hardware/setup.py index bc2c76a8e..0460d9e71 100644 --- a/qupulse/hardware/setup.py +++ b/qupulse/hardware/setup.py @@ -3,7 +3,7 @@ import warnings import numbers -from qupulse.hardware.awgs.base import AWG +from qupulse.hardware.awgs.old_base import AWG from qupulse.hardware.dacs import DAC from qupulse._program._loop import Loop diff --git a/tests/hardware/dummy_devices.py b/tests/hardware/dummy_devices.py index a92ce1282..a90b2bd4f 100644 --- a/tests/hardware/dummy_devices.py +++ b/tests/hardware/dummy_devices.py @@ -2,7 +2,7 @@ from collections import deque -from qupulse.hardware.awgs.base import AWG, ProgramOverwriteException +from qupulse.hardware.awgs.old_base import AWG, ProgramOverwriteException from qupulse.hardware.dacs import DAC class DummyDAC(DAC): diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py index 554d489ac..b03debfd1 100644 --- a/tests/hardware/tabor_dummy_based_tests.py +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -8,9 +8,8 @@ import numpy as np -from qupulse.hardware.awgs.base import AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling from qupulse.hardware.awgs.tabor import TaborProgram, TaborAWGRepresentation, TaborProgramMemory -from qupulse._program.tabor import TableDescription, TimeType, TableEntry from tests.hardware.dummy_modules import import_package From 4f7c2593a6e9412ec1b39a93914f57b5bcf0d773 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Fri, 17 Jan 2020 14:35:34 +0100 Subject: [PATCH 014/107] Added a test script for the base drivers Actually no unit tests, yet, but a first example of how to use the base drivers --- tests/hardware/awg_base_tests.py | 242 +++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 tests/hardware/awg_base_tests.py diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py new file mode 100644 index 000000000..5d7266fe9 --- /dev/null +++ b/tests/hardware/awg_base_tests.py @@ -0,0 +1,242 @@ +from typing import Callable, Collection, Iterable, List, Optional + +from qupulse.hardware.awgs.base import BaseAWG, BaseAWGChannel, BaseAWGChannelTuple, BaseAWGFeature, \ + BaseAWGChannelFeature, BaseAWGChannelTupleFeature + + +######################################################################################################################## +# Features +######################################################################################################################## + +class SynchronizeChannelsFeature(BaseAWGFeature): + def __init__(self, sync_func: Callable[[int], None]): + """Storing the callable, to call it if needed below""" + super().__init__() + self._sync_func = sync_func + + def synchronize_channels(self, group_size: int) -> None: + """Forwarding call to callable object, which was provided threw __init__""" + self._sync_func(group_size) + + +class ChannelTupleNameFeature(BaseAWGChannelTupleFeature): + def __init__(self, name_get: Callable[[], str]): + """Storing the callable, to call it if needed below""" + super().__init__() + self._get_name = name_get + + def get_name(self) -> str: + """Forwarding call to callable object, which was provided threw __init__""" + return self._get_name() + + +class ChannelOffsetAmplitudeFeature(BaseAWGChannelFeature): + def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], + amp_get: Callable[[], float], amp_set: Callable[[float], None]): + """Storing all callables, to call them if needed below""" + super().__init__() + self._get_offset = offset_get + self._set_offset = offset_set + self._get_amp = amp_get + self._set_amp = amp_set + + def get_offset(self) -> float: + """Forwarding call to callable object, which was provided threw __init__""" + return self._get_offset() + + def set_offset(self, offset: float) -> None: + """Forwarding call to callable object, which was provided threw __init__""" + self._set_offset(offset) + + def get_amplitude(self) -> float: + """Forwarding call to callable object, which was provided threw __init__""" + return self._get_amp() + + def set_amplitude(self, amplitude: float) -> None: + """Forwarding call to callable object, which was provided threw __init__""" + self._set_amp(amplitude) + + +######################################################################################################################## +# Device & Channels +######################################################################################################################## + +class TestAWG(BaseAWG): + def __init__(self, name: str): + super().__init__(name) + + # Add feature to this object (self) + # During this call, the function of the feature is dynamically added to this object + self.add_feature(SynchronizeChannelsFeature(self._sync_chans)) + + self._channels = [TestAWGChannel(i, self) for i in range(8)] # 8 channels + self._channel_tuples: List["TestAWGChannelTuple"] = [] + + # Call the feature function, with the feature's signature + self.synchronize_channels(2) # default channel synchronization with a group size of 2 + + def cleanup(self) -> None: + """This will be called automatically in __del__""" + self._channels.clear() + self._channel_tuples.clear() + + @property + def channels(self) -> Collection["TestAWGChannel"]: + return self._channels + + @property + def channel_tuples(self) -> Collection["TestAWGChannelTuple"]: + return self._channel_tuples + + def _sync_chans(self, group_size: int) -> None: + """Implementation of the feature's function""" + if group_size not in [2, 4, 8]: # Allowed group sizes + raise ValueError("Invalid group size for channel synchronization") + + self._channel_tuples.clear() + tmp_channel_tuples: List[List["TestAWGChannel"]] = [[] for i in range(len(self._channels) // group_size)] + + # Preparing the channel structure + for i, channel in enumerate(self._channels): + tmp_channel_tuples[i // group_size].append(channel) + + # Create channel tuples with its belonging channels and refer to their parent tuple + for i, tmp_channel_tuple in enumerate(tmp_channel_tuples): + channel_tuple = TestAWGChannelTuple(i, self, tmp_channel_tuple) + self._channel_tuples.append(channel_tuple) + for channel in tmp_channel_tuple: + channel._set_channel_tuple(channel_tuple) + + +class TestAWGChannelTuple(BaseAWGChannelTuple): + def __init__(self, idn: int, device: TestAWG, channels: Iterable["TestAWGChannel"]): + super().__init__(idn) + + # Add feature to this object (self) + # During this call, the function of the feature is dynamically added to this object + self.add_feature(ChannelTupleNameFeature(self._get_name)) + + self._device = device + self._channels = tuple(channels) + self.sample_rate = 12.456 # default value + + @property + def sample_rate(self) -> float: + return self._sample_rate + + @sample_rate.setter + def sample_rate(self, sample_rate: float) -> None: + self._sample_rate = sample_rate + + @property + def device(self) -> TestAWG: + return self._device + + @property + def channels(self) -> Collection["TestAWGChannel"]: + return self._channels + + # Feature functions + def _get_name(self) -> str: + """Implementation of the feature's function""" + return chr(ord('A') + self.idn) # 0 -> 'A', 1 -> 'B', 2 -> 'C', ... + + +class TestAWGChannel(BaseAWGChannel): + def __init__(self, idn: int, device: TestAWG): + super().__init__(idn) + + # Add feature to this object (self) + # During this call, all functions of the feature are dynamically added to this object + self.add_feature(ChannelOffsetAmplitudeFeature(self._get_offs, + self._set_offs, + self._get_ampl, + self._set_ampl)) + + self._device = device + self._channel_tuple: Optional[TestAWGChannelTuple] = None + self._offset = 0.0 + self._amplitude = 5.0 + + @property + def device(self) -> TestAWG: + return self._device + + @property + def channel_tuple(self) -> Optional[TestAWGChannelTuple]: + return self._channel_tuple + + def _set_channel_tuple(self, channel_tuple: TestAWGChannelTuple) -> None: + self._channel_tuple = channel_tuple + + def _get_offs(self) -> float: + """Implementation of the feature's function""" + return self._offset + + def _set_offs(self, offset: float) -> None: + """Implementation of the feature's function""" + self._offset = offset + + def _get_ampl(self) -> float: + """Implementation of the feature's function""" + return self._amplitude + + def _set_ampl(self, amplitude: float) -> None: + """Implementation of the feature's function""" + self._amplitude = amplitude + + +######################################################################################################################## +# Test +######################################################################################################################## + +def test(): + device_name = "My device" + device = TestAWG(device_name) + + # Check if the default values were set correctly + assert device.name == device_name, "Invalid name for device" + assert len(device.channels) == 8, "Invalid number of channels" + assert len(device.channel_tuples) == 4, "Invalid default channel tuples for device" + + # Check if each channel is working correctly + for i, channel in enumerate(device.channels): + assert channel.idn == i, "Invalid channel id" + assert channel.get_offset() == 0, f"Invalid default offset for channel {i}" + assert channel.get_amplitude() == 5.0, f"Invalid default amplitude for channel {i}" + + offs = -0.1 * i + ampl = 0.5 + 3 * i + channel.set_offset(offs) + channel.set_amplitude(ampl) + assert channel.get_offset() == offs, f"Invalid offset for channel {i}" + assert channel.get_amplitude() == ampl, f"Invalid amplitude for channel {i}" + + # Check if each channel tuple is working fine + for group_size in [2, 4, 8]: + device.synchronize_channels(group_size) + + assert len(device.channel_tuples) == 8 // group_size, "Invalid number of channel tuples" + + # Check if channels and channel tuples are connected right + for i, channel in enumerate(device.channels): + assert channel.channel_tuple.idn == i // group_size, f"Invalid channel tuple {channel.channel_tuple.idn} for channel {i}" + assert channel in channel.channel_tuple.channels, f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}" + + # Check if an error is thrown, when trying to process an invalid parameter + try: + device.synchronize_channels(3) + assert True, "Missing error for invalid group size" + except ValueError: + pass + except: + assert True, "Wrong error for invalid group size" + + # Check if the channel tuples are still the same + assert len(device.channel_tuples) == 1, "Invalid number of channel tuples" + + print("Test successful :)") + + +if __name__ == "__main__": + test() From c219b620fbf9dcf4974f22a6f099e52c32511dbc Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 27 Jan 2020 15:49:55 +0100 Subject: [PATCH 015/107] Change of the feature-logic --- qupulse/hardware/awgs/base_features.py | 58 +++++++------------------- tests/hardware/awg_base_tests.py | 26 ++++++------ 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index 7586668bf..cc06c7991 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -1,64 +1,37 @@ +import typing +from typing import TypeVar, Generic, Dict, Callable import warnings from abc import ABC from copy import copy -from typing import TypeVar, Generic, Dict, Callable class BaseFeature(ABC): """ Base class for features of `FeatureAble`s. - - Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. - This ensures that all targets for the same feature are using the same signature for the feature's functions. All - public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` - dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. """ - - def __init__(self): - super().__init__() - - self._functions = self._read_functions() - - def _read_functions(self) -> Dict[str, Callable]: - """ - Reads the functions of a feature and returns them as a dictionary - - Return: - Returns dictionary with all functions of the feature - """ - directory = dir(self) - function_list: Dict[str, Callable] = {} - for attr in directory: - if callable(getattr(type(self), attr)) and attr[0] != "_": - if not (attr in function_list): - function_list[attr] = getattr(self, attr) - return function_list - - @property - def functions(self) -> Dict[str, Callable]: - """Returns a copy of the dictionary with all public functions of the feature""" - return copy(self._functions) + pass FeatureType = TypeVar("FeatureType", bound=BaseFeature) class FeatureAble(Generic[FeatureType], ABC): - """Base class for all classes that are able to add features""" + """ + Base class for all classes that are able to add features. The features are saved in a dictonary and the methods + can be called with __getitem__. + """ def __init__(self): super().__init__() self._features = {} - @property - def features(self) -> Dict[str, Callable]: - """Returns the dictionary with all features of a FeatureAble""" - return copy(self._features) + def __getitem__(self, feature_type: typing.Type[FeatureType]) -> FeatureType: + return self._features[feature_type] def add_feature(self, feature: FeatureType) -> None: """ - The method adds all functions of feature to a dictionary with all functions + 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 @@ -66,10 +39,9 @@ def add_feature(self, feature: FeatureType) -> None: if not isinstance(feature, BaseFeature): raise TypeError("Invalid type for feature") - for function in feature.functions: - if not hasattr(self, function): - setattr(self, function, getattr(feature, function)) - else: - warnings.warn(f"Omitting function \"{function}\": Another attribute with this name already exists.") + self._features[type(feature)] = feature - self._features[type(feature).__name__] = feature + @property + def features(self) -> Dict[FeatureType, Callable]: + """Returns the dictionary with all features of a FeatureAble""" + return copy(self._features) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 5d7266fe9..f71a4daf7 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -73,7 +73,7 @@ def __init__(self, name: str): self._channel_tuples: List["TestAWGChannelTuple"] = [] # Call the feature function, with the feature's signature - self.synchronize_channels(2) # default channel synchronization with a group size of 2 + self[SynchronizeChannelsFeature].synchronize_channels(2) # default channel synchronization with a group size of 2 def cleanup(self) -> None: """This will be called automatically in __del__""" @@ -135,7 +135,7 @@ def device(self) -> TestAWG: @property def channels(self) -> Collection["TestAWGChannel"]: return self._channels - + # Feature functions def _get_name(self) -> str: """Implementation of the feature's function""" @@ -165,14 +165,14 @@ def device(self) -> TestAWG: @property def channel_tuple(self) -> Optional[TestAWGChannelTuple]: return self._channel_tuple - + def _set_channel_tuple(self, channel_tuple: TestAWGChannelTuple) -> None: self._channel_tuple = channel_tuple def _get_offs(self) -> float: """Implementation of the feature's function""" return self._offset - + def _set_offs(self, offset: float) -> None: """Implementation of the feature's function""" self._offset = offset @@ -202,19 +202,19 @@ def test(): # Check if each channel is working correctly for i, channel in enumerate(device.channels): assert channel.idn == i, "Invalid channel id" - assert channel.get_offset() == 0, f"Invalid default offset for channel {i}" - assert channel.get_amplitude() == 5.0, f"Invalid default amplitude for channel {i}" + assert channel[ChannelOffsetAmplitudeFeature].get_offset() == 0, f"Invalid default offset for channel {i}" + assert channel[ChannelOffsetAmplitudeFeature].get_amplitude() == 5.0, f"Invalid default amplitude for channel {i}" offs = -0.1 * i ampl = 0.5 + 3 * i - channel.set_offset(offs) - channel.set_amplitude(ampl) - assert channel.get_offset() == offs, f"Invalid offset for channel {i}" - assert channel.get_amplitude() == ampl, f"Invalid amplitude for channel {i}" + channel[ChannelOffsetAmplitudeFeature].set_offset(offs) + channel[ChannelOffsetAmplitudeFeature].set_amplitude(ampl) + assert channel[ChannelOffsetAmplitudeFeature].get_offset() == offs, f"Invalid offset for channel {i}" + assert channel[ChannelOffsetAmplitudeFeature].get_amplitude() == ampl, f"Invalid amplitude for channel {i}" # Check if each channel tuple is working fine for group_size in [2, 4, 8]: - device.synchronize_channels(group_size) + device[SynchronizeChannelsFeature].synchronize_channels(group_size) assert len(device.channel_tuples) == 8 // group_size, "Invalid number of channel tuples" @@ -225,7 +225,7 @@ def test(): # Check if an error is thrown, when trying to process an invalid parameter try: - device.synchronize_channels(3) + device[SynchronizeChannelsFeature].synchronize_channels(3) assert True, "Missing error for invalid group size" except ValueError: pass @@ -239,4 +239,4 @@ def test(): if __name__ == "__main__": - test() + test() \ No newline at end of file From 432462635488fa77e132b99bf36a9c62cd378ff7 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 28 Jan 2020 13:49:36 +0100 Subject: [PATCH 016/107] Unittests added --- tests/hardware/awg_base_tests.py | 118 ++++++++++++++++--------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index f71a4daf7..013943964 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -1,3 +1,9 @@ +import warnings + +warnings.simplefilter("ignore", UserWarning) + +import unittest + from typing import Callable, Collection, Iterable, List, Optional from qupulse.hardware.awgs.base import BaseAWG, BaseAWGChannel, BaseAWGChannelTuple, BaseAWGFeature, \ @@ -5,8 +11,8 @@ ######################################################################################################################## -# Features -######################################################################################################################## +# Example Features +######################################################################################################################## class SynchronizeChannelsFeature(BaseAWGFeature): def __init__(self, sync_func: Callable[[int], None]): @@ -73,7 +79,8 @@ def __init__(self, name: str): self._channel_tuples: List["TestAWGChannelTuple"] = [] # Call the feature function, with the feature's signature - self[SynchronizeChannelsFeature].synchronize_channels(2) # default channel synchronization with a group size of 2 + self[SynchronizeChannelsFeature].synchronize_channels( + 2) # default channel synchronization with a group size of 2 def cleanup(self) -> None: """This will be called automatically in __del__""" @@ -186,57 +193,54 @@ def _set_ampl(self, amplitude: float) -> None: self._amplitude = amplitude -######################################################################################################################## -# Test -######################################################################################################################## - -def test(): - device_name = "My device" - device = TestAWG(device_name) - - # Check if the default values were set correctly - assert device.name == device_name, "Invalid name for device" - assert len(device.channels) == 8, "Invalid number of channels" - assert len(device.channel_tuples) == 4, "Invalid default channel tuples for device" - - # Check if each channel is working correctly - for i, channel in enumerate(device.channels): - assert channel.idn == i, "Invalid channel id" - assert channel[ChannelOffsetAmplitudeFeature].get_offset() == 0, f"Invalid default offset for channel {i}" - assert channel[ChannelOffsetAmplitudeFeature].get_amplitude() == 5.0, f"Invalid default amplitude for channel {i}" - - offs = -0.1 * i - ampl = 0.5 + 3 * i - channel[ChannelOffsetAmplitudeFeature].set_offset(offs) - channel[ChannelOffsetAmplitudeFeature].set_amplitude(ampl) - assert channel[ChannelOffsetAmplitudeFeature].get_offset() == offs, f"Invalid offset for channel {i}" - assert channel[ChannelOffsetAmplitudeFeature].get_amplitude() == ampl, f"Invalid amplitude for channel {i}" - - # Check if each channel tuple is working fine - for group_size in [2, 4, 8]: - device[SynchronizeChannelsFeature].synchronize_channels(group_size) - - assert len(device.channel_tuples) == 8 // group_size, "Invalid number of channel tuples" - - # Check if channels and channel tuples are connected right - for i, channel in enumerate(device.channels): - assert channel.channel_tuple.idn == i // group_size, f"Invalid channel tuple {channel.channel_tuple.idn} for channel {i}" - assert channel in channel.channel_tuple.channels, f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}" - - # Check if an error is thrown, when trying to process an invalid parameter - try: - device[SynchronizeChannelsFeature].synchronize_channels(3) - assert True, "Missing error for invalid group size" - except ValueError: - pass - except: - assert True, "Wrong error for invalid group size" - - # Check if the channel tuples are still the same - assert len(device.channel_tuples) == 1, "Invalid number of channel tuples" - - print("Test successful :)") - - -if __name__ == "__main__": - test() \ No newline at end of file +class TestBaseClasses(unittest.TestCase): + def setUp(self): + self.device_name = "My device" + self.device = TestAWG(self.device_name) + + def test_Device(self): + self.assertEqual(self.device.name, self.device_name, "Invalid name for device") + self.assertEqual(len(self.device.channels), 8, "Invalid number of channels") + self.assertEqual(len(self.device.channel_tuples), 4, "Invalid default channel tuples for device") + + def test_channel(self): + for i, channel in enumerate(self.device.channels): + self.assertEqual(channel.idn, i), "Invalid channel id" + self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_offset(), 0, + f"Invalid default offset for channel {i}") + self.assertEqual(channel[ + ChannelOffsetAmplitudeFeature].get_amplitude(), 5.0, + f"Invalid default amplitude for channel {i}") + + offs = -0.1 * i + ampl = 0.5 + 3 * i + channel[ChannelOffsetAmplitudeFeature].set_offset(offs) + channel[ChannelOffsetAmplitudeFeature].set_amplitude(ampl) + self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_offset(), offs, + f"Invalid offset for channel {i}") + self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_amplitude(), ampl, + f"Invalid amplitude for channel {i}") + + def test_channel_tupels(self): + for group_size in [2, 4, 8]: + self.device[SynchronizeChannelsFeature].synchronize_channels(group_size) + + self.assertEqual(len(self.device.channel_tuples), 8 // group_size, "Invalid number of channel tuples") + + # Check if channels and channel tuples are connected right + for i, channel in enumerate(self.device.channels): + self.assertEqual(channel.channel_tuple.idn, i // group_size, + f"Invalid channel tuple {channel.channel_tuple.idn} for channel {i}") + self.assertTrue(channel in channel.channel_tuple.channels, + f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}") + + self.assertEqual(len(self.device.channel_tuples), 1, "Invalid number of channel tuples") + + def test_error_thrown(self): + with self.assertRaises(ValueError) as cm: + self.device[SynchronizeChannelsFeature].synchronize_channels(3) + self.assertEqual(ValueError, cm.exception.__class__, "Missing error for invalid group size") + + +if __name__ == '__main__': + unittest.main() From 5bb39972daa90a270083307fdfdb93f74ddff606 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 28 Jan 2020 13:56:16 +0100 Subject: [PATCH 017/107] Fixed Problem with CI --- tests/hardware/awg_base_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 013943964..675fdb398 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -76,7 +76,7 @@ def __init__(self, name: str): self.add_feature(SynchronizeChannelsFeature(self._sync_chans)) self._channels = [TestAWGChannel(i, self) for i in range(8)] # 8 channels - self._channel_tuples: List["TestAWGChannelTuple"] = [] + self._channel_tuples = [] # Call the feature function, with the feature's signature self[SynchronizeChannelsFeature].synchronize_channels( From 64a9d68561bc93bbeba3cc62968b4f2aab33dd71 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Wed, 5 Feb 2020 08:05:48 +0100 Subject: [PATCH 018/107] Start of the implementation of the tabor drivers --- qupulse/hardware/awgs/__init__.py | 2 +- qupulse/hardware/awgs/features/__init__.py | 0 .../awgs/features/amplitude_offset_feature.py | 30 + .../awgs/features/device_mirror_feature.py | 22 + qupulse/hardware/awgs/old_tabor.py | 1332 +++++++++++++++++ qupulse/hardware/awgs/tabor.py | 1009 ++++++++++--- .../tabor_backward_compatibility_tests.py | 2 +- 7 files changed, 2211 insertions(+), 186 deletions(-) create mode 100644 qupulse/hardware/awgs/features/__init__.py create mode 100644 qupulse/hardware/awgs/features/amplitude_offset_feature.py create mode 100644 qupulse/hardware/awgs/features/device_mirror_feature.py create mode 100644 qupulse/hardware/awgs/old_tabor.py diff --git a/qupulse/hardware/awgs/__init__.py b/qupulse/hardware/awgs/__init__.py index fbbbfefa5..d3566be14 100644 --- a/qupulse/hardware/awgs/__init__.py +++ b/qupulse/hardware/awgs/__init__.py @@ -4,7 +4,7 @@ __all__ = ["install_requirements"] try: - from qupulse.hardware.awgs.tabor import TaborAWGRepresentation, TaborChannelPair + from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair __all__.extend(["TaborAWGRepresentation", "TaborChannelPair"]) except ImportError: pass diff --git a/qupulse/hardware/awgs/features/__init__.py b/qupulse/hardware/awgs/features/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/qupulse/hardware/awgs/features/amplitude_offset_feature.py b/qupulse/hardware/awgs/features/amplitude_offset_feature.py new file mode 100644 index 000000000..b683ef583 --- /dev/null +++ b/qupulse/hardware/awgs/features/amplitude_offset_feature.py @@ -0,0 +1,30 @@ +from collections import Callable + +from qupulse.hardware.awgs.base import BaseAWGChannelFeature + + +class ChannelAmplitudeOffsetFeature(BaseAWGChannelFeature): + def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], + amp_get: Callable[[], float], amp_set: Callable[[float], None]): + """Storing all callables, to call them if needed below""" + super().__init__() + self._get_offset = offset_get + self._set_offset = offset_set + self._get_amp = amp_get + self._set_amp = amp_set + + def get_offset(self) -> float: + """Forwarding call to callable object, which was provided threw __init__""" + return self._get_offset() + + def set_offset(self, offset: float) -> None: + """Forwarding call to callable object, which was provided threw __init__""" + self._set_offset(offset) + + def get_amplitude(self) -> float: + """Forwarding call to callable object, which was provided threw __init__""" + return self._get_amp() + + def set_amplitude(self, amplitude: float) -> None: + """Forwarding call to callable object, which was provided threw __init__""" + self._set_amp(amplitude) diff --git a/qupulse/hardware/awgs/features/device_mirror_feature.py b/qupulse/hardware/awgs/features/device_mirror_feature.py new file mode 100644 index 000000000..7372f2fdd --- /dev/null +++ b/qupulse/hardware/awgs/features/device_mirror_feature.py @@ -0,0 +1,22 @@ +from collections import Callable + +import typing + +from qupulse.hardware.awgs.base import BaseAWGFeature + + +class DeviceMirrorFeature(BaseAWGFeature): + def __init__(self, main_instrument: Callable, mirrored_instruments: Callable, + all_devices: Callable): + self._main_instrument = main_instrument + self._mirrored_instruments = mirrored_instruments + self._all_devices = all_devices + + def main_instrument(self) -> object: + return self.main_instrument() + + def mirrored_instruments(self) -> typing.Any: + return self.mirrored_instruments() + + def all_devices(self) -> typing.Any: + return self.all_devices() diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py new file mode 100644 index 000000000..7c0d16587 --- /dev/null +++ b/qupulse/hardware/awgs/old_tabor.py @@ -0,0 +1,1332 @@ +import fractions +import sys +import functools +import weakref +import itertools +import operator +from typing import List, Tuple, Set, NamedTuple, Callable, Optional, Any, Sequence, cast, Generator, Union, Dict +from enum import Enum +from collections import OrderedDict + +# Provided by Tabor electronics for python 2.7 +# a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech +# Beware of the string encoding change! +import teawg +import numpy as np + +from qupulse.utils.types import ChannelID +from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform +from qupulse._program._loop import Loop, make_compatible +from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times +from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling + + +assert(sys.byteorder == 'little') + + +__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] + + +class TaborSegment: + """Represents one segment of two channels on the device. Convenience class.""" + + __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') + + def __init__(self, + ch_a: Optional[np.ndarray], + ch_b: Optional[np.ndarray], + marker_a: Optional[np.ndarray], + marker_b: Optional[np.ndarray]): + if ch_a is None and ch_b is None: + raise TaborException('Empty TaborSegments are not allowed') + if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): + raise TaborException('Channel entries to have to have the same length') + + self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) + self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) + + self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) + self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) + + if marker_a is not None and len(marker_a)*2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + if marker_b is not None and len(marker_b)*2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + + @classmethod + def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': + data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1, )) + data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() + return cls.from_binary_data(data_a, data_b) + + @classmethod + def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': + ch_b = data_b + + channel_mask = np.uint16(2**14 - 1) + ch_a = np.bitwise_and(data_a, channel_mask) + + marker_a_mask = np.uint16(2**14) + marker_b_mask = np.uint16(2**15) + marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1, )) + + marker_a = np.bitwise_and(marker_data, marker_a_mask) + marker_b = np.bitwise_and(marker_data, marker_b_mask) + + return cls(ch_a=ch_a, + ch_b=ch_b, + marker_a=marker_a, + marker_b=marker_b) + + def __hash__(self) -> int: + return hash(tuple(0 if data is None else bytes(data) + for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) + + def __eq__(self, other: 'TaborSegment'): + def compare_markers(marker_1, marker_2): + if marker_1 is None: + if marker_2 is None: + return True + else: + return not np.any(marker_2) + + elif marker_2 is None: + return not np.any(marker_1) + + else: + return np.array_equal(marker_1, marker_2) + + return (np.array_equal(self.ch_a, other.ch_a) and + np.array_equal(self.ch_b, other.ch_b) and + compare_markers(self.marker_a, other.marker_a) and + compare_markers(self.marker_b, other.marker_b)) + + @property + def data_a(self) -> np.ndarray: + """channel_data and marker data""" + if self.marker_a is None and self.marker_b is None: + return self.ch_a + + if self.ch_a is None: + raise NotImplementedError('What data should be used in a?') + + # copy channel information + data = np.array(self.ch_a) + + if self.marker_a is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) + + if self.marker_b is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) + + return data + + @property + def data_b(self) -> np.ndarray: + """channel_data and marker data""" + return self.ch_b + + @property + def num_points(self) -> int: + return len(self.ch_b) if self.ch_a is None else len(self.ch_a) + + def get_as_binary(self) -> np.ndarray: + assert not (self.ch_a is None or self.ch_b is None) + return make_combined_wave([self]) + + +class TaborSequencing(Enum): + SINGLE = 1 + ADVANCED = 2 + + +class TaborProgram: + def __init__(self, + program: Loop, + device_properties, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]]): + if len(channels) != device_properties['chan_per_part']: + raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) + if len(markers) != device_properties['chan_per_part']: + raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) + channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker + for marker in + markers if marker is not None) + self._program = program + + self.__waveform_mode = None + self._channels = tuple(channels) + self._markers = tuple(markers) + self.__used_channels = channel_set + self.__device_properties = device_properties + + self._waveforms = [] # type: List[MultiChannelWaveform] + self._sequencer_tables = [] + self._advanced_sequencer_table = [] + + if self.program.repetition_count > 1: + self.program.encapsulate() + + if self.program.depth() > 1: + self.setup_advanced_sequence_mode() + self.__waveform_mode = TaborSequencing.ADVANCED + else: + if self.program.depth() == 0: + self.program.encapsulate() + self.setup_single_sequence_mode() + self.__waveform_mode = TaborSequencing.SINGLE + + @property + def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._markers + + @property + def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._channels + + def sampled_segments(self, + sample_rate: fractions.Fraction, + voltage_amplitude: Tuple[float, float], + voltage_offset: Tuple[float, float], + voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], + Sequence[int]]: + sample_rate = fractions.Fraction(sample_rate, 10**9) + + time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) + + if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): + raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') + + def voltage_to_data(waveform, time, channel): + if self._channels[channel]: + return voltage_to_uint16( + voltage_transformation[channel]( + waveform.get_sampled(channel=self._channels[channel], + sample_times=time)), + voltage_amplitude[channel], + voltage_offset[channel], + resolution=14) + else: + return np.full_like(time, 8192, dtype=np.uint16) + + def get_marker_data(waveform: MultiChannelWaveform, time, marker): + if self._markers[marker]: + markerID = self._markers[marker] + return waveform.get_sampled(channel=markerID, sample_times=time) != 0 + else: + return np.full_like(time, False, dtype=bool) + + segments = np.empty_like(self._waveforms, dtype=TaborSegment) + for i, waveform in enumerate(self._waveforms): + t = time_array[:segment_lengths[i]] + marker_time = t[::2] + segment_a = voltage_to_data(waveform, t, 0) + segment_b = voltage_to_data(waveform, t, 1) + assert (len(segment_a) == len(t)) + assert (len(segment_b) == len(t)) + marker_a = get_marker_data(waveform, marker_time, 0) + marker_b = get_marker_data(waveform, marker_time, 1) + segments[i] = TaborSegment(ch_a=segment_a, + ch_b=segment_b, + marker_a=marker_a, + marker_b=marker_b) + return segments, segment_lengths + + def setup_single_sequence_mode(self) -> None: + assert self.program.depth() == 1 + + sequencer_table = [] + waveforms = OrderedDict() + + for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in self.program): + if waveform in waveforms: + waveform_index = waveforms[waveform] + else: + waveform_index = len(waveforms) + waveforms[waveform] = waveform_index + sequencer_table.append((repetition_count, waveform_index, 0)) + + self._waveforms = tuple(waveforms.keys()) + self._sequencer_tables = [sequencer_table] + self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] + + def setup_advanced_sequence_mode(self) -> None: + assert self.program.depth() > 1 + assert self.program.repetition_count == 1 + + self.program.flatten_and_balance(2) + + min_seq_len = self.__device_properties['min_seq_len'] + max_seq_len = self.__device_properties['max_seq_len'] + + def check_merge_with_next(program, n): + if (program[n].repetition_count == 1 and program[n+1].repetition_count == 1 and + len(program[n]) + len(program[n+1]) < max_seq_len): + program[n][len(program[n]):] = program[n + 1][:] + program[n + 1:n + 2] = [] + return True + return False + + def check_partial_unroll(program, n): + st = program[n] + if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: + if sum(entry.repetition_count for entry in st) < min_seq_len: + st.unroll_children() + while len(st) < min_seq_len: + st.split_one_child() + return True + return False + + i = 0 + while i < len(self.program): + self.program[i].assert_tree_integrity() + if len(self.program[i]) > max_seq_len: + raise TaborException('The algorithm is not smart enough to make sequence tables shorter') + elif len(self.program[i]) < min_seq_len: + assert self.program[i].repetition_count > 0 + if self.program[i].repetition_count == 1: + # check if merging with neighbour is possible + if i > 0 and check_merge_with_next(self.program, i-1): + pass + elif i+1 < len(self.program) and check_merge_with_next(self.program, i): + pass + + # check if (partial) unrolling is possible + elif check_partial_unroll(self.program, i): + i += 1 + + # check if sequence table can be extended by unrolling a neighbor + elif (i > 0 + and self.program[i - 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i-1]) < max_seq_len): + self.program[i][:0] = self.program[i-1].copy_tree_structure()[:] + self.program[i - 1].repetition_count -= 1 + + elif (i+1 < len(self.program) + and self.program[i+1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i+1]) < max_seq_len): + self.program[i][len(self.program[i]):] = self.program[i+1].copy_tree_structure()[:] + self.program[i+1].repetition_count -= 1 + + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + elif check_partial_unroll(self.program, i): + i += 1 + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + else: + i += 1 + + for sequence_table in self.program: + assert len(sequence_table) >= self.__device_properties['min_seq_len'] + assert len(sequence_table) <= self.__device_properties['max_seq_len'] + + advanced_sequencer_table = [] + sequencer_tables = [] + waveforms = OrderedDict() + for sequencer_table_loop in self.program: + current_sequencer_table = [] + for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in sequencer_table_loop): + if waveform in waveforms: + wf_index = waveforms[waveform] + else: + wf_index = len(waveforms) + waveforms[waveform] = wf_index + current_sequencer_table.append((repetition_count, wf_index, 0)) + + if current_sequencer_table in sequencer_tables: + sequence_no = sequencer_tables.index(current_sequencer_table) + 1 + else: + sequence_no = len(sequencer_tables) + 1 + sequencer_tables.append(current_sequencer_table) + + advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) + + self._advanced_sequencer_table = advanced_sequencer_table + self._sequencer_tables = sequencer_tables + self._waveforms = tuple(waveforms.keys()) + + @property + def program(self) -> Loop: + return self._program + + def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: + return self._sequencer_tables + + def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: + """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" + return self._advanced_sequencer_table + + @property + def waveform_mode(self) -> str: + return self.__waveform_mode + + +class TaborAWGRepresentation: + def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): + """ + :param instr_addr: Instrument address that is forwarded to teawag + :param paranoia_level: Paranoia level that is forwarded to teawg + :param external_trigger: Not supported yet + :param reset: + :param mirror_addresses: + """ + self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) + self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) + + self._clock_marker = [0, 0, 0, 0] + + if external_trigger: + raise NotImplementedError() # pragma: no cover + + if reset: + self.send_cmd(':RES') + + self.initialize() + + self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') + self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + + @property + def channel_pair_AB(self) -> 'TaborChannelPair': + return self._channel_pair_AB + + @property + def channel_pair_CD(self) -> 'TaborChannelPair': + return self._channel_pair_CD + + @property + def main_instrument(self) -> teawg.TEWXAwg: + return self._instr + + @property + def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: + return self._mirrors + + @property + def paranoia_level(self) -> int: + return self._instr.paranoia_level + + @paranoia_level.setter + def paranoia_level(self, val): + for instr in self.all_devices: + instr.paranoia_level = val + + @property + def dev_properties(self) -> dict: + return self._instr.dev_properties + + @property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr, ) + self._mirrors + + def send_cmd(self, cmd_str, paranoia_level=None): + for instr in self.all_devices: + instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) + + def send_query(self, query_str, query_mirrors=False) -> Any: + if query_mirrors: + return tuple(instr.send_query(query_str) for instr in self.all_devices) + else: + return self._instr.send_query(query_str) + + def send_binary_data(self, pref, bin_dat, paranoia_level=None): + for instr in self.all_devices: + instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) + + def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): + for instr in self.all_devices: + instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) + + def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): + for instr in self.all_devices: + instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) + + def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): + for instr in self.all_devices: + instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) + + make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) + + def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: + """Overwrite send_cmd for paranoia_level > 3""" + if paranoia_level is None: + paranoia_level = self.paranoia_level + + if paranoia_level < 3: + super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover + else: + cmd_str = cmd_str.rstrip() + + if len(cmd_str) > 0: + ask_str = cmd_str + '; *OPC?; :SYST:ERR?' + else: + ask_str = '*OPC?; :SYST:ERR?' + + *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(';') + + error_code, error_msg = error_code_msg.split(',') + error_code = int(error_code) + if error_code != 0: + _ = self._visa_inst.ask('*CLS; *OPC?') + + if error_code == -450: + # query queue overflow + self.send_cmd(cmd_str) + else: + raise RuntimeError('Cannot execute command: {}\n{}: {}'.format(cmd_str, error_code, error_msg)) + + assert len(answers) == 0 + + def get_status_table(self) -> Dict[str, Union[str, float, int]]: + """Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame + + Returns: + An ordered dictionary with the results + """ + name_query_type_list = [('channel', ':INST:SEL?', int), + ('coupling', ':OUTP:COUP?', str), + ('volt_dc', ':SOUR:VOLT:LEV:AMPL:DC?', float), + ('volt_hv', ':VOLT:HV?', float), + ('offset', ':VOLT:OFFS?', float), + ('outp', ':OUTP?', str), + ('mode', ':SOUR:FUNC:MODE?', str), + ('shape', ':SOUR:FUNC:SHAPE?', str), + ('dc_offset', ':SOUR:DC?', float), + ('freq_rast', ':FREQ:RAST?', float), + + ('gated', ':INIT:GATE?', str), + ('continuous', ':INIT:CONT?', str), + ('continuous_enable', ':INIT:CONT:ENAB?', str), + ('continuous_source', ':INIT:CONT:ENAB:SOUR?', str), + ('marker_source', ':SOUR:MARK:SOUR?', str), + ('seq_jump_event', ':SOUR:SEQ:JUMP:EVEN?', str), + ('seq_adv_mode', ':SOUR:SEQ:ADV?', str), + ('aseq_adv_mode', ':SOUR:ASEQ:ADV?', str), + + ('marker', ':SOUR:MARK:SEL?', int), + ('marker_high', ':MARK:VOLT:HIGH?', str), + ('marker_low', ':MARK:VOLT:LOW?', str), + ('marker_width', ':MARK:WIDT?', int), + ('marker_state', ':MARK:STAT?', str)] + + data = OrderedDict((name, []) for name, *_ in name_query_type_list) + for ch in (1, 2, 3, 4): + self.select_channel(ch) + self.select_marker((ch-1) % 2 + 1) + for name, query, dtype in name_query_type_list: + data[name].append(dtype(self.send_query(query))) + return data + + @property + def is_open(self) -> bool: + return self._instr.visa_inst is not None # pragma: no cover + + def select_channel(self, channel) -> None: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + + self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) + + def select_marker(self, marker: int) -> None: + """Select marker 1 or 2 of the currently active channel pair.""" + if marker not in (1, 2): + raise TaborException('Invalid marker: {}'.format(marker)) + self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker)) + + def sample_rate(self, channel) -> int: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) + + def amplitude(self, channel) -> float: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) + if coupling == 'DC': + return float(self.send_query(':VOLT?')) + elif coupling == 'HV': + return float(self.send_query(':VOLT:HV?')) + else: + raise TaborException('Unknown coupling: {}'.format(coupling)) + + def offset(self, channel: int) -> float: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) + + def enable(self) -> None: + self.send_cmd(':ENAB') + + def abort(self) -> None: + self.send_cmd(':ABOR') + + def initialize(self) -> None: + # 1. Select channel + # 2. Turn off gated mode + # 3. continous mode + # 4. Armed mode (onlz generate waveforms after enab command) + # 5. Expect enable signal from (USB / LAN / GPIB) + # 6. Use arbitrary waveforms as marker source + # 7. Expect jump command for sequencing from (USB / LAN / GPIB) + setup_command = ( + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + self.send_cmd(':INST:SEL 1') + self.send_cmd(setup_command) + self.send_cmd(':INST:SEL 3') + self.send_cmd(setup_command) + + + def reset(self) -> None: + self.send_cmd(':RES') + self.initialize() + self.channel_pair_AB.clear() + self.channel_pair_CD.clear() + + def trigger(self) -> None: + self.send_cmd(':TRIG') + + def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: + for device in self.all_devices: + if device.fw_ver >= 3.0: + if simulator: + if device.is_simulator: + return device + else: + return device + raise TaborException('No device capable of device data read') + + +TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), + ('program', TaborProgram)]) + + +def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], + Any]: + """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" + @functools.wraps(function_object) + def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + if channel_pair._configuration_guard_count == 0: + channel_pair._enter_config_mode() + channel_pair._configuration_guard_count += 1 + + try: + return function_object(channel_pair, *args, **kwargs) + finally: + channel_pair._configuration_guard_count -= 1 + if channel_pair._configuration_guard_count == 0: + channel_pair._exit_config_mode() + + return guarding_method + + +def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: + """Asserts the channel pair is selcted when the wrapped function is called""" + @functools.wraps(function_object) + def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + channel_pair.select() + return function_object(channel_pair, *args, **kwargs) + + return selector + + +class PlottableProgram: + TableEntry = NamedTuple('TableEntry', [('repetition_count', int), + ('element_number', int), + ('jump_flag', int)]) + + def __init__(self, + segments: List[TaborSegment], + sequence_tables: List[List[Tuple[int, int, int]]], + advanced_sequence_table: List[Tuple[int, int, int]]): + self._segments = segments + self._sequence_tables = [[self.TableEntry(*sequence_table_entry) + for sequence_table_entry in sequence_table] + for sequence_table in sequence_tables] + self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) + for adv_seq_entry in advanced_sequence_table] + + @classmethod + def from_read_data(cls, waveforms: List[np.ndarray], + sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], + advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': + return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], + [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], + cls._reformat_rep_seg_jump(advanced_sequence_table)) + + @classmethod + def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: + return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) + for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) + + def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: + if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): + adv_seq_tab = self._advanced_sequence_table[1:] + else: + adv_seq_tab = self._advanced_sequence_table + + # remove idle pulse at end + if with_last_idles: + return adv_seq_tab + else: + while adv_seq_tab[-1] == (1, 1, 0): + adv_seq_tab = adv_seq_tab[:-1] + return adv_seq_tab + + def _iter_segment_table_entry(self, + with_first_idle=False, + with_last_idles=False) -> Generator[TableEntry, None, None]: + for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): + for _ in range(sequence_repeat): + yield from self._sequence_tables[sequence_no - 1] + + def iter_waveforms_and_repetitions(self, + channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): + yield ch_getter(self._segments[segment_no - 1]), segment_repeat + + def iter_samples(self, channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[np.uint16, None, None]: + for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): + waveform = list(waveform) + for _ in range(repetition): + yield from waveform + + def get_as_single_waveform(self, channel: int, + max_total_length: int=10**9, + with_marker: bool=False) -> Optional[np.ndarray]: + waveforms = self.get_waveforms(channel, with_marker=with_marker) + repetitions = self.get_repetitions() + waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) + + total_length = (repetitions*waveform_lengths).sum() + if total_length > max_total_length: + return None + + result = np.empty(total_length, dtype=np.uint16) + c_idx = 0 + for wf, rep in zip(waveforms, repetitions): + mem = wf.size*rep + target = result[c_idx:c_idx+mem] + + target = target.reshape((rep, wf.size)) + target[:, :] = wf[np.newaxis, :] + c_idx += mem + return result + + def get_waveforms(self, channel: int, with_marker: bool=False) -> List[np.ndarray]: + if with_marker: + ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] + else: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1]) + for _, segment_no, _ in self._iter_segment_table_entry()] + + def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1])] + + def get_repetitions(self) -> np.ndarray: + return np.fromiter((segment_repeat + for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) + + def __eq__(self, other): + for ch in (0, 1): + for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), + other.iter_samples(ch, True, False)): + if x != y: + return False + return True + + def to_builtin(self) -> dict: + waveforms = [[wf.data_a.tolist() for wf in self._segments], + [wf.data_b.tolist() for wf in self._segments]] + return {'waveforms': waveforms, + 'seq_tables': self._sequence_tables, + 'adv_seq_table': self._advanced_sequence_table} + + @classmethod + def from_builtin(cls, data: dict) -> 'PlottableProgram': + waveforms = data['waveforms'] + waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) + for data_a, data_b in zip(*waveforms)] + return cls(waveforms, data['seq_tables'], data['adv_seq_table']) + + +class TaborChannelPair(AWG): + def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): + super().__init__(identifier) + self._device = weakref.ref(tabor_device) + + self._configuration_guard_count = 0 + self._is_in_config_mode = False + + if channels not in ((1, 2), (3, 4)): + raise ValueError('Invalid channel pair: {}'.format(channels)) + self._channels = channels + + self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), None, None) + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + + self._known_programs = dict() # type: Dict[str, TaborProgramMemory] + self._current_program = None + + self._segment_lengths = None + self._segment_capacity = None + self._segment_hashes = None + self._segment_references = None + + self._sequencer_tables = None + self._advanced_sequence_table = None + + self.clear() + + def select(self) -> None: + self.device.send_cmd(':INST:SEL {}'.format(self._channels[0])) + + @property + def total_capacity(self) -> int: + return int(self.device.dev_properties['max_arb_mem']) // 2 + + @property + def device(self) -> TaborAWGRepresentation: + return self._device() + + def free_program(self, name: str) -> TaborProgramMemory: + if name is None: + raise TaborException('Removing "None" program is forbidden.') + program = self._known_programs.pop(name) + self._segment_references[program.waveform_to_segment] -= 1 + if self._current_program == name: + self.change_armed_program(None) + return program + + def _restore_program(self, name: str, program: TaborProgram) -> None: + if name in self._known_programs: + raise ValueError('Program cannot be restored as it is already known.') + self._segment_references[program.waveform_to_segment] += 1 + self._known_programs[name] = program + + @property + def _segment_reserved(self) -> np.ndarray: + return self._segment_references > 0 + + @property + def _free_points_in_total(self) -> int: + return self.total_capacity - np.sum(self._segment_capacity[self._segment_reserved]) + + @property + def _free_points_at_end(self) -> int: + reserved_index = np.flatnonzero(self._segment_reserved) + if len(reserved_index): + return self.total_capacity - np.sum(self._segment_capacity[:reserved_index[-1]]) + else: + return self.total_capacity + + @with_select + def read_waveforms(self) -> List[np.ndarray]: + device = self.device.get_readable_device(simulator=True) + + old_segment = device.send_query(':TRAC:SEL?') + waveforms = [] + uploaded_waveform_indices = np.flatnonzero(self._segment_references) + 1 + for segment in uploaded_waveform_indices: + device.send_cmd(':TRAC:SEL {}'.format(segment)) + waveforms.append(device.read_act_seg_dat()) + device.send_cmd(':TRAC:SEL {}'.format(old_segment)) + return waveforms + + @with_select + def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: + device = self.device.get_readable_device(simulator=True) + + old_sequence = device.send_query(':SEQ:SEL?') + sequences = [] + uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 + for sequence in uploaded_sequence_indices: + device.send_cmd(':SEQ:SEL {}'.format(sequence)) + sequences.append(device.read_sequencer_table()) + device.send_cmd(':SEQ:SEL {}'.format(old_sequence)) + return sequences + + @with_select + def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + return self.device.get_readable_device(simulator=True).read_adv_seq_table() + + def read_complete_program(self) -> PlottableProgram: + return PlottableProgram.from_read_data(self.read_waveforms(), + self.read_sequence_tables(), + self.read_advanced_sequencer_table()) + + @with_configuration_guard + @with_select + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]], + voltage_transformation: Tuple[Callable, Callable], + force: bool=False) -> None: + """Upload a program to the AWG. + + The policy is to prefer amending the unknown waveforms to overwriting old ones.""" + + if len(channels) != self.num_channels: + raise ValueError('Channel ID not specified') + if len(markers) != self.num_markers: + raise ValueError('Markers not specified') + if len(voltage_transformation) != self.num_channels: + raise ValueError('Wrong number of voltage transformations') + + # adjust program to fit criteria + sample_rate = self.device.sample_rate(self._channels[0]) + make_compatible(program, + minimal_waveform_length=192, + waveform_quantum=16, + sample_rate=fractions.Fraction(sample_rate, 10**9)) + + # helper to restore previous state if upload is impossible + to_restore = None + to_restore_was_armed = False + if name in self._known_programs: + if force: + if self._current_program == name: + to_restore_was_armed = True + + # save old program to restore in on error + to_restore = name, self.free_program(name) + else: + raise ValueError('{} is already known on {}'.format(name, self.identifier)) + + try: + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=markers, + device_properties=self.device.dev_properties) + + # They call the peak to peak range amplitude + ranges = (self.device.amplitude(self._channels[0]), + self.device.amplitude(self._channels[1])) + + voltage_amplitudes = (ranges[0]/2, ranges[1]/2) + + if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets = (0, 0) + elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets = (self.device.offset(self._channels[0]), + self.device.offset(self._channels[1])) + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) + + segments, segment_lengths = tabor_program.sampled_segments(sample_rate=sample_rate, + voltage_amplitude=voltage_amplitudes, + voltage_offset=voltage_offsets, + voltage_transformation=voltage_transformation) + + waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, + segment_lengths) + except: + if to_restore: + self._restore_program(*to_restore) + if to_restore_was_armed: + self.change_armed_program(name) + raise + + self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 + + for wf_index in np.flatnonzero(to_insert > 0): + segment_index = to_insert[wf_index] + self._upload_segment(to_insert[wf_index], segments[wf_index]) + waveform_to_segment[wf_index] = segment_index + + if np.any(to_amend): + segments_to_amend = segments[to_amend] + waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) + + self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, + program=tabor_program) + + @with_configuration_guard + @with_select + def clear(self) -> None: + """Delete all segments and clear memory""" + self.device.select_channel(self._channels[0]) + self.device.send_cmd(':TRAC:DEL:ALL') + self.device.send_cmd(':SOUR:SEQ:DEL:ALL') + self.device.send_cmd(':ASEQ:DEL') + + self.device.send_cmd(':TRAC:DEF 1, 192') + self.device.send_cmd(':TRAC:SEL 1') + self.device.send_cmd(':TRAC:MODE COMB') + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) + + self._segment_lengths = 192*np.ones(1, dtype=np.uint32) + self._segment_capacity = 192*np.ones(1, dtype=np.uint32) + self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) + self._segment_references = np.ones(1, dtype=np.uint32) + + self._advanced_sequence_table = [] + self._sequencer_tables = [] + + self._known_programs = dict() + self.change_armed_program(None) + + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + 1. Find known segments + 2. Find empty spaces with fitting length + 3. Find empty spaces with bigger length + 4. Amend remaining segments + :param segments: + :param segment_lengths: + :return: + """ + segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) + + waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) + + # separate into known and unknown + unknown = (waveform_to_segment == -1) + known = ~unknown + + known_pos_in_memory = waveform_to_segment[known] + + assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + + new_reference_counter = self._segment_references.copy() + new_reference_counter[known_pos_in_memory] += 1 + + to_upload_size = np.sum(segment_lengths[unknown] + 16) + free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) + if free_points_in_total < to_upload_size: + raise MemoryError('Not enough free memory', + free_points_in_total, + to_upload_size, + self._free_points_in_total) + + to_amend = cast(np.ndarray, unknown) + to_insert = np.full(len(segments), fill_value=-1, dtype=np.int64) + + reserved_indices = np.flatnonzero(new_reference_counter > 0) + first_free = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + + free_segments = new_reference_counter[:first_free] == 0 + free_segment_count = np.sum(free_segments) + + # look for a free segment place with the same length + for segment_idx in np.flatnonzero(to_amend): + if free_segment_count == 0: + break + + pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + idx_same_length = np.argmax(pos_of_same_length) + if pos_of_same_length[idx_same_length]: + free_segments[idx_same_length] = False + free_segment_count -= 1 + + to_amend[segment_idx] = False + to_insert[segment_idx] = idx_same_length + + # try to find places that are larger than the segments to fit in starting with the large segments and large + # free spaces + segment_indices = np.flatnonzero(to_amend)[np.argsort(segment_lengths[to_amend])[::-1]] + capacities = self._segment_capacity[:first_free] + for segment_idx in segment_indices: + free_capacities = capacities[free_segments] + free_segments_indices = np.flatnonzero(free_segments)[np.argsort(free_capacities)[::-1]] + + if len(free_segments_indices) == 0: + break + + fitting_segment = np.argmax((free_capacities >= segment_lengths[segment_idx])[::-1]) + fitting_segment = free_segments_indices[fitting_segment] + if self._segment_capacity[fitting_segment] >= segment_lengths[segment_idx]: + free_segments[fitting_segment] = False + to_amend[segment_idx] = False + to_insert[segment_idx] = fitting_segment + + free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) + if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: + raise MemoryError('Fragmentation does not allow upload.', + np.sum(segment_lengths[to_amend] + 16), + free_points_at_end, + self._free_points_at_end) + + return waveform_to_segment, to_amend, to_insert + + @with_select + @with_configuration_guard + def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: + if self._segment_references[segment_index] > 0: + raise ValueError('Reference count not zero') + if segment.num_points > self._segment_capacity[segment_index]: + raise ValueError('Cannot upload segment here.') + + segment_no = segment_index + 1 + + self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points)) + self._segment_lengths[segment_index] = segment.num_points + + self.device.send_cmd(':TRAC:SEL {}'.format(segment_no)) + + self.device.send_cmd(':TRAC:MODE COMB') + wf_data = segment.get_as_binary() + + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + self._segment_references[segment_index] = 1 + self._segment_hashes[segment_index] = hash(segment) + + @with_select + @with_configuration_guard + def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: + new_lengths = np.asarray([s.num_points for s in segments], dtype=np.uint32) + + wf_data = make_combined_wave(segments) + trac_len = len(wf_data) // 2 + + segment_index = len(self._segment_capacity) + first_segment_number = segment_index + 1 + self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len)) + self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number)) + self.device.send_cmd(':TRAC:MODE COMB') + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + + old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) + segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) + segment_lengths = np.concatenate((self._segment_lengths, new_lengths)) + segment_references = np.concatenate((self._segment_references, np.ones(len(segments), dtype=int))) + segment_hashes = np.concatenate((self._segment_hashes, [hash(s) for s in segments])) + if len(segments) < old_to_update: + for i, segment in enumerate(segments): + current_segment_number = first_segment_number + i + self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points)) + else: + # flush the capacity + self.device.download_segment_lengths(segment_capacity) + + # update non fitting lengths + for i in np.flatnonzero(segment_capacity != segment_lengths): + self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i])) + + self._segment_capacity = segment_capacity + self._segment_lengths = segment_lengths + self._segment_hashes = segment_hashes + self._segment_references = segment_references + + return segment_index + np.arange(len(segments), dtype=np.int64) + + @with_select + @with_configuration_guard + def cleanup(self) -> None: + """Discard all segments after the last which is still referenced""" + reserved_indices = np.flatnonzero(self._segment_references > 0) + old_end = len(self._segment_lengths) + new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 + self._segment_lengths = self._segment_lengths[:new_end] + self._segment_capacity = self._segment_capacity[:new_end] + self._segment_hashes = self._segment_hashes[:new_end] + self._segment_references = self._segment_references[:new_end] + + try: + # send max 10 commands at once + chunk_size = 10 + for chunk_start in range(new_end, old_end, chunk_size): + self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) + for i in range(chunk_start, min(chunk_start+chunk_size, old_end)))) + except Exception as e: + raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e + + def remove(self, name: str) -> None: + """Remove a program from the AWG. + + Also discards all waveforms referenced only by the program identified by name. + + Args: + name (str): The name of the program to remove. + """ + self.free_program(name) + self.cleanup() + + def set_marker_state(self, marker: int, active: bool) -> None: + """Sets the marker state of this channel pair. According to the manual one cannot turn them off/on separately.""" + command_string = ':INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}' + command_string = command_string.format( + channel=self._channels[0], + marker=(1, 2)[marker], + active='ON' if active else 'OFF') + self.device.send_cmd(command_string) + + def set_channel_state(self, channel, active) -> None: + command_string = ':INST:SEL {}; :OUTP {}'.format(self._channels[channel], 'ON' if active else 'OFF') + self.device.send_cmd(command_string) + + @with_select + def arm(self, name: str) -> None: + if self._current_program == name: + self.device.send_cmd('SEQ:SEL 1') + else: + self.change_armed_program(name) + + def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): + self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table + + def set_program_sequence_table(self, name, new_sequence_table): + self._known_programs[name][1]._sequencer_tables = new_sequence_table + + @with_select + @with_configuration_guard + def change_armed_program(self, name: Optional[str]) -> None: + if name is None: + sequencer_tables = [self._idle_sequence_table] + advanced_sequencer_table = [(1, 1, 0)] + else: + waveform_to_segment_index, program = self._known_programs[name] + waveform_to_segment_number = waveform_to_segment_index + 1 + + # translate waveform number to actual segment + sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) + for (rep_count, wf_index, jump_flag) in sequencer_table] + for sequencer_table in program.get_sequencer_tables()] + + # insert idle sequence + sequencer_tables = [self._idle_sequence_table] + sequencer_tables + + # adjust advanced sequence table entries by idle sequence table offset + advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) + for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] + + if program.waveform_mode == TaborSequencing.SINGLE: + assert len(advanced_sequencer_table) == 1 + assert len(sequencer_tables) == 2 + + while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: + assert advanced_sequencer_table[0][0] == 1 + sequencer_tables[1].append((1, 1, 0)) + + # insert idle sequence in advanced sequence table + advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table + + while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: + advanced_sequencer_table.append((1, 1, 0)) + + # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs + self.device.send_cmd('SEQ:DEL:ALL') + self._sequencer_tables = [] + self.device.send_cmd('ASEQ:DEL') + self._advanced_sequence_table = [] + + # download all sequence tables + for i, sequencer_table in enumerate(sequencer_tables): + self.device.send_cmd('SEQ:SEL {}'.format(i+1)) + self.device.download_sequencer_table(sequencer_table) + self._sequencer_tables = sequencer_tables + self.device.send_cmd('SEQ:SEL 1') + + self.device.download_adv_seq_table(advanced_sequencer_table) + self._advanced_sequence_table = advanced_sequencer_table + + self._current_program = name + + @with_select + def run_current_program(self) -> None: + if self._current_program: + self.device.send_cmd(':TRIG') + else: + raise RuntimeError('No program active') + + @property + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program.name for program in self._known_programs.keys()) + + @property + def sample_rate(self) -> float: + return self.device.sample_rate(self._channels[0]) + + @property + def num_channels(self) -> int: + return 2 + + @property + def num_markers(self) -> int: + return 2 + + def _enter_config_mode(self) -> None: + """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the sequencing is disabled + as the manual states this speeds up sequence validation when uploading multiple sequences.""" + if self._is_in_config_mode is False: + + # 1. Selct channel pair + # 2. Select DC as function shape + # 3. Select build-in waveform mode + + if self.device.send_query(':INST:COUP:STAT?') == 'ON': + self.device.send_cmd(':OUTP:ALL OFF') + else: + self.device.send_cmd(':INST:SEL {}; :OUTP OFF; :INST:SEL {}; :OUTP OFF'.format(*self._channels)) + + self.set_marker_state(0, False) + self.set_marker_state(1, False) + self.device.send_cmd(':SOUR:FUNC:MODE FIX') + + self._is_in_config_mode = True + + @with_select + def _exit_config_mode(self) -> None: + """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" + + if self.device.send_query(':INST:COUP:STAT?') == 'ON': + # Coupled -> switch all channels at once + if self._channels == (1, 2): + other_channel_pair = self.device.channel_pair_CD + else: + other_channel_pair = self.device.channel_pair_AB + + if not other_channel_pair._is_in_config_mode: + self.device.send_cmd(':SOUR:FUNC:MODE ASEQ') + self.device.send_cmd(':SEQ:SEL 1') + self.device.send_cmd(':OUTP:ALL ON') + + else: + self.device.send_cmd(':SOUR:FUNC:MODE ASEQ') + self.device.send_cmd(':SEQ:SEL 1') + + self.device.send_cmd(':INST:SEL {}; :OUTP ON; :INST:SEL {}; :OUTP ON'.format(*self._channels)) + + self.set_marker_state(0, True) + self.set_marker_state(1, True) + self._is_in_config_mode = False + + +class TaborException(Exception): + pass + +class TaborUndefinedState(TaborException): + """If this exception is raised the attached tabor device is in an undefined state. + It is highly recommended to call reset it.""" + + def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): + super().__init__(*args) + self.device = device + + def reset_device(self): + if isinstance(self.device, TaborAWGRepresentation): + self.device.reset() + elif isinstance(self.device, TaborChannelPair): + self.device.clear() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index d42088d62..9b5dd972a 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,9 +1,12 @@ import fractions import functools import weakref -import logging -import numbers -from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple + +import itertools +import operator +from typing import List, Tuple, Set, NamedTuple, Callable, Optional, Any, Sequence, cast, Generator, Union, Dict, \ + Iterable +from enum import Enum from collections import OrderedDict # Provided by Tabor electronics for python 2.7 @@ -12,108 +15,662 @@ import teawg import numpy as np +from qupulse.hardware.awgs.features.amplitude_offset_feature import ChannelAmplitudeOffsetFeature +from qupulse.hardware.awgs.features.device_mirror_feature import DeviceMirrorFeature from qupulse.utils.types import ChannelID from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.util import voltage_to_uint16, find_positions -from qupulse.hardware.awgs.base import AWG, AWGAmplitudeOffsetHandling -from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ - make_combined_wave -assert(sys.byteorder == 'little') +from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times +from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs.base_features import FeatureAble + +from qupulse.hardware.awgs.base import BaseAWG, BaseAWGChannel, BaseAWGChannelTuple + + +# TODO: ??? +assert (sys.byteorder == 'little') + + +# TODO: ??? +# __all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] + +######################################################################################################################## + +class TaborSegment: + """Represents one segment of two channels on the device. Convenience class.""" + + __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') + + def __init__(self, + ch_a: Optional[np.ndarray], + ch_b: Optional[np.ndarray], + marker_a: Optional[np.ndarray], + marker_b: Optional[np.ndarray]): + if ch_a is None and ch_b is None: + raise TaborException('Empty TaborSegments are not allowed') + if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): + raise TaborException('Channel entries to have to have the same length') + + self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) + self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) + + self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) + self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) + + if marker_a is not None and len(marker_a) * 2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + if marker_b is not None and len(marker_b) * 2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + + @classmethod + def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': + data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) + data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() + return cls.from_binary_data(data_a, data_b) + + @classmethod + def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': + ch_b = data_b + + channel_mask = np.uint16(2 ** 14 - 1) + ch_a = np.bitwise_and(data_a, channel_mask) + + marker_a_mask = np.uint16(2 ** 14) + marker_b_mask = np.uint16(2 ** 15) + marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) + + marker_a = np.bitwise_and(marker_data, marker_a_mask) + marker_b = np.bitwise_and(marker_data, marker_b_mask) + + return cls(ch_a=ch_a, + ch_b=ch_b, + marker_a=marker_a, + marker_b=marker_b) + + # TODO: Marker ueberarbeiten + def __hash__(self) -> int: + return hash(tuple(0 if data is None else bytes(data) + for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) + + def __eq__(self, other: 'TaborSegment'): + def compare_markers(marker_1, marker_2): + if marker_1 is None: + if marker_2 is None: + return True + else: + return not np.any(marker_2) + + elif marker_2 is None: + return not np.any(marker_1) + + else: + return np.array_equal(marker_1, marker_2) + + return (np.array_equal(self.ch_a, other.ch_a) and + np.array_equal(self.ch_b, other.ch_b) and + compare_markers(self.marker_a, other.marker_a) and + compare_markers(self.marker_b, other.marker_b)) + + @property + def data_a(self) -> np.ndarray: + """channel_data and marker data""" + if self.marker_a is None and self.marker_b is None: + return self.ch_a + + if self.ch_a is None: + raise NotImplementedError('What data should be used in a?') + + # copy channel information + data = np.array(self.ch_a) + + if self.marker_a is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) + + if self.marker_b is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) + return data + + @property + def data_b(self) -> np.ndarray: + """channel_data and marker data""" + return self.ch_b + + @property + def num_points(self) -> int: + return len(self.ch_b) if self.ch_a is None else len(self.ch_a) + + def get_as_binary(self) -> np.ndarray: + assert not (self.ch_a is None or self.ch_b is None) + return make_combined_wave([self]) + + +class TaborSequencing(Enum): + SINGLE = 1 + ADVANCED = 2 + + +class TaborProgram: + def __init__(self, + program: Loop, + device_properties, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]]): + if len(channels) != device_properties['chan_per_part']: + raise TaborException( + 'TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) + if len(markers) != device_properties['chan_per_part']: + raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) + channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker + for marker in + markers if + marker is not None) + self._program = program + + self.__waveform_mode = None + self._channels = tuple(channels) + self._markers = tuple(markers) + self.__used_channels = channel_set + self.__device_properties = device_properties + + self._waveforms = [] # type: List[MultiChannelWaveform] + self._sequencer_tables = [] + self._advanced_sequencer_table = [] -__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] + if self.program.repetition_count > 1: + self.program.encapsulate() + if self.program.depth() > 1: + self.setup_advanced_sequence_mode() + self.__waveform_mode = TaborSequencing.ADVANCED + else: + if self.program.depth() == 0: + self.program.encapsulate() + self.setup_single_sequence_mode() + self.__waveform_mode = TaborSequencing.SINGLE + + @property + def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._markers + + @property + def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._channels + + def sampled_segments(self, + sample_rate: fractions.Fraction, + voltage_amplitude: Tuple[float, float], + voltage_offset: Tuple[float, float], + voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], + Sequence[int]]: + sample_rate = fractions.Fraction(sample_rate, 10 ** 9) + + time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) + + if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): + raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') + + def voltage_to_data(waveform, time, channel): + if self._channels[channel]: + return voltage_to_uint16( + voltage_transformation[channel]( + waveform.get_sampled(channel=self._channels[channel], + sample_times=time)), + voltage_amplitude[channel], + voltage_offset[channel], + resolution=14) + else: + return np.full_like(time, 8192, dtype=np.uint16) + + def get_marker_data(waveform: MultiChannelWaveform, time, marker): + if self._markers[marker]: + markerID = self._markers[marker] + return waveform.get_sampled(channel=markerID, sample_times=time) != 0 + else: + return np.full_like(time, False, dtype=bool) + + segments = np.empty_like(self._waveforms, dtype=TaborSegment) + for i, waveform in enumerate(self._waveforms): + t = time_array[:segment_lengths[i]] + marker_time = t[::2] + segment_a = voltage_to_data(waveform, t, 0) + segment_b = voltage_to_data(waveform, t, 1) + assert (len(segment_a) == len(t)) + assert (len(segment_b) == len(t)) + marker_a = get_marker_data(waveform, marker_time, 0) + marker_b = get_marker_data(waveform, marker_time, 1) + segments[i] = TaborSegment(ch_a=segment_a, + ch_b=segment_b, + marker_a=marker_a, + marker_b=marker_b) + return segments, segment_lengths + + def setup_single_sequence_mode(self) -> None: + assert self.program.depth() == 1 + + sequencer_table = [] + waveforms = OrderedDict() + + for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in self.program): + if waveform in waveforms: + waveform_index = waveforms[waveform] + else: + waveform_index = len(waveforms) + waveforms[waveform] = waveform_index + sequencer_table.append((repetition_count, waveform_index, 0)) + + self._waveforms = tuple(waveforms.keys()) + self._sequencer_tables = [sequencer_table] + self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] + + def setup_advanced_sequence_mode(self) -> None: + assert self.program.depth() > 1 + assert self.program.repetition_count == 1 + + self.program.flatten_and_balance(2) + + min_seq_len = self.__device_properties['min_seq_len'] + max_seq_len = self.__device_properties['max_seq_len'] + + def check_merge_with_next(program, n): + if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and + len(program[n]) + len(program[n + 1]) < max_seq_len): + program[n][len(program[n]):] = program[n + 1][:] + program[n + 1:n + 2] = [] + return True + return False + + def check_partial_unroll(program, n): + st = program[n] + if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: + if sum(entry.repetition_count for entry in st) < min_seq_len: + st.unroll_children() + while len(st) < min_seq_len: + st.split_one_child() + return True + return False + + i = 0 + while i < len(self.program): + self.program[i].assert_tree_integrity() + if len(self.program[i]) > max_seq_len: + raise TaborException('The algorithm is not smart enough to make sequence tables shorter') + elif len(self.program[i]) < min_seq_len: + assert self.program[i].repetition_count > 0 + if self.program[i].repetition_count == 1: + # check if merging with neighbour is possible + if i > 0 and check_merge_with_next(self.program, i - 1): + pass + elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): + pass + + # check if (partial) unrolling is possible + elif check_partial_unroll(self.program, i): + i += 1 + + # check if sequence table can be extended by unrolling a neighbor + elif (i > 0 + and self.program[i - 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): + self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] + self.program[i - 1].repetition_count -= 1 + + elif (i + 1 < len(self.program) + and self.program[i + 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): + self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] + self.program[i + 1].repetition_count -= 1 + + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + elif check_partial_unroll(self.program, i): + i += 1 + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + else: + i += 1 + + for sequence_table in self.program: + assert len(sequence_table) >= self.__device_properties['min_seq_len'] + assert len(sequence_table) <= self.__device_properties['max_seq_len'] + + advanced_sequencer_table = [] + sequencer_tables = [] + waveforms = OrderedDict() + for sequencer_table_loop in self.program: + current_sequencer_table = [] + for waveform, repetition_count in ( + (waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in sequencer_table_loop): + if waveform in waveforms: + wf_index = waveforms[waveform] + else: + wf_index = len(waveforms) + waveforms[waveform] = wf_index + current_sequencer_table.append((repetition_count, wf_index, 0)) -class TaborAWGRepresentation: - def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): + if current_sequencer_table in sequencer_tables: + sequence_no = sequencer_tables.index(current_sequencer_table) + 1 + else: + sequence_no = len(sequencer_tables) + 1 + sequencer_tables.append(current_sequencer_table) + + advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) + + self._advanced_sequencer_table = advanced_sequencer_table + self._sequencer_tables = sequencer_tables + self._waveforms = tuple(waveforms.keys()) + + @property + def program(self) -> Loop: + return self._program + + def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: + return self._sequencer_tables + + def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: + """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" + return self._advanced_sequencer_table + + @property + def waveform_mode(self) -> str: + return self.__waveform_mode + + +def with_configuration_guard(function_object: Callable[['AWGChannelTupleTabor', Any], Any]) -> Callable[ + ['AWGChannelTupleTabor'], Any]: + """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" + + @functools.wraps(function_object) + def guarding_method(channel_pair: 'AWGChannelTupleTabor', *args, **kwargs) -> Any: + if channel_pair._configuration_guard_count == 0: + channel_pair._enter_config_mode() + channel_pair._configuration_guard_count += 1 + + try: + return function_object(channel_pair, *args, **kwargs) + finally: + channel_pair._configuration_guard_count -= 1 + if channel_pair._configuration_guard_count == 0: + channel_pair._exit_config_mode() + + return guarding_method + + +def with_select(function_object: Callable[['AWGChannelTupleTabor', Any], Any]) -> Callable[['AWGChannelTupleTabor'], Any]: + """Asserts the channel pair is selcted when the wrapped function is called""" + + @functools.wraps(function_object) + def selector(channel_pair: 'AWGChannelTupleTabor', *args, **kwargs) -> Any: + channel_pair.select() + return function_object(channel_pair, *args, **kwargs) + + return selector + + +class PlottableProgram: + TableEntry = NamedTuple('TableEntry', [('repetition_count', int), + ('element_number', int), + ('jump_flag', int)]) + + def __init__(self, + segments: List[TaborSegment], + sequence_tables: List[List[Tuple[int, int, int]]], + advanced_sequence_table: List[Tuple[int, int, int]]): + self._segments = segments + self._sequence_tables = [[self.TableEntry(*sequence_table_entry) + for sequence_table_entry in sequence_table] + for sequence_table in sequence_tables] + self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) + for adv_seq_entry in advanced_sequence_table] + + @classmethod + def from_read_data(cls, waveforms: List[np.ndarray], + sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], + advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': + return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], + [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], + cls._reformat_rep_seg_jump(advanced_sequence_table)) + + @classmethod + def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: + return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) + for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) + + def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: + if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): + adv_seq_tab = self._advanced_sequence_table[1:] + else: + adv_seq_tab = self._advanced_sequence_table + + # remove idle pulse at end + if with_last_idles: + return adv_seq_tab + else: + while adv_seq_tab[-1] == (1, 1, 0): + adv_seq_tab = adv_seq_tab[:-1] + return adv_seq_tab + + def _iter_segment_table_entry(self, + with_first_idle=False, + with_last_idles=False) -> Generator[TableEntry, None, None]: + for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): + for _ in range(sequence_repeat): + yield from self._sequence_tables[sequence_no - 1] + + def iter_waveforms_and_repetitions(self, + channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): + yield ch_getter(self._segments[segment_no - 1]), segment_repeat + + def iter_samples(self, channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[np.uint16, None, None]: + for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): + waveform = list(waveform) + for _ in range(repetition): + yield from waveform + + def get_as_single_waveform(self, channel: int, + max_total_length: int = 10 ** 9, + with_marker: bool = False) -> Optional[np.ndarray]: + waveforms = self.get_waveforms(channel, with_marker=with_marker) + repetitions = self.get_repetitions() + waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) + + total_length = (repetitions * waveform_lengths).sum() + if total_length > max_total_length: + return None + + result = np.empty(total_length, dtype=np.uint16) + c_idx = 0 + for wf, rep in zip(waveforms, repetitions): + mem = wf.size * rep + target = result[c_idx:c_idx + mem] + + target = target.reshape((rep, wf.size)) + target[:, :] = wf[np.newaxis, :] + c_idx += mem + return result + + def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: + if with_marker: + ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] + else: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1]) + for _, segment_no, _ in self._iter_segment_table_entry()] + + def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1])] + + def get_repetitions(self) -> np.ndarray: + return np.fromiter((segment_repeat + for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) + + def __eq__(self, other): + for ch in (0, 1): + for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), + other.iter_samples(ch, True, False)): + if x != y: + return False + return True + + def to_builtin(self) -> dict: + waveforms = [[wf.data_a.tolist() for wf in self._segments], + [wf.data_b.tolist() for wf in self._segments]] + return {'waveforms': waveforms, + 'seq_tables': self._sequence_tables, + 'adv_seq_table': self._advanced_sequence_table} + + @classmethod + def from_builtin(cls, data: dict) -> 'PlottableProgram': + waveforms = data['waveforms'] + waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) + for data_a, data_b in zip(*waveforms)] + return cls(waveforms, data['seq_tables'], data['adv_seq_table']) + + +######################################################################################################################## + +class AWGDeviceTabor(BaseAWG): + def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, + mirror_addresses=()): """ - :param instr_addr: Instrument address that is forwarded to teawg - :param paranoia_level: Paranoia level that is forwarded to teawg - :param external_trigger: Not supported yet - :param reset: - :param mirror_addresses: + :param device_name: Name of the device + :param instr_addr: Instrument address that is forwarded to teawag + :param paranoia_level: Paranoia level that is forwarded to teawg + :param external_trigger: Not supported yet + :param reset: + :param mirror_addresses: """ + # New + ################# + super().__init__(device_name) + self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) + + # TODO: Mirrorfeature ueberarbeiten + self.add_feature(DeviceMirrorFeature(self.main_instrument, self.mirrored_instruments, self.all_devices)) + self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) self._coupled = None self._clock_marker = [0, 0, 0, 0] + # TODO: Wird der Parametername wirklich noch benoetigt? + # TODO: Startet i bei 1 oder 0? + # Wenn es doch bei 1 startet + 1 entfernen + self._channels = [AWGChannelTabor("test", i + 1, self) for i in range(4)] + self._channel_tuples = [] + + # TODO: Braucht man den alten identifier noch? + self._channel_tuples.append(AWGChannelTupleTabor(1, self, self._channels[0:1])) + self._channel_tuples.append(AWGChannelTupleTabor(2, self, self._channels[2:3])) + + # self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') + # self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + if external_trigger: raise NotImplementedError() # pragma: no cover if reset: self.send_cmd(':RES') + # TODO: ggf. ueberarbeiten self.initialize() - self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') - self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + # New + ########################################################################################### + def cleanup(self) -> None: + for tuple in self._channel_tuples: + # TODO: clear oder cleanup + tuple.cleanup() - def is_coupled(self) -> bool: - if self._coupled is None: - return self.send_query(':INST:COUP:STAT?') == 'ON' - else: - return self._coupled + # Bearbeitet @property - def channel_pair_AB(self) -> 'TaborChannelPair': - return self._channel_pair_AB + def channels(self) -> Iterable["BaseAWGChannel"]: + return self._channels + # Bearbeitet @property - def channel_pair_CD(self) -> 'TaborChannelPair': - return self._channel_pair_CD + def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + return self._channel_tuples - @property + # Old + ########################################################################################### + + # TODO: nicht mehr benötigt oder? + + # @property + # def channel_pair_AB(self) -> 'TaborChannelPair': + # return self._channel_pair_AB + + # @property + # def channel_pair_CD(self) -> 'TaborChannelPair': + # return self._channel_pair_CD + + # @property def main_instrument(self) -> teawg.TEWXAwg: return self._instr - @property + #@property def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: return self._mirrors + #@property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr,) + self._mirrors + @property def paranoia_level(self) -> int: return self._instr.paranoia_level + # TODO: Paranoia als Feature? + + # TODO: MirrorFeature @paranoia_level.setter def paranoia_level(self, val): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.paranoia_level = val @property def dev_properties(self) -> dict: return self._instr.dev_properties - @property - def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr, ) + self._mirrors - + # TODO: MirrorFeature def send_cmd(self, cmd_str, paranoia_level=None): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self.all_devices) + return tuple(instr.send_query(query_str) for instr in self[DeviceMirrorFeature].all_devices) else: return self._instr.send_query(query_str) def send_binary_data(self, pref, bin_dat, paranoia_level=None): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): - for instr in self.all_devices: + for instr in self[DeviceMirrorFeature].all_devices: instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) @@ -183,20 +740,24 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): self.select_channel(ch) - self.select_marker((ch-1) % 2 + 1) + self.select_marker((ch - 1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data + # TODO: Schleifen ersetzen + # for channel in self._channels: + # channel.select_channel() + @property def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover - def select_channel(self, channel) -> None: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - - self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) + # def select_channel(self, channel) -> None: + # if channel not in (1, 2, 3, 4): + # raise TaborException('Invalid channel: {}'.format(channel)) + # + # self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) def select_marker(self, marker: int) -> None: """Select marker 1 or 2 of the currently active channel pair.""" @@ -204,26 +765,26 @@ def select_marker(self, marker: int) -> None: raise TaborException('Invalid marker: {}'.format(marker)) self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker)) - def sample_rate(self, channel) -> int: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) - - def amplitude(self, channel) -> float: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) - if coupling == 'DC': - return float(self.send_query(':VOLT?')) - elif coupling == 'HV': - return float(self.send_query(':VOLT:HV?')) - else: - raise TaborException('Unknown coupling: {}'.format(coupling)) - - def offset(self, channel: int) -> float: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) + # def sample_rate(self, channel) -> int: + # if channel not in (1, 2, 3, 4): + # raise TaborException('Invalid channel: {}'.format(channel)) + # return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) + + # def get_amplitude(self, channel) -> float: + # if channel not in (1, 2, 3, 4): + # raise TaborException('Invalid channel: {}'.format(channel)) + # coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) + # if coupling == 'DC': + # return float(self.send_query(':VOLT?')) + # elif coupling == 'HV': + # return float(self.send_query(':VOLT:HV?')) + # else: + # raise TaborException('Unknown coupling: {}'.format(coupling)) + + # def get_offset(self, channel: int) -> float: + # if channel not in (1, 2, 3, 4): + # raise TaborException('Invalid channel: {}'.format(channel)) + # return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) def enable(self) -> None: self.send_cmd(':ENAB') @@ -240,20 +801,26 @@ def initialize(self) -> None: # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) setup_command = ( - ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " - ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") self.send_cmd(':INST:SEL 1') self.send_cmd(setup_command) self.send_cmd(':INST:SEL 3') self.send_cmd(setup_command) + + # TODO: Oder direkt in ein cleanup des Geraets? + # TODO: Wieso funktionier clear() fuer ChannelTuple nicht def reset(self) -> None: self.send_cmd(':RES') self._coupled = None self.initialize() - self.channel_pair_AB.clear() - self.channel_pair_CD.clear() + for channel_tuple in self.channel_tuples: + channel_tuple.clear() + + # self.channel_pair_AB.clear() + # self.channel_pair_CD.clear() def trigger(self) -> None: self.send_cmd(':TRIG') @@ -273,56 +840,38 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: ('program', TaborProgram)]) -def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], - Any]: - """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" - @functools.wraps(function_object) - def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: - if channel_pair._configuration_guard_count == 0: - channel_pair._enter_config_mode() - channel_pair._configuration_guard_count += 1 - - try: - return function_object(channel_pair, *args, **kwargs) - finally: - channel_pair._configuration_guard_count -= 1 - if channel_pair._configuration_guard_count == 0: - channel_pair._exit_config_mode() - - return guarding_method - - -def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: - """Asserts the channel pair is selected when the wrapped function is called""" - @functools.wraps(function_object) - def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: - channel_pair.select() - return function_object(channel_pair, *args, **kwargs) - return selector - - -class TaborChannelPair(AWG): - CONFIG_MODE_PARANOIA_LEVEL = None +class AWGChannelTupleTabor(BaseAWGChannelTuple): + def __init__(self, idn: int, tabor_device: AWGDeviceTabor, channels: Iterable["AWGChannelTabor"]): + # New + ########### + super().__init__(idn) + self._device = tabor_device + self._channels = tuple(channels) + # TODO: Standart Sample Rate? - def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): - super().__init__(identifier) + # Old + ########### + # TODO: Braucht man die alte Vererbung? + # super().__init__(identifier) self._device = weakref.ref(tabor_device) self._configuration_guard_count = 0 self._is_in_config_mode = False - if channels not in ((1, 2), (3, 4)): - raise ValueError('Invalid channel pair: {}'.format(channels)) - self._channels = channels - - self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - None, None) + # TODO: Fehler der jetzt beim synchronisiern geworfen wird + # if channels not in ((1, 2), (3, 4)): + # raise ValueError('Invalid channel pair: {}'.format(channels)) + + # self._channels = channels + + + self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), None, None) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -340,16 +889,25 @@ def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, in self.clear() + # New + #################################################################################################################### + # TODO: Samplerate ist Channel oder Channeltuple aufgabe? + # @property + # def sample_rate(self) -> float: + # pass + + # Bearbeitet @property - def internal_paranoia_level(self) -> Optional[int]: - return self._internal_paranoia_level + def device(self) -> BaseAWG: + return self._device() - @internal_paranoia_level.setter - def internal_paranoia_level(self, paranoia_level: Optional[int]): - """ Sets the paranoia level with which commands from within methods are called """ - assert paranoia_level in (None, 0, 1, 2) - self._internal_paranoia_level = paranoia_level + # Bearbeitet + @property + def channels(self) -> Iterable["BaseAWGChannel"]: + return self._channels + # Old + #################################################################################################################### def select(self) -> None: self.device.send_cmd(':INST:SEL {}'.format(self._channels[0]), paranoia_level=self.internal_paranoia_level) @@ -358,14 +916,6 @@ def select(self) -> None: def total_capacity(self) -> int: return int(self.device.dev_properties['max_arb_mem']) // 2 - @property - def logger(self): - return logging.getLogger("qupulse.tabor") - - @property - def device(self) -> TaborAWGRepresentation: - return self._device() - def free_program(self, name: str) -> TaborProgramMemory: if name is None: raise TaborException('Removing "None" program is forbidden.') @@ -452,46 +1002,56 @@ def upload(self, name: str, raise ValueError('Wrong number of voltage transformations') # adjust program to fit criteria - sample_rate = self.device.sample_rate(self._channels[0]) + sample_rate = self.device._channels[0].sample_rate() make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10**9)) + sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) if name in self._known_programs: if force: self.free_program(name) else: raise ValueError('{} is already known on {}'.format(name, self.identifier)) + try: + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=markers, + device_properties=self.device.dev_properties) - # They call the peak to peak range amplitude - ranges = (self.device.amplitude(self._channels[0]), - self.device.amplitude(self._channels[1])) - - voltage_amplitudes = (ranges[0]/2, ranges[1]/2) + #TODO: Iterable does not implements __get_item__ + # They call the peak to peak range amplitude - if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: - voltage_offsets = (0, 0) - elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets = (self.device.offset(self._channels[0]), - self.device.offset(self._channels[1])) - else: - raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) + ranges = (self.device.channels[0][ChannelAmplitudeOffsetFeature].getAmplitude(), + self.device.channels[1][ChannelAmplitudeOffsetFeature].getAmplitude()) - # parse to tabor program - tabor_program = TaborProgram(program, - channels=tuple(channels), - markers=markers, - device_properties=self.device.dev_properties, - sample_rate=sample_rate / 10**9, - amplitudes=voltage_amplitudes, - offsets=voltage_offsets, - voltage_transformations=voltage_transformation) + # ranges = (self.device.amplitude(self._channels[0]), + # self.device.amplitude(self._channels[1])) - segments, segment_lengths = tabor_program.get_sampled_segments() + voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) - waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, - segment_lengths) + if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets = (0, 0) + elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets = (self.device._channels[0][ChannelAmplitudeOffsetFeature].get_offset(), + self.device._channels[1][ChannelAmplitudeOffsetFeature].get_offset()) + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) + + segments, segment_lengths = tabor_program.sampled_segments(sample_rate=sample_rate, + voltage_amplitude=voltage_amplitudes, + voltage_offset=voltage_offsets, + voltage_transformation=voltage_transformation) + + waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, + segment_lengths) + except: + if to_restore: + self._restore_program(*to_restore) + if to_restore_was_armed: + self.change_armed_program(sname) + raise self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 @@ -511,18 +1071,19 @@ def upload(self, name: str, @with_select def clear(self) -> None: """Delete all segments and clear memory""" - self.device.select_channel(self._channels[0]) - self.device.send_cmd(':TRAC:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':SOUR:SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':ASEQ:DEL', paranoia_level=self.internal_paranoia_level) + self.device._channels[0].select_channel() + self.device.send_cmd(':TRAC:DEL:ALL') + self.device.send_cmd(':SOUR:SEQ:DEL:ALL') + self.device.send_cmd(':ASEQ:DEL') + self.device.send_cmd(':TRAC:DEF 1, 192', paranoia_level=self.internal_paranoia_level) self.device.send_cmd(':TRAC:SEL 1', paranoia_level=self.internal_paranoia_level) self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) - self._segment_lengths = 192*np.ones(1, dtype=np.uint32) - self._segment_capacity = 192*np.ones(1, dtype=np.uint32) + self._segment_lengths = 192 * np.ones(1, dtype=np.uint32) + self._segment_capacity = 192 * np.ones(1, dtype=np.uint32) self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) self._segment_references = np.ones(1, dtype=np.uint32) @@ -532,7 +1093,8 @@ def clear(self) -> None: self._known_programs = dict() self.change_armed_program(None) - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ + np.ndarray, np.ndarray, np.ndarray]: """ 1. Find known segments 2. Find empty spaces with fitting length @@ -552,7 +1114,8 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths known_pos_in_memory = waveform_to_segment[known] - assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + assert len(known_pos_in_memory) == 0 or np.all( + self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) new_reference_counter = self._segment_references.copy() new_reference_counter[known_pos_in_memory] += 1 @@ -579,7 +1142,8 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths if free_segment_count == 0: break - pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + pos_of_same_length = np.logical_and(free_segments, + segment_lengths[segment_idx] == self._segment_capacity[:first_free]) idx_same_length = np.argmax(pos_of_same_length) if pos_of_same_length[idx_same_length]: free_segments[idx_same_length] = False @@ -672,8 +1236,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i]), - paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:DEF {},{}'.format(i + 1, segment_lengths[i])) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -688,7 +1251,7 @@ def cleanup(self) -> None: """Discard all segments after the last which is still referenced""" reserved_indices = np.flatnonzero(self._segment_references > 0) old_end = len(self._segment_lengths) - new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 + new_end = reserved_indices[-1] + 1 if len(reserved_indices) else 0 self._segment_lengths = self._segment_lengths[:new_end] self._segment_capacity = self._segment_capacity[:new_end] self._segment_hashes = self._segment_hashes[:new_end] @@ -698,9 +1261,8 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) - for i in range(chunk_start, min(chunk_start+chunk_size, old_end))), - paranoia_level=self.internal_paranoia_level) + self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i + 1) + for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) except Exception as e: raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e @@ -810,7 +1372,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # translate waveform number to actual segment sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for ((rep_count, wf_index, jump_flag), _) in sequencer_table] + for (rep_count, wf_index, jump_flag) in sequencer_table] for sequencer_table in program.get_sequencer_tables()] # insert idle sequence @@ -842,7 +1404,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i+1), paranoia_level=self.internal_paranoia_level) + self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) self.device.download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) @@ -864,17 +1426,16 @@ def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(program for program in self._known_programs.keys()) - @property - def sample_rate(self) -> float: - return self.device.sample_rate(self._channels[0]) - + # Bearbeitet @property def num_channels(self) -> int: - return 2 + """Returns the number of channels of a certain channel tuple""" + return len(self._channels) + # TODO: ? - Jeder Channel hat genau einen Marker oder? @property def num_markers(self) -> int: - return 2 + return len(self._channels) def _enter_config_mode(self) -> None: """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the @@ -889,10 +1450,12 @@ def _enter_config_mode(self) -> None: if self.device.is_coupled(): out_cmd = ':OUTP:ALL OFF' else: - out_cmd = ':INST:SEL {};:OUTP OFF;:INST:SEL {};:OUTP OFF'.format(*self._channels) + self.device.send_cmd(':INST:SEL {}; :OUTP OFF; :INST:SEL {}; :OUTP OFF'.format(*self._channels)) + + self.set_marker_state(0, False) + self.set_marker_state(1, False) + self.device.send_cmd(':SOUR:FUNC:MODE FIX') - marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' wf_mode_cmd = ':SOUR:FUNC:MODE FIX' @@ -900,6 +1463,9 @@ def _enter_config_mode(self) -> None: self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = True + + # TODO: In Channel Touple aendern + @with_select def _exit_config_mode(self) -> None: """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" @@ -930,16 +1496,91 @@ def _exit_config_mode(self) -> None: self._is_in_config_mode = False + +class AWGChannelTabor(BaseAWGChannel): + # Union aus String und Int in der alten Darstellung + + # TODO: doppelter identifier wegen identifier des alten ChannelPairs + Marker + def __init__(self, name: str, idn: int, tabor_device: AWGDeviceTabor, marker=False): + super().__init__(idn) + + self.add_feature( + ChannelAmplitudeOffsetFeature(self._get_offset, self._set_offset, self._get_amplitude, self._set_amplitude)) + + self.name = name + self.marker = marker + + self._device = tabor_device + self._channel_tuple: Optional[AWGChannelTupleTabor] = None + + # TODO: Amplitude und Offset Feature + + def select_channel(self) -> None: + self.send_cmd(':INST:SEL {channel}'.format(channel=self.idn)) + + def get_sample_rate(self) -> int: + return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=self.idn)))) + + def _get_offset(self) -> float: + return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=self.idn))) + + # TODO: Noch implementieren + def _set_offset(self, offset: float) -> None: + pass + + def _get_amplitude(self) -> float: + coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=self.idn)) + if coupling == 'DC': + return float(self.send_query(':VOLT?')) + elif coupling == 'HV': + return float(self.send_query(':VOLT:HV?')) + else: + raise TaborException('Unknown coupling: {}'.format(coupling)) + + # TODO: Noch implementieren + def _set_amplitude(self, amplitude: float) -> None: + pass + + @property + def device(self) -> BaseAWG: + return self._device + + @property + def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: + return self._channel_tuple + + def _set_channel_tuple(self, channel_tuple) -> None: + self._channel_tuple = channel_tuple + + +######################################################################################################################## + +class TaborException(Exception): + pass + + +# TODO: Anpassen class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" - def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): + # TODO: so okay? - evtl noch anpassen - isinstance auche fuer channel? + def __init__(self, *args, device: FeatureAble): super().__init__(*args) self.device = device def reset_device(self): - if isinstance(self.device, TaborAWGRepresentation): + if isinstance(self.device, AWGDeviceTabor): self.device.reset() - elif isinstance(self.device, TaborChannelPair): + elif isinstance(self.device, AWGChannelTupleTabor): self.device.clear() + + # def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): + # super().__init__(*args) + # self.device = device + # + # def reset_device(self): + # if isinstance(self.device, TaborAWGRepresentation): + # self.device.reset() + # elif isinstance(self.device, TaborChannelPair): + # self.device.clear() diff --git a/tests/backward_compatibility/tabor_backward_compatibility_tests.py b/tests/backward_compatibility/tabor_backward_compatibility_tests.py index 97d567232..0e78099b5 100644 --- a/tests/backward_compatibility/tabor_backward_compatibility_tests.py +++ b/tests/backward_compatibility/tabor_backward_compatibility_tests.py @@ -11,7 +11,7 @@ from qupulse.serialization import Serializer, FilesystemBackend, PulseStorage from qupulse.pulses.pulse_template import PulseTemplate from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel, MeasurementMask -from qupulse.hardware.awgs.tabor import PlottableProgram +from qupulse.hardware.awgs.old_tabor import PlottableProgram From c9de729ae52b943653d99980b5949a34dbe97402 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Thu, 6 Feb 2020 12:13:14 +0100 Subject: [PATCH 019/107] Start of the implementation of the Tabor driver --- qupulse/hardware/awgs/base.py | 26 +++++++------- qupulse/hardware/awgs/base_features.py | 6 ++-- .../awgs/features/amplitude_offset_feature.py | 4 +-- .../awgs/features/device_mirror_feature.py | 4 +-- qupulse/hardware/awgs/tabor.py | 36 +++++++++---------- tests/hardware/awg_base_tests.py | 26 +++++++------- 6 files changed, 49 insertions(+), 53 deletions(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index af35402c4..13d4ad876 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,25 +1,25 @@ from abc import ABC, abstractmethod from typing import Iterable, Optional -from .base_features import BaseFeature, FeatureAble +from .base_features import Feature, FeatureAble -class BaseAWGFeature(BaseFeature, ABC): +class AWGFeature(Feature, ABC): """Base class for features that are used for `AWGDevice`s""" pass -class BaseAWGChannelFeature(BaseFeature, ABC): +class AWGChannelFeature(Feature, ABC): """Base class for features that are used for `AWGChannel`s""" pass -class BaseAWGChannelTupleFeature(BaseFeature, ABC): +class AWGChannelTupleFeature(Feature, ABC): """Base class for features that are used for `AWGChannelTuple`s""" pass -class BaseAWG(FeatureAble[BaseAWGFeature], ABC): +class AWGDevice(FeatureAble[AWGFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -45,18 +45,18 @@ def cleanup(self) -> None: @property @abstractmethod - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: """Returns a list of all channels of a Device""" raise NotImplementedError() @property @abstractmethod - def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + def channel_tuples(self) -> Iterable["AWGChannelTuple"]: """Returns a list of all channel tuples of a list""" raise NotImplementedError() -class BaseAWGChannelTuple(FeatureAble[BaseAWGChannelTupleFeature], ABC): +class AWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): """Base class for all groups of synchronized channels of an AWG""" def __init__(self, idn: int): @@ -81,18 +81,18 @@ def sample_rate(self) -> float: @property @abstractmethod - def device(self) -> BaseAWG: + def device(self) -> AWGDevice: """Returns the device which the channel tuple belong to""" raise NotImplementedError() @property @abstractmethod - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: """Returns a list of all channels of the channel tuple""" raise NotImplementedError() -class BaseAWGChannel(FeatureAble[BaseAWGChannelFeature], ABC): +class AWGChannel(FeatureAble[AWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -110,13 +110,13 @@ def idn(self) -> int: @property @abstractmethod - def device(self) -> BaseAWG: + def device(self) -> AWGDevice: """Returns the device which the channel belongs to""" raise NotImplementedError() @property @abstractmethod - def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: + def channel_tuple(self) -> Optional[AWGChannelTuple]: """Returns the channel tuple which a channel belongs to""" raise NotImplementedError() diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index cc06c7991..c79a45cb8 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -5,14 +5,14 @@ from copy import copy -class BaseFeature(ABC): +class Feature(ABC): """ Base class for features of `FeatureAble`s. """ pass -FeatureType = TypeVar("FeatureType", bound=BaseFeature) +FeatureType = TypeVar("FeatureType", bound=Feature) class FeatureAble(Generic[FeatureType], ABC): @@ -36,7 +36,7 @@ def add_feature(self, feature: FeatureType) -> None: Args: feature: A certain feature which functions should be added to the dictionary _features """ - if not isinstance(feature, BaseFeature): + if not isinstance(feature, Feature): raise TypeError("Invalid type for feature") self._features[type(feature)] = feature diff --git a/qupulse/hardware/awgs/features/amplitude_offset_feature.py b/qupulse/hardware/awgs/features/amplitude_offset_feature.py index b683ef583..647be5426 100644 --- a/qupulse/hardware/awgs/features/amplitude_offset_feature.py +++ b/qupulse/hardware/awgs/features/amplitude_offset_feature.py @@ -1,9 +1,9 @@ from collections import Callable -from qupulse.hardware.awgs.base import BaseAWGChannelFeature +from qupulse.hardware.awgs.base import AWGChannelFeature -class ChannelAmplitudeOffsetFeature(BaseAWGChannelFeature): +class ChannelAmplitudeOffsetFeature(AWGChannelFeature): def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], amp_get: Callable[[], float], amp_set: Callable[[float], None]): """Storing all callables, to call them if needed below""" diff --git a/qupulse/hardware/awgs/features/device_mirror_feature.py b/qupulse/hardware/awgs/features/device_mirror_feature.py index 7372f2fdd..fac7315f5 100644 --- a/qupulse/hardware/awgs/features/device_mirror_feature.py +++ b/qupulse/hardware/awgs/features/device_mirror_feature.py @@ -2,10 +2,10 @@ import typing -from qupulse.hardware.awgs.base import BaseAWGFeature +from qupulse.hardware.awgs.base import AWGFeature -class DeviceMirrorFeature(BaseAWGFeature): +class DeviceMirrorFeature(AWGFeature): def __init__(self, main_instrument: Callable, mirrored_instruments: Callable, all_devices: Callable): self._main_instrument = main_instrument diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 9b5dd972a..684022ec1 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -24,7 +24,7 @@ from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling from qupulse.hardware.awgs.base_features import FeatureAble -from qupulse.hardware.awgs.base import BaseAWG, BaseAWGChannel, BaseAWGChannelTuple +from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple # TODO: ??? @@ -540,7 +540,7 @@ def from_builtin(cls, data: dict) -> 'PlottableProgram': ######################################################################################################################## -class AWGDeviceTabor(BaseAWG): +class AWGDeviceDeviceTabor(AWGDevice): def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ @@ -567,8 +567,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # TODO: Wird der Parametername wirklich noch benoetigt? # TODO: Startet i bei 1 oder 0? - # Wenn es doch bei 1 startet + 1 entfernen - self._channels = [AWGChannelTabor("test", i + 1, self) for i in range(4)] + self._channels = [AWGChannelTabor("test", i, self) for i in range(4)] self._channel_tuples = [] # TODO: Braucht man den alten identifier noch? @@ -590,19 +589,16 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # New ########################################################################################### def cleanup(self) -> None: - for tuple in self._channel_tuples: + for channel_tuple in self._channel_tuples: # TODO: clear oder cleanup - tuple.cleanup() + channel_tuple.cleanup() - - # Bearbeitet @property - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: return self._channels - # Bearbeitet @property - def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + def channel_tuples(self) -> Iterable["AWGChannelTuple"]: return self._channel_tuples # Old @@ -841,8 +837,8 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: -class AWGChannelTupleTabor(BaseAWGChannelTuple): - def __init__(self, idn: int, tabor_device: AWGDeviceTabor, channels: Iterable["AWGChannelTabor"]): +class AWGChannelTupleTabor(AWGChannelTuple): + def __init__(self, idn: int, tabor_device: AWGDeviceDeviceTabor, channels: Iterable["AWGChannelTabor"]): # New ########### super().__init__(idn) @@ -898,12 +894,12 @@ def __init__(self, idn: int, tabor_device: AWGDeviceTabor, channels: Iterable["A # Bearbeitet @property - def device(self) -> BaseAWG: + def device(self) -> AWGDevice: return self._device() # Bearbeitet @property - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: return self._channels # Old @@ -1497,11 +1493,11 @@ def _exit_config_mode(self) -> None: -class AWGChannelTabor(BaseAWGChannel): +class AWGChannelTabor(AWGChannel): # Union aus String und Int in der alten Darstellung # TODO: doppelter identifier wegen identifier des alten ChannelPairs + Marker - def __init__(self, name: str, idn: int, tabor_device: AWGDeviceTabor, marker=False): + def __init__(self, name: str, idn: int, tabor_device: AWGDeviceDeviceTabor, marker=False): super().__init__(idn) self.add_feature( @@ -1542,11 +1538,11 @@ def _set_amplitude(self, amplitude: float) -> None: pass @property - def device(self) -> BaseAWG: + def device(self) -> AWGDevice: return self._device @property - def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: + def channel_tuple(self) -> Optional[AWGChannelTuple]: return self._channel_tuple def _set_channel_tuple(self, channel_tuple) -> None: @@ -1570,7 +1566,7 @@ def __init__(self, *args, device: FeatureAble): self.device = device def reset_device(self): - if isinstance(self.device, AWGDeviceTabor): + if isinstance(self.device, AWGDeviceDeviceTabor): self.device.reset() elif isinstance(self.device, AWGChannelTupleTabor): self.device.clear() diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 675fdb398..d045d8839 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -6,15 +6,15 @@ from typing import Callable, Collection, Iterable, List, Optional -from qupulse.hardware.awgs.base import BaseAWG, BaseAWGChannel, BaseAWGChannelTuple, BaseAWGFeature, \ - BaseAWGChannelFeature, BaseAWGChannelTupleFeature +from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGFeature, \ + AWGChannelFeature, AWGChannelTupleFeature ######################################################################################################################## # Example Features ######################################################################################################################## -class SynchronizeChannelsFeature(BaseAWGFeature): +class SynchronizeChannelsFeature(AWGFeature): def __init__(self, sync_func: Callable[[int], None]): """Storing the callable, to call it if needed below""" super().__init__() @@ -25,7 +25,7 @@ def synchronize_channels(self, group_size: int) -> None: self._sync_func(group_size) -class ChannelTupleNameFeature(BaseAWGChannelTupleFeature): +class ChannelTupleNameFeature(AWGChannelTupleFeature): def __init__(self, name_get: Callable[[], str]): """Storing the callable, to call it if needed below""" super().__init__() @@ -36,7 +36,7 @@ def get_name(self) -> str: return self._get_name() -class ChannelOffsetAmplitudeFeature(BaseAWGChannelFeature): +class ChannelOffsetAmplitudeFeature(AWGChannelFeature): def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], amp_get: Callable[[], float], amp_set: Callable[[float], None]): """Storing all callables, to call them if needed below""" @@ -67,7 +67,7 @@ def set_amplitude(self, amplitude: float) -> None: # Device & Channels ######################################################################################################################## -class TestAWG(BaseAWG): +class TestAWGDevice(AWGDevice): def __init__(self, name: str): super().__init__(name) @@ -115,8 +115,8 @@ def _sync_chans(self, group_size: int) -> None: channel._set_channel_tuple(channel_tuple) -class TestAWGChannelTuple(BaseAWGChannelTuple): - def __init__(self, idn: int, device: TestAWG, channels: Iterable["TestAWGChannel"]): +class TestAWGChannelTuple(AWGChannelTuple): + def __init__(self, idn: int, device: TestAWGDevice, channels: Iterable["TestAWGChannel"]): super().__init__(idn) # Add feature to this object (self) @@ -136,7 +136,7 @@ def sample_rate(self, sample_rate: float) -> None: self._sample_rate = sample_rate @property - def device(self) -> TestAWG: + def device(self) -> TestAWGDevice: return self._device @property @@ -149,8 +149,8 @@ def _get_name(self) -> str: return chr(ord('A') + self.idn) # 0 -> 'A', 1 -> 'B', 2 -> 'C', ... -class TestAWGChannel(BaseAWGChannel): - def __init__(self, idn: int, device: TestAWG): +class TestAWGChannel(AWGChannel): + def __init__(self, idn: int, device: TestAWGDevice): super().__init__(idn) # Add feature to this object (self) @@ -166,7 +166,7 @@ def __init__(self, idn: int, device: TestAWG): self._amplitude = 5.0 @property - def device(self) -> TestAWG: + def device(self) -> TestAWGDevice: return self._device @property @@ -196,7 +196,7 @@ def _set_ampl(self, amplitude: float) -> None: class TestBaseClasses(unittest.TestCase): def setUp(self): self.device_name = "My device" - self.device = TestAWG(self.device_name) + self.device = TestAWGDevice(self.device_name) def test_Device(self): self.assertEqual(self.device.name, self.device_name, "Invalid name for device") From a2587fa9ae4021b302941112cb0a9485c91a877e Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 4 Nov 2019 10:02:36 +0100 Subject: [PATCH 020/107] Start der Implementierung der AWG-Treiber --- qupulse/hardware/awgs/AWGDriver.py | 99 ++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 qupulse/hardware/awgs/AWGDriver.py diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py new file mode 100644 index 000000000..72bdc2bf6 --- /dev/null +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -0,0 +1,99 @@ +from abc import ABC, abstractmethod + + +class AWGDevice(ABC): + channels = [] + channel_groups = [] + + @abstractmethod + def initialize(self): + pass + + @abstractmethod + def synchronoize_channels(self, group: int): + pass + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Wie wird der Return vom String umgesetzt + pass + +class AWGChannelTuple(ABC): + def __init__(self, channel_tuple_id, device: AWGDevice, channels, sample_rate, programs): + self._channel_tuptle_id = channel_tuple_id + self._device = device + self._channels = channels + self._sample_rate = sample_rate + self._programs = programs + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Wie wird der Return vom String umgesetzt + pass + + +class AWGChannel(ABC): + def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): + self._channel_id = channel_id + self._device = device + self._channel_tupel = channel_tupel + + @abstractmethod + def _send_cmd(self, cmd: str): + pass + + @abstractmethod + def _send_querry(self, cmd: str) -> str: + # Was passiert wenn was anderes als ein String returnt wird? + pass + + # muss die Methode dann auch abstrakt sein? + @property + def channel_id(self): + return self._channel_id + + # braucht channelId einen Setter? + +class AWGProgrammManager(ABC): + @abstractmethod + def add(self, program: Programm): + pass + + @abstractmethod + def get(self, name: str) -> Programm: + # Wie macht man die rückgabe von Programm? + pass + + @abstractmethod + def remove(self, name: str): + pass + + @abstractmethod + def clear(self): + pass + + +# Basiskalssen + +class BaseFeature(ABC): + pass + + +class AWGDeviceFeature(AWGDevice, ABC, BaseFeature): + pass + + +class AWGChannelFeature(AWGChannel, ABC, BaseFeature): + pass + + +class AWGChannelTupleFeature(AWGChannelTuple, ABC, BaseFeature): + pass From 9c5f634f77effa7b6fda4ce9ff0c3e20fbb3e2c0 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 5 Nov 2019 12:41:53 +0100 Subject: [PATCH 021/107] Test push --- qupulse/hardware/awgs/AWGDriver.py | 106 +++++++++++++++++++---------- 1 file changed, 69 insertions(+), 37 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 72bdc2bf6..8b54770c6 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,9 +1,47 @@ from abc import ABC, abstractmethod +class BaseFeature(ABC): + pass + + +class AWGDeviceFeature(ABC, BaseFeature): + pass + + +class AWGChannelFeature(ABC, BaseFeature): + pass + + +class AWGChannelTupleFeature(ABC, BaseFeature): + pass + + +class AWGProgrammManager(ABC): + @abstractmethod + def add(self, program: Programm): + pass + + @abstractmethod + def get(self, name: str) -> Programm: + # Wie macht man die rückgabe von Programm? + pass + + @abstractmethod + def remove(self, name: str): + pass + + @abstractmethod + def clear(self): + pass + + class AWGDevice(ABC): - channels = [] - channel_groups = [] + def __init__(self): + self._channels = [] + self._channel_groups = [] + + self._featureList = [] @abstractmethod def initialize(self): @@ -22,14 +60,22 @@ def _send_querry(self, cmd: str) -> str: # Wie wird der Return vom String umgesetzt pass + @abstractmethod + def add_feature(self, feature: AWGDeviceFeature): + self._featureList.append(feature) + + class AWGChannelTuple(ABC): - def __init__(self, channel_tuple_id, device: AWGDevice, channels, sample_rate, programs): + def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, + programs: AWGProgrammManager): self._channel_tuptle_id = channel_tuple_id self._device = device self._channels = channels self._sample_rate = sample_rate self._programs = programs + self._featureList = [] + @abstractmethod def _send_cmd(self, cmd: str): pass @@ -39,6 +85,10 @@ def _send_querry(self, cmd: str) -> str: # Wie wird der Return vom String umgesetzt pass + @abstractmethod + def _add_feature(self, feature: AWGChannelFeature): + self._featureList.append(feature) + class AWGChannel(ABC): def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): @@ -46,6 +96,8 @@ def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannel self._device = device self._channel_tupel = channel_tupel + self._featureList = [] + @abstractmethod def _send_cmd(self, cmd: str): pass @@ -55,45 +107,25 @@ def _send_querry(self, cmd: str) -> str: # Was passiert wenn was anderes als ein String returnt wird? pass + @abstractmethod + def addFeature(self, feature: AWGChannelFeature): + self._featureList.append(feature) + # muss die Methode dann auch abstrakt sein? + + # Getter fuer alle Attribute @property def channel_id(self): return self._channel_id - # braucht channelId einen Setter? - -class AWGProgrammManager(ABC): - @abstractmethod - def add(self, program: Programm): - pass - - @abstractmethod - def get(self, name: str) -> Programm: - # Wie macht man die rückgabe von Programm? - pass - - @abstractmethod - def remove(self, name: str): - pass - - @abstractmethod - def clear(self): - pass - - -# Basiskalssen - -class BaseFeature(ABC): - pass - - -class AWGDeviceFeature(AWGDevice, ABC, BaseFeature): - pass - + @property + def device(self): + return self.device -class AWGChannelFeature(AWGChannel, ABC, BaseFeature): - pass + @property + def channel_tupel(self): + return self.channel_tupel + # braucht channelId einen Setter? -class AWGChannelTupleFeature(AWGChannelTuple, ABC, BaseFeature): - pass +# Basisklassen From 83e7a8a13c82d6456ebbc866f09ec1f3fb58d7ab Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Fri, 22 Nov 2019 08:01:43 +0100 Subject: [PATCH 022/107] Anfang der Umsetzung der Feature Strucktur --- qupulse/hardware/awgs/AWGDriver.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 8b54770c6..5ce54fa43 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -2,7 +2,8 @@ class BaseFeature(ABC): - pass + def __init__(self): + self._functionList = [] class AWGDeviceFeature(ABC, BaseFeature): @@ -17,21 +18,25 @@ class AWGChannelTupleFeature(ABC, BaseFeature): pass -class AWGProgrammManager(ABC): - @abstractmethod - def add(self, program: Programm): +class Program: + def __init__(self, name: str, program: Loop): + self._name = name + self._program = program + self._channel_ids = [] + self._marker_ids = [] + + +class AWGProgramManager: + def add(self, program: Program): pass - @abstractmethod - def get(self, name: str) -> Programm: + def get(self, name: str) -> Program: # Wie macht man die rückgabe von Programm? pass - @abstractmethod def remove(self, name: str): pass - @abstractmethod def clear(self): pass @@ -41,6 +46,7 @@ def __init__(self): self._channels = [] self._channel_groups = [] + # Code redundanz self._featureList = [] @abstractmethod @@ -63,11 +69,15 @@ def _send_querry(self, cmd: str) -> str: @abstractmethod def add_feature(self, feature: AWGDeviceFeature): self._featureList.append(feature) + # str is not a callable + for function in feature._functionList: + #in Liste einfügen + class AWGChannelTuple(ABC): def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, - programs: AWGProgrammManager): + programs: AWGProgramManager): self._channel_tuptle_id = channel_tuple_id self._device = device self._channels = channels From c27ce1b97a8bab484baff865f3d929de38daf737 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 9 Dec 2019 14:39:03 +0100 Subject: [PATCH 023/107] Erster Fertiger Entwurf - vor dem Testen --- qupulse/hardware/awgs/AWGDriver.py | 96 +++++++++++++++++++----------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 5ce54fa43..fc22acd4b 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -4,6 +4,27 @@ class BaseFeature(ABC): def __init__(self): self._functionList = [] + directory = dir(self) + tmp_list = [] + i = 0 + for attr in directory: + if callable(getattr(self, attr)) and attr[0] != "_": + tmp_list.append(attr) + + @property + def function_list(self): + return tuple(self._functionList) + + +class FeatureList(ABC): + def __init__(self): + self._featureList = [] + + def add_feature(self, feature: BaseFeature): + for function in feature.function_list: + setattr(self, function, getattr(feature, function)) + + self._featureList.append(feature) class AWGDeviceFeature(ABC, BaseFeature): @@ -18,36 +39,55 @@ class AWGChannelTupleFeature(ABC, BaseFeature): pass -class Program: +class Program(ABC): def __init__(self, name: str, program: Loop): self._name = name self._program = program self._channel_ids = [] self._marker_ids = [] + @property + def name(self): + return self._name + + @property + def program(self): + return self._program + + @property + def channel_ids(self): + return self._channel_ids + + @property + def marker_ids(self): + return self.marker_ids + -class AWGProgramManager: +class AWGProgramManager(ABC): + @abstractmethod def add(self, program: Program): pass + @abstractmethod def get(self, name: str) -> Program: - # Wie macht man die rückgabe von Programm? pass + @abstractmethod def remove(self, name: str): pass + @abstractmethod def clear(self): pass -class AWGDevice(ABC): +class AWGDevice(ABC, FeatureList): def __init__(self): self._channels = [] self._channel_groups = [] - # Code redundanz - self._featureList = [] + def add_feature(self, feature: AWGDeviceFeature): + super().add_feature(feature) @abstractmethod def initialize(self): @@ -63,19 +103,18 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Wie wird der Return vom String umgesetzt pass - @abstractmethod - def add_feature(self, feature: AWGDeviceFeature): - self._featureList.append(feature) - # str is not a callable - for function in feature._functionList: - #in Liste einfügen + @property + def channels(self): + return self._channels + @property + def channel_group(self): + return self._channel_groups -class AWGChannelTuple(ABC): +class AWGChannelTuple(ABC, FeatureList): def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, programs: AWGProgramManager): self._channel_tuptle_id = channel_tuple_id @@ -84,7 +123,8 @@ def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_ra self._sample_rate = sample_rate self._programs = programs - self._featureList = [] + def add_feature(self, feature: AWGChannelTupleFeature): + super().add_feature(feature) @abstractmethod def _send_cmd(self, cmd: str): @@ -92,21 +132,17 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Wie wird der Return vom String umgesetzt pass - @abstractmethod - def _add_feature(self, feature: AWGChannelFeature): - self._featureList.append(feature) - -class AWGChannel(ABC): +class AWGChannel(ABC, FeatureList): def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): self._channel_id = channel_id self._device = device self._channel_tupel = channel_tupel - self._featureList = [] + def add_feature(self, feature: AWGChannelFeature): + super().add_feature(feature) @abstractmethod def _send_cmd(self, cmd: str): @@ -114,28 +150,16 @@ def _send_cmd(self, cmd: str): @abstractmethod def _send_querry(self, cmd: str) -> str: - # Was passiert wenn was anderes als ein String returnt wird? pass - @abstractmethod - def addFeature(self, feature: AWGChannelFeature): - self._featureList.append(feature) - - # muss die Methode dann auch abstrakt sein? - - # Getter fuer alle Attribute @property def channel_id(self): return self._channel_id @property def device(self): - return self.device + return self._device @property def channel_tupel(self): - return self.channel_tupel - - # braucht channelId einen Setter? - -# Basisklassen + return self._channel_tupel From 4f8a9a2d1172bcf3995506761b0867c0deeb7cde Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 14 Jan 2020 10:22:25 +0100 Subject: [PATCH 024/107] Erste Verbesserungen eingebaut --- qupulse/hardware/awgs/AWGDriver.py | 143 +++++++++++++--------- tests/hardware/awg_tests.py | 186 +++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 55 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index fc22acd4b..1800a70c7 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,46 +1,76 @@ from abc import ABC, abstractmethod +from qupulse._program._loop import Loop +from typing import TypeVar, Generic, List +# TODO: Abstrakte Getter und Setter + class BaseFeature(ABC): def __init__(self): - self._functionList = [] + super().__init__() + + self._function_list = {} + directory = dir(self) - tmp_list = [] i = 0 for attr in directory: if callable(getattr(self, attr)) and attr[0] != "_": - tmp_list.append(attr) + if not (attr in self._function_list): + self._function_list[attr] = getattr(self, attr) + # self._functionList.append(attr) + + # TODO: es heisst function_list aber ist ein Dictionary @property def function_list(self): - return tuple(self._functionList) + return tuple(self._function_list) -class FeatureList(ABC): +FeatureType = TypeVar(BaseFeature) + + +class FeatureList(Generic[FeatureType], ABC): def __init__(self): - self._featureList = [] + super().__init__() + + self._feature_list = {} - def add_feature(self, feature: BaseFeature): + @property + def feature_list(self): + return self._feature_list + + def add_feature(self, feature: FeatureType): for function in feature.function_list: setattr(self, function, getattr(feature, function)) - self._featureList.append(feature) + # self._feature_list.append(feature) + self._feature_list[type(feature).__name__] = feature + @abstractmethod + def _send_cmd(self, cmd: str): + pass -class AWGDeviceFeature(ABC, BaseFeature): + @abstractmethod + def _send_query(self, cmd: str) -> str: + pass + + +class AWGDeviceFeature(BaseFeature, ABC): pass -class AWGChannelFeature(ABC, BaseFeature): +class AWGChannelFeature(BaseFeature, ABC): pass -class AWGChannelTupleFeature(ABC, BaseFeature): +class AWGChannelTupleFeature(BaseFeature, ABC): pass class Program(ABC): - def __init__(self, name: str, program: Loop): + def __init__(self, name: str, program: "Loop"): + super().__init__() + self._name = name self._program = program self._channel_ids = [] @@ -81,76 +111,73 @@ def clear(self): pass -class AWGDevice(ABC, FeatureList): +class AWGDevice(FeatureList[AWGDeviceFeature], ABC): def __init__(self): - self._channels = [] - self._channel_groups = [] + super().__init__() - def add_feature(self, feature: AWGDeviceFeature): - super().add_feature(feature) + self._channels = List["AWGChannel"] # TODO: "AWGChannel" + self._channel_groups = List[AWGChannelTuple] @abstractmethod def initialize(self): pass @abstractmethod - def synchronoize_channels(self, group: int): - pass - - @abstractmethod - def _send_cmd(self, cmd: str): - pass - - @abstractmethod - def _send_querry(self, cmd: str) -> str: + def cleanup(self): pass @property def channels(self): return self._channels + # TODO: Ueberpruefung in " " + #@channels.setter + #@abstractmethod + #def channels(self, channels: List["AWGChannel"]): + # pass + @property def channel_group(self): return self._channel_groups -class AWGChannelTuple(ABC, FeatureList): - def __init__(self, channel_tuple_id: int, device: AWGDevice, channels, sample_rate: float, - programs: AWGProgramManager): - self._channel_tuptle_id = channel_tuple_id +class AWGChannelTuple(FeatureList[AWGChannelTupleFeature], ABC): + def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): + super().__init__() + + self._channel_tuple_id = channel_tuple_id self._device = device self._channels = channels - self._sample_rate = sample_rate - self._programs = programs - def add_feature(self, feature: AWGChannelTupleFeature): - super().add_feature(feature) + #@property + #@abstractmethod + #def sample_rate(self): + # pass - @abstractmethod - def _send_cmd(self, cmd: str): - pass - - @abstractmethod - def _send_querry(self, cmd: str) -> str: - pass + #@sample_rate.setter + #@abstractmethod + #def channel_tuple(self, sample_rate: float): + # pass + @property + def channel_tuple_id(self): + return self._channel_tuple_id -class AWGChannel(ABC, FeatureList): - def __init__(self, channel_id: int, device: AWGDevice, channel_tupel: AWGChannelTuple): - self._channel_id = channel_id - self._device = device - self._channel_tupel = channel_tupel + @property + def device(self): + return self._device - def add_feature(self, feature: AWGChannelFeature): - super().add_feature(feature) + @property + def channels(self): + return self._channels - @abstractmethod - def _send_cmd(self, cmd: str): - pass - @abstractmethod - def _send_querry(self, cmd: str) -> str: - pass +class AWGChannel(FeatureList[AWGChannelFeature], ABC): + def __init__(self, channel_id: int, device: AWGDevice): + super().__init__() + self._channel_id = channel_id + self._device = device + self._channel_tuple = None @property def channel_id(self): @@ -161,5 +188,11 @@ def device(self): return self._device @property - def channel_tupel(self): + def channel_tuple(self): return self._channel_tupel + + # TODO: @channel_tuple.setter da hat kann es keinen _ davor haben. -> Namensgebungs? + + @channel_tuple.setter + def channel_tuple(self, channel_tuple: AWGChannelTuple): + self._channel_tuple = channel_tuple diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index e69de29bb..d85607509 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -0,0 +1,186 @@ +# Warnungen ignorieren +import warnings + +warnings.simplefilter("ignore", UserWarning) + +from qupulse.hardware.awgs.AWGDriver import * +import unittest +from typing import TypeVar, Generic, List + +print("--Device Test:") + + +class TestDevice(AWGDevice): + def __init__(self): + super().__init__() + self.initialize() + self._send_cmd("TestCMD") + + def initialize(self): + print("initialize") + + #def synchronize_channels(self, group: int): + # print("synchronize channels") + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query") + return "test String" + + def cleanup(self): + pass + + +class DeviceTestFeature(AWGDeviceFeature): + def device_test_feature_methode1(self): + print("feature1methode1 works") + + def device_test_feature_methode2(self): + print("feature1methode2 works") + + +deviceTestFeature = DeviceTestFeature() +device = TestDevice() + +print("Dir(device): ") +print(dir(device)) +device.add_feature(deviceTestFeature) +print("Dir(device) mit Feature: ") +print(dir(device)) + +device.initialize() +device.device_test_feature_methode1() +device.device_test_feature_methode2() + +print("") + + +class Loop: + pass + + +testLoop = Loop() + + +class TestProgram(Program): + def __init__(self, name: str, loop: Loop): + super().__init__(name, loop) + + +testProgram = TestProgram("TestProgram", testLoop) + + +class TestProgramManager(AWGProgramManager): + def add(self, program: Program): + print("Test " + Program) + + def get(self): + pass + + def remove(self, name: str): + print("remove") + + def clear(self): + print("clear") + + +testProgramManager = TestProgramManager() + +print("--ChannelTupelTest:") + + +class TestChannelTuple(AWGChannelTuple): + def __init__(self): + super().__init__(1, device, 8) + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query") + return str + + def sample_rate(self, sample_rate: float): + pass + + +channelTupel = TestChannelTuple() + + +class ChannelTupelTestFeature(AWGChannelTupleFeature): + def channel_tupel_test_feature_methode1(self): + print("ChannelTupelTestFeatureMethode1 works") + + def channel_tupel_test_feature_methode2(self): + print("ChannelTupelTestFeatureMethode2 works") + + +channelTupelTestFeature = ChannelTupelTestFeature() + +channelTupel.add_feature(channelTupelTestFeature) +print("dir(channel):") +print(dir(channelTupel)) +channelTupel.add_feature(channelTupelTestFeature) +print("dir(channel):") +print(dir(channelTupel)) + +channelTupel.channel_tupel_test_feature_methode1() +channelTupel.channel_tupel_test_feature_methode2() + +print("") + + +class TestChannel(AWGChannel): + def __init__(self, channel_id: int): + super().__init__(channel_id, device) + + def _send_cmd(self, cmd: str): + print("send cmd: " + cmd) + + def _send_query(self, cmd: str) -> str: + print("send query: " + cmd) + return str + + +print("--ChannelTest:") + + +class ChannelTestFeature(AWGChannelFeature): + def channel_test_feature_methode1(self): + print("ChannelTestFeatureMethode1 works") + + def channel_test_feature_methode2(self): + print("ChannelTestFeatureMethode2 works") + + +class SynchronizeChannels(AWGChannelFeature): + def synchronize(self, test: List[AWGChannel]): + print("ChannelSynchronisieren") + + +channel = TestChannel(1) +channelTestFeature = ChannelTestFeature() + +print("dir(channel):") +print(dir(channel)) +channel.add_feature(channelTestFeature) +print("dir(channel):") +print(dir(channel)) +channel.channel_test_feature_methode1() +channel.channel_test_feature_methode2() + + +class TestAWGDriver(unittest.TestCase): + + def TestDeviceAddFeature(self): + pass + + +test_list = List[AWGChannel] + + +#--- + +testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), TestChannel(6), TestChannel(7)] \ No newline at end of file From 74db239faa9ed248cecc5de3ee1dd44917a9cb9d Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 14 Jan 2020 13:18:46 +0100 Subject: [PATCH 025/107] Abstrakte Property ueberarbeitet --- qupulse/hardware/awgs/AWGDriver.py | 32 +++++++++--------------------- tests/hardware/awg_tests.py | 12 +++-------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 1800a70c7..82a6fecd0 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -2,9 +2,6 @@ from qupulse._program._loop import Loop from typing import TypeVar, Generic, List - -# TODO: Abstrakte Getter und Setter - class BaseFeature(ABC): def __init__(self): super().__init__() @@ -17,7 +14,6 @@ def __init__(self): if callable(getattr(self, attr)) and attr[0] != "_": if not (attr in self._function_list): self._function_list[attr] = getattr(self, attr) - # self._functionList.append(attr) # TODO: es heisst function_list aber ist ein Dictionary @@ -43,7 +39,6 @@ def add_feature(self, feature: FeatureType): for function in feature.function_list: setattr(self, function, getattr(feature, function)) - # self._feature_list.append(feature) self._feature_list[type(feature).__name__] = feature @abstractmethod @@ -130,12 +125,6 @@ def cleanup(self): def channels(self): return self._channels - # TODO: Ueberpruefung in " " - #@channels.setter - #@abstractmethod - #def channels(self, channels: List["AWGChannel"]): - # pass - @property def channel_group(self): return self._channel_groups @@ -149,15 +138,15 @@ def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): self._device = device self._channels = channels - #@property - #@abstractmethod - #def sample_rate(self): - # pass + @property + @abstractmethod + def sample_rate(self): + pass - #@sample_rate.setter - #@abstractmethod - #def channel_tuple(self, sample_rate: float): - # pass + @sample_rate.setter + @abstractmethod + def sample_rate(self, sample_rate: float): + pass @property def channel_tuple_id(self): @@ -191,8 +180,5 @@ def device(self): def channel_tuple(self): return self._channel_tupel - # TODO: @channel_tuple.setter da hat kann es keinen _ davor haben. -> Namensgebungs? - - @channel_tuple.setter - def channel_tuple(self, channel_tuple: AWGChannelTuple): + def _set_channel_tuple(self, channel_tuple: AWGChannelTuple): self._channel_tuple = channel_tuple diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index d85607509..1764a85dc 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -19,9 +19,6 @@ def __init__(self): def initialize(self): print("initialize") - #def synchronize_channels(self, group: int): - # print("synchronize channels") - def _send_cmd(self, cmd: str): print("send cmd: " + cmd) @@ -141,7 +138,7 @@ def _send_cmd(self, cmd: str): def _send_query(self, cmd: str) -> str: print("send query: " + cmd) - return str + return cmd print("--ChannelTest:") @@ -173,14 +170,11 @@ def synchronize(self, test: List[AWGChannel]): class TestAWGDriver(unittest.TestCase): - def TestDeviceAddFeature(self): pass test_list = List[AWGChannel] - -#--- - -testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), TestChannel(6), TestChannel(7)] \ No newline at end of file +testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), + TestChannel(6), TestChannel(7)] From 4af016b9a25d661aa02d2274d5c841ba478bb4c3 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Fri, 17 Jan 2020 10:52:01 +0100 Subject: [PATCH 026/107] Finished AWG Driver --- qupulse/hardware/awgs/AWGDriver.py | 228 ++++++++++++++++------------- tests/hardware/awg_tests.py | 180 ----------------------- 2 files changed, 123 insertions(+), 285 deletions(-) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/AWGDriver.py index 82a6fecd0..1ecd95c89 100644 --- a/qupulse/hardware/awgs/AWGDriver.py +++ b/qupulse/hardware/awgs/AWGDriver.py @@ -1,184 +1,202 @@ +import warnings from abc import ABC, abstractmethod -from qupulse._program._loop import Loop -from typing import TypeVar, Generic, List +from copy import copy +from typing import TypeVar, Generic, List, Iterable, Dict, Callable + class BaseFeature(ABC): + """ + Base class for features of `FeatureAble`s. + + Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. + This ensures that all targets for the same feature are using the same signature for the feature's functions. All + public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` + dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. + """ + def __init__(self): super().__init__() - self._function_list = {} + self._functions = self._read_functions() + + def _read_functions(self) -> Dict[str, Callable]: + """ + Reads the functions of a feature and returns them as a dictionary + Return: + Returns dictionary with all functions of the feature + """ directory = dir(self) - i = 0 + function_list = {} for attr in directory: - if callable(getattr(self, attr)) and attr[0] != "_": - if not (attr in self._function_list): - self._function_list[attr] = getattr(self, attr) - - # TODO: es heisst function_list aber ist ein Dictionary + if callable(getattr(type(self), attr)) and attr[0] != "_": + if not (attr in function_list): + function_list[attr] = getattr(self, attr) + return function_list @property - def function_list(self): - return tuple(self._function_list) + def functions(self) -> Dict[str, Callable]: + """Returns a copy of the dictionary with all public functions of the feature""" + return copy(self._functions) FeatureType = TypeVar(BaseFeature) -class FeatureList(Generic[FeatureType], ABC): +class FeatureAble(Generic[FeatureType], ABC): + """Base class for all classes that are able to add features""" + def __init__(self): super().__init__() - self._feature_list = {} + self._features = {} @property - def feature_list(self): - return self._feature_list + def features(self) -> Dict[str, Callable]: + """Returns the dictionary with all features of a FeatureAble""" + return copy(self._features) - def add_feature(self, feature: FeatureType): - for function in feature.function_list: - setattr(self, function, getattr(feature, function)) + def add_feature(self, feature: FeatureType) -> None: + """ + The method adds all functions of feature to a dictionary with all functions - self._feature_list[type(feature).__name__] = feature + Args: + feature: A certain feature which functions should be added to the dictionary _features + """ + if not isinstance(feature, FeatureType): + raise TypeError("Invalid type for feature") - @abstractmethod - def _send_cmd(self, cmd: str): - pass + for function in feature.function_list: + if not hasattr(self, function): + setattr(self, function, getattr(feature, function)) + else: + warnings.warning( + f"Ommiting function \"{function}\": Another attribute with this name already exists.") - @abstractmethod - def _send_query(self, cmd: str) -> str: - pass + self._features[type(feature).__name__] = feature class AWGDeviceFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGDevice`s""" pass class AWGChannelFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGChannel`s""" pass class AWGChannelTupleFeature(BaseFeature, ABC): + """Base class for features that are used for `AWGChannelTuple`s""" pass -class Program(ABC): - def __init__(self, name: str, program: "Loop"): - super().__init__() +class BaseAWG(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 - self._program = program - self._channel_ids = [] - self._marker_ids = [] - @property - def name(self): - return self._name + def __del__(self): + self.cleanup() - @property - def program(self): - return self._program + @abstractmethod + def cleanup(self) -> None: + """Function for cleaning up the dependencies of the device""" + pass @property - def channel_ids(self): - return self._channel_ids + def name(self) -> str: + """Returns the name of a Device as a String""" + return self._name @property - def marker_ids(self): - return self.marker_ids - - -class AWGProgramManager(ABC): @abstractmethod - def add(self, program: Program): - pass + def channels(self) -> Iterable["BaseAWGChannel"]: + """Returns a list of all channels of a Device""" + return self._channels + @property @abstractmethod - def get(self, name: str) -> Program: + def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + """Returns a list of all channel tuples of a list""" pass - @abstractmethod - def remove(self, name: str): - pass - - @abstractmethod - def clear(self): - pass +class BaseAWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): + """Base class for all groups of synchronized channels of an AWG""" -class AWGDevice(FeatureList[AWGDeviceFeature], ABC): - def __init__(self): + def __init__(self, idn: int): + """ + Args: + idn: The identification number of a channel tuple + """ super().__init__() - self._channels = List["AWGChannel"] # TODO: "AWGChannel" - self._channel_groups = List[AWGChannelTuple] - - @abstractmethod - def initialize(self): - pass + self._idn = idn + @property @abstractmethod - def cleanup(self): + def sample_rate(self) -> float: + """Returns the sample rate of a channel tuple as a float""" pass @property - def channels(self): - return self._channels - - @property - def channel_group(self): - return self._channel_groups - - -class AWGChannelTuple(FeatureList[AWGChannelTupleFeature], ABC): - def __init__(self, channel_tuple_id: int, device: AWGDevice, channels): - super().__init__() - - self._channel_tuple_id = channel_tuple_id - self._device = device - self._channels = channels + def idn(self) -> int: + """Returns the identification number of a channel tuple""" + return self._idn @property @abstractmethod - def sample_rate(self): + def device(self) -> BaseAWG: + """Returns the device which the channel tuple belong to""" pass - @sample_rate.setter + @property @abstractmethod - def sample_rate(self, sample_rate: float): + def channels(self) -> Iterable["BaseAWGChannel"]: + """Returns a list of all channels of the channel tuple""" pass - @property - def channel_tuple_id(self): - return self._channel_tuple_id - - @property - def device(self): - return self._device - - @property - def channels(self): - return self._channels +class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): + """Base class for a single channel of an AWG""" -class AWGChannel(FeatureList[AWGChannelFeature], ABC): - def __init__(self, channel_id: int, device: AWGDevice): + def __init__(self, idn: int): + """ + Args: + idn: The identification number of a channel + """ super().__init__() - self._channel_id = channel_id - self._device = device - self._channel_tuple = None + self._idn = idn @property - def channel_id(self): - return self._channel_id + def idn(self) -> int: + """Returns the identification number of a channel""" + return self._idn @property - def device(self): - return self._device + @abstractmethod + def device(self) -> BaseAWG: + """Returns the device which the channel belongs to""" + pass @property - def channel_tuple(self): - return self._channel_tupel + @abstractmethod + def channel_tuple(self) -> BaseAWGChannelTuple: + """Returns the channel tuple which a channel belongs to""" + pass - def _set_channel_tuple(self, channel_tuple: AWGChannelTuple): - self._channel_tuple = channel_tuple + @abstractmethod + def _set_channel_tuple(self, channel_tuple: BaseAWGChannelTuple): + """ + Sets the channel tuple which a channel belongs to + Args: + channel_tuple: reference to the channel tuple + """ + pass diff --git a/tests/hardware/awg_tests.py b/tests/hardware/awg_tests.py index 1764a85dc..e69de29bb 100644 --- a/tests/hardware/awg_tests.py +++ b/tests/hardware/awg_tests.py @@ -1,180 +0,0 @@ -# Warnungen ignorieren -import warnings - -warnings.simplefilter("ignore", UserWarning) - -from qupulse.hardware.awgs.AWGDriver import * -import unittest -from typing import TypeVar, Generic, List - -print("--Device Test:") - - -class TestDevice(AWGDevice): - def __init__(self): - super().__init__() - self.initialize() - self._send_cmd("TestCMD") - - def initialize(self): - print("initialize") - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query") - return "test String" - - def cleanup(self): - pass - - -class DeviceTestFeature(AWGDeviceFeature): - def device_test_feature_methode1(self): - print("feature1methode1 works") - - def device_test_feature_methode2(self): - print("feature1methode2 works") - - -deviceTestFeature = DeviceTestFeature() -device = TestDevice() - -print("Dir(device): ") -print(dir(device)) -device.add_feature(deviceTestFeature) -print("Dir(device) mit Feature: ") -print(dir(device)) - -device.initialize() -device.device_test_feature_methode1() -device.device_test_feature_methode2() - -print("") - - -class Loop: - pass - - -testLoop = Loop() - - -class TestProgram(Program): - def __init__(self, name: str, loop: Loop): - super().__init__(name, loop) - - -testProgram = TestProgram("TestProgram", testLoop) - - -class TestProgramManager(AWGProgramManager): - def add(self, program: Program): - print("Test " + Program) - - def get(self): - pass - - def remove(self, name: str): - print("remove") - - def clear(self): - print("clear") - - -testProgramManager = TestProgramManager() - -print("--ChannelTupelTest:") - - -class TestChannelTuple(AWGChannelTuple): - def __init__(self): - super().__init__(1, device, 8) - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query") - return str - - def sample_rate(self, sample_rate: float): - pass - - -channelTupel = TestChannelTuple() - - -class ChannelTupelTestFeature(AWGChannelTupleFeature): - def channel_tupel_test_feature_methode1(self): - print("ChannelTupelTestFeatureMethode1 works") - - def channel_tupel_test_feature_methode2(self): - print("ChannelTupelTestFeatureMethode2 works") - - -channelTupelTestFeature = ChannelTupelTestFeature() - -channelTupel.add_feature(channelTupelTestFeature) -print("dir(channel):") -print(dir(channelTupel)) -channelTupel.add_feature(channelTupelTestFeature) -print("dir(channel):") -print(dir(channelTupel)) - -channelTupel.channel_tupel_test_feature_methode1() -channelTupel.channel_tupel_test_feature_methode2() - -print("") - - -class TestChannel(AWGChannel): - def __init__(self, channel_id: int): - super().__init__(channel_id, device) - - def _send_cmd(self, cmd: str): - print("send cmd: " + cmd) - - def _send_query(self, cmd: str) -> str: - print("send query: " + cmd) - return cmd - - -print("--ChannelTest:") - - -class ChannelTestFeature(AWGChannelFeature): - def channel_test_feature_methode1(self): - print("ChannelTestFeatureMethode1 works") - - def channel_test_feature_methode2(self): - print("ChannelTestFeatureMethode2 works") - - -class SynchronizeChannels(AWGChannelFeature): - def synchronize(self, test: List[AWGChannel]): - print("ChannelSynchronisieren") - - -channel = TestChannel(1) -channelTestFeature = ChannelTestFeature() - -print("dir(channel):") -print(dir(channel)) -channel.add_feature(channelTestFeature) -print("dir(channel):") -print(dir(channel)) -channel.channel_test_feature_methode1() -channel.channel_test_feature_methode2() - - -class TestAWGDriver(unittest.TestCase): - def TestDeviceAddFeature(self): - pass - - -test_list = List[AWGChannel] - -testChannelList = [TestChannel(0), TestChannel(1), TestChannel(2), TestChannel(3), TestChannel(4), TestChannel(5), - TestChannel(6), TestChannel(7)] From 36fdc8d54c0842422aed2d1f379a42ed28af964b Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:09:02 +0100 Subject: [PATCH 027/107] Rename hardware/awgs/base.py to old_base.py --- qupulse/hardware/awgs/base.py | 131 ------------------------------ qupulse/hardware/awgs/old_base.py | 1 + 2 files changed, 1 insertion(+), 131 deletions(-) delete mode 100644 qupulse/hardware/awgs/base.py diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py deleted file mode 100644 index 13d4ad876..000000000 --- a/qupulse/hardware/awgs/base.py +++ /dev/null @@ -1,131 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Iterable, Optional - -from .base_features import Feature, FeatureAble - - -class AWGFeature(Feature, ABC): - """Base class for features that are used for `AWGDevice`s""" - pass - - -class AWGChannelFeature(Feature, ABC): - """Base class for features that are used for `AWGChannel`s""" - pass - - -class AWGChannelTupleFeature(Feature, ABC): - """Base class for features that are used for `AWGChannelTuple`s""" - pass - - -class AWGDevice(FeatureAble[AWGFeature], 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 - - def __del__(self): - self.cleanup() - - @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) -> Iterable["AWGChannel"]: - """Returns a list of all channels of a Device""" - raise NotImplementedError() - - @property - @abstractmethod - def channel_tuples(self) -> Iterable["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 - def idn(self) -> int: - """Returns the identification number of a channel tuple""" - return 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) -> Iterable["AWGChannel"]: - """Returns a list of all channels of the channel tuple""" - raise NotImplementedError() - - -class AWGChannel(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() diff --git a/qupulse/hardware/awgs/old_base.py b/qupulse/hardware/awgs/old_base.py index dd8750907..104629ee3 100644 --- a/qupulse/hardware/awgs/old_base.py +++ b/qupulse/hardware/awgs/old_base.py @@ -257,3 +257,4 @@ def __init__(self, channel): def __str__(self) -> str: return 'Marker or channel not found: {}'.format(self.channel) + From 979005e83aa1330a186360c93d36f0dad7e2f59a Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:09:28 +0100 Subject: [PATCH 028/107] Rename AWGDriver.py to base.py --- qupulse/hardware/awgs/{AWGDriver.py => base.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename qupulse/hardware/awgs/{AWGDriver.py => base.py} (100%) diff --git a/qupulse/hardware/awgs/AWGDriver.py b/qupulse/hardware/awgs/base.py similarity index 100% rename from qupulse/hardware/awgs/AWGDriver.py rename to qupulse/hardware/awgs/base.py From 70700c3b90b900d358634ccd8dfd823addf4c681 Mon Sep 17 00:00:00 2001 From: Lukas Lankes <41990335+lankes-fzj@users.noreply.github.com> Date: Fri, 17 Jan 2020 12:10:44 +0100 Subject: [PATCH 029/107] Update tabor.py Import base classes from old_base --- qupulse/hardware/awgs/tabor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 684022ec1..7878cb712 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -19,10 +19,9 @@ from qupulse.hardware.awgs.features.device_mirror_feature import DeviceMirrorFeature from qupulse.utils.types import ChannelID from qupulse._program._loop import Loop, make_compatible - from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times -from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling -from qupulse.hardware.awgs.base_features import FeatureAble +from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling + from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple From 5a0a15b79228cc95b6b4a709508be37842f04546 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Fri, 17 Jan 2020 13:58:42 +0100 Subject: [PATCH 030/107] Minor bug fixes Refactoring of class/function names Modification of import statements for hardware.awgs.old_base --- qupulse/hardware/awgs/base.py | 129 ++++++------------------- qupulse/hardware/awgs/base_features.py | 2 + 2 files changed, 31 insertions(+), 100 deletions(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 1ecd95c89..af35402c4 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,97 +1,25 @@ -import warnings from abc import ABC, abstractmethod -from copy import copy -from typing import TypeVar, Generic, List, Iterable, Dict, Callable +from typing import Iterable, Optional +from .base_features import BaseFeature, FeatureAble -class BaseFeature(ABC): - """ - Base class for features of `FeatureAble`s. - Features are classes containing functions which are bound dynamically to the target instance of type `FeatureAble`. - This ensures that all targets for the same feature are using the same signature for the feature's functions. All - public callables of a specific feature will be added to the function dictionary. Those functions (in the `functions` - dictionary) will be automatically added to the specific `FeatureAble` that calls `FeatureAble.add_feature`. - """ - - def __init__(self): - super().__init__() - - self._functions = self._read_functions() - - def _read_functions(self) -> Dict[str, Callable]: - """ - Reads the functions of a feature and returns them as a dictionary - - Return: - Returns dictionary with all functions of the feature - """ - directory = dir(self) - function_list = {} - for attr in directory: - if callable(getattr(type(self), attr)) and attr[0] != "_": - if not (attr in function_list): - function_list[attr] = getattr(self, attr) - return function_list - - @property - def functions(self) -> Dict[str, Callable]: - """Returns a copy of the dictionary with all public functions of the feature""" - return copy(self._functions) - - -FeatureType = TypeVar(BaseFeature) - - -class FeatureAble(Generic[FeatureType], ABC): - """Base class for all classes that are able to add features""" - - def __init__(self): - super().__init__() - - self._features = {} - - @property - def features(self) -> Dict[str, Callable]: - """Returns the dictionary with all features of a FeatureAble""" - return copy(self._features) - - def add_feature(self, feature: FeatureType) -> None: - """ - The method adds all functions of feature to a dictionary with all functions - - Args: - feature: A certain feature which functions should be added to the dictionary _features - """ - if not isinstance(feature, FeatureType): - raise TypeError("Invalid type for feature") - - for function in feature.function_list: - if not hasattr(self, function): - setattr(self, function, getattr(feature, function)) - else: - warnings.warning( - f"Ommiting function \"{function}\": Another attribute with this name already exists.") - - self._features[type(feature).__name__] = feature - - -class AWGDeviceFeature(BaseFeature, ABC): +class BaseAWGFeature(BaseFeature, ABC): """Base class for features that are used for `AWGDevice`s""" pass -class AWGChannelFeature(BaseFeature, ABC): +class BaseAWGChannelFeature(BaseFeature, ABC): """Base class for features that are used for `AWGChannel`s""" pass -class AWGChannelTupleFeature(BaseFeature, ABC): +class BaseAWGChannelTupleFeature(BaseFeature, ABC): """Base class for features that are used for `AWGChannelTuple`s""" pass -class BaseAWG(FeatureAble[AWGDeviceFeature], ABC): +class BaseAWG(FeatureAble[BaseAWGFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -105,30 +33,30 @@ def __init__(self, name: str): def __del__(self): self.cleanup() - @abstractmethod - def cleanup(self) -> None: - """Function for cleaning up the dependencies of the device""" - pass - @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) -> Iterable["BaseAWGChannel"]: """Returns a list of all channels of a Device""" - return self._channels + raise NotImplementedError() @property @abstractmethod def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: """Returns a list of all channel tuples of a list""" - pass + raise NotImplementedError() -class BaseAWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): +class BaseAWGChannelTuple(FeatureAble[BaseAWGChannelTupleFeature], ABC): """Base class for all groups of synchronized channels of an AWG""" def __init__(self, idn: int): @@ -140,31 +68,31 @@ def __init__(self, idn: int): self._idn = idn - @property - @abstractmethod - def sample_rate(self) -> float: - """Returns the sample rate of a channel tuple as a float""" - pass - @property def idn(self) -> int: """Returns the identification number of a channel tuple""" return 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) -> BaseAWG: """Returns the device which the channel tuple belong to""" - pass + raise NotImplementedError() @property @abstractmethod def channels(self) -> Iterable["BaseAWGChannel"]: """Returns a list of all channels of the channel tuple""" - pass + raise NotImplementedError() -class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): +class BaseAWGChannel(FeatureAble[BaseAWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -184,19 +112,20 @@ def idn(self) -> int: @abstractmethod def device(self) -> BaseAWG: """Returns the device which the channel belongs to""" - pass + raise NotImplementedError() @property @abstractmethod - def channel_tuple(self) -> BaseAWGChannelTuple: + def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: """Returns the channel tuple which a channel belongs to""" - pass + raise NotImplementedError() @abstractmethod - def _set_channel_tuple(self, channel_tuple: BaseAWGChannelTuple): + 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 """ - pass + raise NotImplementedError() diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index c79a45cb8..a4391d83d 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -21,6 +21,7 @@ class FeatureAble(Generic[FeatureType], ABC): can be called with __getitem__. """ + def __init__(self): super().__init__() @@ -45,3 +46,4 @@ def add_feature(self, feature: FeatureType) -> None: def features(self) -> Dict[FeatureType, Callable]: """Returns the dictionary with all features of a FeatureAble""" return copy(self._features) + From 329b035e019d5f7d43f40c6e00efb8b310c51b32 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Fri, 17 Jan 2020 14:35:34 +0100 Subject: [PATCH 031/107] Added a test script for the base drivers Actually no unit tests, yet, but a first example of how to use the base drivers --- tests/hardware/awg_base_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index d045d8839..a0684f4e8 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -244,3 +244,4 @@ def test_error_thrown(self): if __name__ == '__main__': unittest.main() + From e9f4ccb417abf6005aec4c8be28aa2a78675f0f0 Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Mon, 27 Jan 2020 15:49:55 +0100 Subject: [PATCH 032/107] Change of the feature-logic --- qupulse/hardware/awgs/base_features.py | 3 --- tests/hardware/awg_base_tests.py | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index a4391d83d..e2cbef14a 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -11,7 +11,6 @@ class Feature(ABC): """ pass - FeatureType = TypeVar("FeatureType", bound=Feature) @@ -21,7 +20,6 @@ class FeatureAble(Generic[FeatureType], ABC): can be called with __getitem__. """ - def __init__(self): super().__init__() @@ -46,4 +44,3 @@ def add_feature(self, feature: FeatureType) -> None: def features(self) -> Dict[FeatureType, Callable]: """Returns the dictionary with all features of a FeatureAble""" return copy(self._features) - diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index a0684f4e8..37c938261 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -79,8 +79,7 @@ def __init__(self, name: str): self._channel_tuples = [] # Call the feature function, with the feature's signature - self[SynchronizeChannelsFeature].synchronize_channels( - 2) # default channel synchronization with a group size of 2 + self[SynchronizeChannelsFeature].synchronize_channels(2) # default channel synchronization with a group size of 2 def cleanup(self) -> None: """This will be called automatically in __del__""" @@ -233,7 +232,6 @@ def test_channel_tupels(self): f"Invalid channel tuple {channel.channel_tuple.idn} for channel {i}") self.assertTrue(channel in channel.channel_tuple.channels, f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}") - self.assertEqual(len(self.device.channel_tuples), 1, "Invalid number of channel tuples") def test_error_thrown(self): @@ -244,4 +242,3 @@ def test_error_thrown(self): if __name__ == '__main__': unittest.main() - From 123d31eaf9fb3750f73a155dc85aab348cbfa30c Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Tue, 28 Jan 2020 13:49:36 +0100 Subject: [PATCH 033/107] Unittests added --- tests/hardware/awg_base_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 37c938261..1ac467665 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -79,7 +79,8 @@ def __init__(self, name: str): self._channel_tuples = [] # Call the feature function, with the feature's signature - self[SynchronizeChannelsFeature].synchronize_channels(2) # default channel synchronization with a group size of 2 + self[SynchronizeChannelsFeature].synchronize_channels( + 2) # default channel synchronization with a group size of 2 def cleanup(self) -> None: """This will be called automatically in __del__""" From e72b24fcdbf831fc72f6e9204917f59b66df143c Mon Sep 17 00:00:00 2001 From: Papajewski <b.papajewski@fz-juelich.de> Date: Wed, 5 Feb 2020 08:26:15 +0100 Subject: [PATCH 034/107] Removed 'Base' form the names of the classes --- qupulse/hardware/awgs/base.py | 22 +++++++++++----------- tests/hardware/awg_base_tests.py | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index af35402c4..84bf5375c 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,25 +1,25 @@ from abc import ABC, abstractmethod from typing import Iterable, Optional -from .base_features import BaseFeature, FeatureAble +from .base_features import Feature, FeatureAble -class BaseAWGFeature(BaseFeature, ABC): +class AWGFeature(Feature, ABC): """Base class for features that are used for `AWGDevice`s""" pass -class BaseAWGChannelFeature(BaseFeature, ABC): +class AWGChannelFeature(Feature, ABC): """Base class for features that are used for `AWGChannel`s""" pass -class BaseAWGChannelTupleFeature(BaseFeature, ABC): +class AWGChannelTupleFeature(Feature, ABC): """Base class for features that are used for `AWGChannelTuple`s""" pass -class BaseAWG(FeatureAble[BaseAWGFeature], ABC): +class AWG(FeatureAble[AWGFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -51,12 +51,12 @@ def channels(self) -> Iterable["BaseAWGChannel"]: @property @abstractmethod - def channel_tuples(self) -> Iterable["BaseAWGChannelTuple"]: + def channel_tuples(self) -> Iterable["AWGChannelTuple"]: """Returns a list of all channel tuples of a list""" raise NotImplementedError() -class BaseAWGChannelTuple(FeatureAble[BaseAWGChannelTupleFeature], ABC): +class AWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): """Base class for all groups of synchronized channels of an AWG""" def __init__(self, idn: int): @@ -81,7 +81,7 @@ def sample_rate(self) -> float: @property @abstractmethod - def device(self) -> BaseAWG: + def device(self) -> AWG: """Returns the device which the channel tuple belong to""" raise NotImplementedError() @@ -92,7 +92,7 @@ def channels(self) -> Iterable["BaseAWGChannel"]: raise NotImplementedError() -class BaseAWGChannel(FeatureAble[BaseAWGChannelFeature], ABC): +class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -110,13 +110,13 @@ def idn(self) -> int: @property @abstractmethod - def device(self) -> BaseAWG: + def device(self) -> AWG: """Returns the device which the channel belongs to""" raise NotImplementedError() @property @abstractmethod - def channel_tuple(self) -> Optional[BaseAWGChannelTuple]: + def channel_tuple(self) -> Optional[AWGChannelTuple]: """Returns the channel tuple which a channel belongs to""" raise NotImplementedError() diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 1ac467665..2bde9801b 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -5,7 +5,6 @@ import unittest from typing import Callable, Collection, Iterable, List, Optional - from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGFeature, \ AWGChannelFeature, AWGChannelTupleFeature From 12dd7d6d76709ae30a5df1a221b20383ffb67aa5 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 09:01:34 +0100 Subject: [PATCH 035/107] Renamed BaseAWGChannel to AWGChannel and AWG to AWGDevice to avoid confusion with old_base.AWG --- qupulse/hardware/awgs/base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 84bf5375c..13d4ad876 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -19,7 +19,7 @@ class AWGChannelTupleFeature(Feature, ABC): pass -class AWG(FeatureAble[AWGFeature], ABC): +class AWGDevice(FeatureAble[AWGFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -45,7 +45,7 @@ def cleanup(self) -> None: @property @abstractmethod - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: """Returns a list of all channels of a Device""" raise NotImplementedError() @@ -81,18 +81,18 @@ def sample_rate(self) -> float: @property @abstractmethod - def device(self) -> AWG: + def device(self) -> AWGDevice: """Returns the device which the channel tuple belong to""" raise NotImplementedError() @property @abstractmethod - def channels(self) -> Iterable["BaseAWGChannel"]: + def channels(self) -> Iterable["AWGChannel"]: """Returns a list of all channels of the channel tuple""" raise NotImplementedError() -class BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): +class AWGChannel(FeatureAble[AWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -110,7 +110,7 @@ def idn(self) -> int: @property @abstractmethod - def device(self) -> AWG: + def device(self) -> AWGDevice: """Returns the device which the channel belongs to""" raise NotImplementedError() From f80e25f6dab0a075212471fa3dd0feacdd4e4b1c Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 15:03:04 +0100 Subject: [PATCH 036/107] Some improvements for the feature logic * Features are now implemented as a subclass * Error handling when adding/getting features * Added some first features in features.py. This file will contain general features, which can be subclassed for specific instruments --- qupulse/hardware/awgs/base.py | 66 ++++++-- qupulse/hardware/awgs/base_features.py | 80 ++++++++-- qupulse/hardware/awgs/features.py | 138 +++++++++++++++++ tests/hardware/awg_base_tests.py | 202 ++++++++++++++----------- 4 files changed, 370 insertions(+), 116 deletions(-) create mode 100644 qupulse/hardware/awgs/features.py diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 13d4ad876..2325117a7 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,25 +1,32 @@ from abc import ABC, abstractmethod -from typing import Iterable, Optional +from typing import Collection, Optional -from .base_features import Feature, FeatureAble +from qupulse.hardware.awgs.base_features import Feature, FeatureAble -class AWGFeature(Feature, ABC): +__all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature", + "AWGChannelTupleFeature"] + + +class AWGDeviceFeature(Feature, ABC): """Base class for features that are used for `AWGDevice`s""" - pass + def __init__(self): + super().__init__(AWGDevice) class AWGChannelFeature(Feature, ABC): """Base class for features that are used for `AWGChannel`s""" - pass + def __init__(self): + super().__init__(AWGChannel) class AWGChannelTupleFeature(Feature, ABC): """Base class for features that are used for `AWGChannelTuple`s""" - pass + def __init__(self): + super().__init__(AWGChannelTuple) -class AWGDevice(FeatureAble[AWGFeature], ABC): +class AWGDevice(FeatureAble[AWGDeviceFeature], ABC): """Base class for all drivers of all arbitrary waveform generators""" def __init__(self, name: str): @@ -45,13 +52,19 @@ def cleanup(self) -> None: @property @abstractmethod - def channels(self) -> Iterable["AWGChannel"]: + def channels(self) -> Collection["AWGChannel"]: """Returns a list of all channels of a Device""" raise NotImplementedError() @property @abstractmethod - def channel_tuples(self) -> Iterable["AWGChannelTuple"]: + 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() @@ -73,12 +86,21 @@ 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() + # Optional sample_rate-setter + # @sample_rate.setter + # def sample_rate(self, sample_rate: float) -> None: + @property @abstractmethod def device(self) -> AWGDevice: @@ -87,12 +109,18 @@ def device(self) -> AWGDevice: @property @abstractmethod - def channels(self) -> Iterable["AWGChannel"]: + 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 AWGChannel(FeatureAble[AWGChannelFeature], ABC): +class _BaseAWGChannel(ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -129,3 +157,19 @@ def _set_channel_tuple(self, channel_tuple) -> None: channel_tuple: reference to the channel tuple """ raise NotImplementedError() + + +class AWGChannel(_BaseAWGChannel, FeatureAble[AWGChannelFeature], 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) diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index e2cbef14a..f03059e08 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -1,32 +1,46 @@ -import typing -from typing import TypeVar, Generic, Dict, Callable -import warnings +from types import MappingProxyType +from typing import Callable, Generic, Mapping, Optional, Type, TypeVar from abc import ABC -from copy import copy + + +__all__ = ["Feature", "FeatureAble"] class Feature(ABC): """ Base class for features of `FeatureAble`s. """ - pass + 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], ABC): """ - Base class for all classes that are able to add features. The features are saved in a dictonary and the methods - can be called with __getitem__. + 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: typing.Type[FeatureType]) -> FeatureType: - return self._features[feature_type] + def __getitem__(self, feature_type: Type[GetItemFeatureType]) -> GetItemFeatureType: + 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: """ @@ -35,12 +49,46 @@ def add_feature(self, feature: FeatureType) -> None: Args: feature: A certain feature which functions should be added to the dictionary _features """ - if not isinstance(feature, Feature): - raise TypeError("Invalid type for feature") - - self._features[type(feature)] = feature + 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 @property - def features(self) -> Dict[FeatureType, Callable]: + def features(self) -> Mapping[FeatureType, Callable]: """Returns the dictionary with all features of a FeatureAble""" - return copy(self._features) + 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) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py new file mode 100644 index 000000000..9cc280364 --- /dev/null +++ b/qupulse/hardware/awgs/features.py @@ -0,0 +1,138 @@ +from abc import ABC, abstractmethod +from typing import Callable, Optional, Set, Tuple + +from qupulse._program._loop import Loop +from qupulse.hardware.awgs.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature +from qupulse.utils.types import ChannelID + + +######################################################################################################################## +# device features +######################################################################################################################## + +class ChannelSynchronization(AWGDeviceFeature, ABC): + @abstractmethod + def synchronize_channels(self, group_size: int) -> None: + """ + Synchronize in groups of `group_size` channels. Groups of synchronized channels will be provided as + AWGChannelTuples. + + Args: + group_size: Number of channels per channel tuple + """ + raise NotImplementedError() + + +######################################################################################################################## +# channel tuple features +######################################################################################################################## + +class ProgramManagement(AWGChannelTupleFeature, ABC): + @abstractmethod + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], + voltage_transformation: Tuple[Optional[Callable], ...], + force: bool=False) -> None: + """Upload a program to the AWG. + + Physically uploads all waveforms required by the program - excluding those already present - + to the device and sets up playback sequences accordingly. + This method should be cheap for program already on the device and can therefore be used + for syncing. Programs that are uploaded should be fast(~1 sec) to arm. + + Args: + name: A name for the program on the AWG. + program: The program (a sequence of instructions) to upload. + channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel + markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel + voltage_transformation: transformations applied to the waveforms extracted rom the program. Position + in the list corresponds to the AWG channel + force: If a different sequence is already present with the same name, it is + overwritten if force is set to True. (default = False) + """ + raise NotImplementedError() + + @abstractmethod + def remove(self, name: str) -> None: + """Remove a program from the AWG. + + Also discards all waveforms referenced only by the program identified by name. + + Args: + name: The name of the program to remove. + """ + raise NotImplementedError() + + @abstractmethod + def clear(self) -> None: + """Removes all programs and waveforms from the AWG. + + Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! + """ + raise NotImplementedError() + + @abstractmethod + def arm(self, name: Optional[str]) -> None: + """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current + program.""" + raise NotImplementedError() + + @property + @abstractmethod + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + raise NotImplementedError() + + +######################################################################################################################## +# channel features +######################################################################################################################## + +class AmplitudeOffsetHandling: + IGNORE_OFFSET = 'ignore_offset' # Offset is ignored. + CONSIDER_OFFSET = 'consider_offset' # Offset is discounted from the waveforms. + + _valid = (IGNORE_OFFSET, CONSIDER_OFFSET) + + +class OffsetAmplitude(AWGChannelFeature): + @property + @abstractmethod + def offset(self) -> float: + """Get offset of AWG channel""" + raise NotImplementedError() + + @offset.setter + @abstractmethod + def offset(self, offset: float) -> None: + """Set offset for AWG channel""" + raise NotImplementedError() + + @property + @abstractmethod + def amplitude(self) -> float: + """Get amplitude of AWG channel""" + raise NotImplementedError() + + @amplitude.setter + @abstractmethod + def amplitude(self, amplitude: float) -> None: + """Set amplitude for AWG channel""" + raise NotImplementedError() + + @property + @abstractmethod + def amplitude_offset_handling(self) -> str: + """Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and + offset settings are constant or if these should be optimized by the driver""" + raise NotImplementedError() + + @amplitude_offset_handling.setter + @abstractmethod + def amplitude_offset_handling(self, amp_offs_handling: str) -> None: + """ + amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` + """ + raise NotImplementedError() diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 2bde9801b..5c7fd2599 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -1,65 +1,98 @@ +from typing import Callable, Collection, Iterable, List, Optional, Set, Tuple +import unittest import warnings -warnings.simplefilter("ignore", UserWarning) +from qupulse import ChannelID +from qupulse._program._loop import Loop +from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel +from qupulse.hardware.awgs.features import ChannelSynchronization, ProgramManagement, OffsetAmplitude, \ + AmplitudeOffsetHandling -import unittest +warnings.simplefilter("ignore", UserWarning) -from typing import Callable, Collection, Iterable, List, Optional -from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGFeature, \ - AWGChannelFeature, AWGChannelTupleFeature ######################################################################################################################## # Example Features ######################################################################################################################## -class SynchronizeChannelsFeature(AWGFeature): - def __init__(self, sync_func: Callable[[int], None]): - """Storing the callable, to call it if needed below""" +class TestSynchronizeChannelsFeature(ChannelSynchronization): + def __init__(self, device: "TestAWGDevice"): super().__init__() - self._sync_func = sync_func + self._parent = device def synchronize_channels(self, group_size: int) -> None: - """Forwarding call to callable object, which was provided threw __init__""" - self._sync_func(group_size) + """Forwarding call to TestAWGDevice""" + self._parent.synchronize_channels(group_size) -class ChannelTupleNameFeature(AWGChannelTupleFeature): - def __init__(self, name_get: Callable[[], str]): - """Storing the callable, to call it if needed below""" +class TestOffsetAmplitudeFeature(OffsetAmplitude): + def __init__(self, channel: "TestAWGChannel"): super().__init__() - self._get_name = name_get + self._parent = channel - def get_name(self) -> str: - """Forwarding call to callable object, which was provided threw __init__""" - return self._get_name() + @property + def offset(self) -> float: + """Get offset of TestAWGChannel""" + return self._parent._offset + @offset.setter + def offset(self, offset: float) -> None: + """Set offset of TestAWGChannel""" + self._parent._offset = offset -class ChannelOffsetAmplitudeFeature(AWGChannelFeature): - def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], - amp_get: Callable[[], float], amp_set: Callable[[float], None]): - """Storing all callables, to call them if needed below""" - super().__init__() - self._get_offset = offset_get - self._set_offset = offset_set - self._get_amp = amp_get - self._set_amp = amp_set + @property + def amplitude(self) -> float: + """Get amplitude of TestAWGChannel""" + return self._parent._amplitude - def get_offset(self) -> float: - """Forwarding call to callable object, which was provided threw __init__""" - return self._get_offset() + @amplitude.setter + def amplitude(self, amplitude: float) -> None: + """Set amplitude of TestAWGChannel""" + self._parent._amplitude = amplitude + + @property + def amplitude_offset_handling(self) -> str: + """Get amplitude-offset-handling of TestAWGChannel""" + return self._parent._ampl_offs_handling + + @amplitude_offset_handling.setter + def amplitude_offset_handling(self, ampl_offs_handling: float) -> None: + """Set amplitude-offset-handling of TestAWGChannel""" + self._parent._ampl_offs_handling = ampl_offs_handling - def set_offset(self, offset: float) -> None: - """Forwarding call to callable object, which was provided threw __init__""" - self._set_offset(offset) - def get_amplitude(self) -> float: - """Forwarding call to callable object, which was provided threw __init__""" - return self._get_amp() +class TestProgramManagementFeature(ProgramManagement): + def __init__(self): + super().__init__() + self._programs = {} + self._armed_program = None + + def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], + force: bool = False) -> None: + if name in self._programs: + raise KeyError("Program with name \"{}\" is already on the instrument.".format(name)) + self._programs[name] = program + + def remove(self, name: str) -> None: + if self._armed_program == name: + raise RuntimeError("Cannot remove program, when it is armed.") + if name not in self._programs: + raise KeyError("Unknown program: {}".format(name)) + del self._programs[name] + + def clear(self) -> None: + if self._armed_program is not None: + raise RuntimeError("Cannot clear programs, with an armed program.") + self._programs.clear() + + def arm(self, name: Optional[str]) -> None: + self._armed_program = name - def set_amplitude(self, amplitude: float) -> None: - """Forwarding call to callable object, which was provided threw __init__""" - self._set_amp(amplitude) + @property + def programs(self) -> Set[str]: + return set(self._programs.keys()) ######################################################################################################################## @@ -72,14 +105,14 @@ def __init__(self, name: str): # Add feature to this object (self) # During this call, the function of the feature is dynamically added to this object - self.add_feature(SynchronizeChannelsFeature(self._sync_chans)) + self.add_feature(TestSynchronizeChannelsFeature(self)) self._channels = [TestAWGChannel(i, self) for i in range(8)] # 8 channels self._channel_tuples = [] # Call the feature function, with the feature's signature - self[SynchronizeChannelsFeature].synchronize_channels( - 2) # default channel synchronization with a group size of 2 + # Default channel synchronization with a group size of 2 + self[ChannelSynchronization].synchronize_channels(2) def cleanup(self) -> None: """This will be called automatically in __del__""" @@ -90,12 +123,16 @@ def cleanup(self) -> None: def channels(self) -> Collection["TestAWGChannel"]: return self._channels + @property + def marker_channels(self) -> Collection[AWGMarkerChannel]: + return [] + @property def channel_tuples(self) -> Collection["TestAWGChannelTuple"]: return self._channel_tuples - def _sync_chans(self, group_size: int) -> None: - """Implementation of the feature's function""" + def synchronize_channels(self, group_size: int) -> None: + """Implementation of the feature's , but you can also call it directly""" if group_size not in [2, 4, 8]: # Allowed group sizes raise ValueError("Invalid group size for channel synchronization") @@ -120,11 +157,11 @@ def __init__(self, idn: int, device: TestAWGDevice, channels: Iterable["TestAWGC # Add feature to this object (self) # During this call, the function of the feature is dynamically added to this object - self.add_feature(ChannelTupleNameFeature(self._get_name)) + self.add_feature(TestProgramManagementFeature()) self._device = device self._channels = tuple(channels) - self.sample_rate = 12.456 # default value + self._sample_rate = 12.456 # default value @property def sample_rate(self) -> float: @@ -142,10 +179,9 @@ def device(self) -> TestAWGDevice: def channels(self) -> Collection["TestAWGChannel"]: return self._channels - # Feature functions - def _get_name(self) -> str: - """Implementation of the feature's function""" - return chr(ord('A') + self.idn) # 0 -> 'A', 1 -> 'B', 2 -> 'C', ... + @property + def marker_channels(self) -> Collection[AWGMarkerChannel]: + return [] class TestAWGChannel(AWGChannel): @@ -154,15 +190,13 @@ def __init__(self, idn: int, device: TestAWGDevice): # Add feature to this object (self) # During this call, all functions of the feature are dynamically added to this object - self.add_feature(ChannelOffsetAmplitudeFeature(self._get_offs, - self._set_offs, - self._get_ampl, - self._set_ampl)) + self.add_feature(TestOffsetAmplitudeFeature(self)) self._device = device self._channel_tuple: Optional[TestAWGChannelTuple] = None self._offset = 0.0 self._amplitude = 5.0 + self._ampl_offs_handling = AmplitudeOffsetHandling.IGNORE_OFFSET @property def device(self) -> TestAWGDevice: @@ -175,54 +209,36 @@ def channel_tuple(self) -> Optional[TestAWGChannelTuple]: def _set_channel_tuple(self, channel_tuple: TestAWGChannelTuple) -> None: self._channel_tuple = channel_tuple - def _get_offs(self) -> float: - """Implementation of the feature's function""" - return self._offset - - def _set_offs(self, offset: float) -> None: - """Implementation of the feature's function""" - self._offset = offset - - def _get_ampl(self) -> float: - """Implementation of the feature's function""" - return self._amplitude - - def _set_ampl(self, amplitude: float) -> None: - """Implementation of the feature's function""" - self._amplitude = amplitude - class TestBaseClasses(unittest.TestCase): def setUp(self): self.device_name = "My device" self.device = TestAWGDevice(self.device_name) - def test_Device(self): + def test_device(self): self.assertEqual(self.device.name, self.device_name, "Invalid name for device") self.assertEqual(len(self.device.channels), 8, "Invalid number of channels") + self.assertEqual(len(self.device.marker_channels), 0, "Invalid number of marker channels") self.assertEqual(len(self.device.channel_tuples), 4, "Invalid default channel tuples for device") - def test_channel(self): + def test_channels(self): for i, channel in enumerate(self.device.channels): self.assertEqual(channel.idn, i), "Invalid channel id" - self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_offset(), 0, - f"Invalid default offset for channel {i}") - self.assertEqual(channel[ - ChannelOffsetAmplitudeFeature].get_amplitude(), 5.0, + self.assertEqual(channel[OffsetAmplitude].offset, 0, f"Invalid default offset for channel {i}") + self.assertEqual(channel[OffsetAmplitude].amplitude, 5.0, f"Invalid default amplitude for channel {i}") offs = -0.1 * i ampl = 0.5 + 3 * i - channel[ChannelOffsetAmplitudeFeature].set_offset(offs) - channel[ChannelOffsetAmplitudeFeature].set_amplitude(ampl) - self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_offset(), offs, - f"Invalid offset for channel {i}") - self.assertEqual(channel[ChannelOffsetAmplitudeFeature].get_amplitude(), ampl, - f"Invalid amplitude for channel {i}") - - def test_channel_tupels(self): + channel[OffsetAmplitude].offset = offs + channel[OffsetAmplitude].amplitude = ampl + + self.assertEqual(channel[OffsetAmplitude].offset, offs, f"Invalid offset for channel {i}") + self.assertEqual(channel[OffsetAmplitude].amplitude, ampl, f"Invalid amplitude for channel {i}") + + def test_channel_tuples(self): for group_size in [2, 4, 8]: - self.device[SynchronizeChannelsFeature].synchronize_channels(group_size) + self.device[ChannelSynchronization].synchronize_channels(group_size) self.assertEqual(len(self.device.channel_tuples), 8 // group_size, "Invalid number of channel tuples") @@ -234,10 +250,18 @@ def test_channel_tupels(self): f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}") self.assertEqual(len(self.device.channel_tuples), 1, "Invalid number of channel tuples") - def test_error_thrown(self): - with self.assertRaises(ValueError) as cm: - self.device[SynchronizeChannelsFeature].synchronize_channels(3) - self.assertEqual(ValueError, cm.exception.__class__, "Missing error for invalid group size") + def test_error_handling(self): + with self.assertRaises(ValueError): + self.device[ChannelSynchronization].synchronize_channels(3) + + with self.assertRaises(KeyError): + self.device.add_feature(TestSynchronizeChannelsFeature(self.device)) + + with self.assertRaises(TypeError): + self.device.add_feature(TestProgramManagementFeature()) + + with self.assertRaises(TypeError): + self.device.features[ChannelSynchronization] = None if __name__ == '__main__': From b8ecc0aaa2edd16908c4d80cd6803b2498d38949 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 16:09:57 +0100 Subject: [PATCH 037/107] Removed type hint in awg_base_test --- tests/hardware/awg_base_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 5c7fd2599..65022ed43 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -137,7 +137,7 @@ def synchronize_channels(self, group_size: int) -> None: raise ValueError("Invalid group size for channel synchronization") self._channel_tuples.clear() - tmp_channel_tuples: List[List["TestAWGChannel"]] = [[] for i in range(len(self._channels) // group_size)] + tmp_channel_tuples = [[] for i in range(len(self._channels) // group_size)] # Preparing the channel structure for i, channel in enumerate(self._channels): From 086a3b37a0b6b8e2fa64429b4d9ed3194913e526 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 16:14:48 +0100 Subject: [PATCH 038/107] Removed type hint in awg_base_test --- tests/hardware/awg_base_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 65022ed43..6ae7c0f5a 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -193,7 +193,7 @@ def __init__(self, idn: int, device: TestAWGDevice): self.add_feature(TestOffsetAmplitudeFeature(self)) self._device = device - self._channel_tuple: Optional[TestAWGChannelTuple] = None + self._channel_tuple = None self._offset = 0.0 self._amplitude = 5.0 self._ampl_offs_handling = AmplitudeOffsetHandling.IGNORE_OFFSET From 3a0fbf0b4910e207d6a62f26b057cdf972b44115 Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 16:20:27 +0100 Subject: [PATCH 039/107] Removed f-strings to keep compatibility to Python 3.5 --- tests/hardware/awg_base_tests.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 6ae7c0f5a..40fb2ef76 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -224,17 +224,17 @@ def test_device(self): def test_channels(self): for i, channel in enumerate(self.device.channels): self.assertEqual(channel.idn, i), "Invalid channel id" - self.assertEqual(channel[OffsetAmplitude].offset, 0, f"Invalid default offset for channel {i}") + self.assertEqual(channel[OffsetAmplitude].offset, 0, "Invalid default offset for channel {}".format(i)) self.assertEqual(channel[OffsetAmplitude].amplitude, 5.0, - f"Invalid default amplitude for channel {i}") + "Invalid default amplitude for channel {}".format(i)) offs = -0.1 * i ampl = 0.5 + 3 * i channel[OffsetAmplitude].offset = offs channel[OffsetAmplitude].amplitude = ampl - self.assertEqual(channel[OffsetAmplitude].offset, offs, f"Invalid offset for channel {i}") - self.assertEqual(channel[OffsetAmplitude].amplitude, ampl, f"Invalid amplitude for channel {i}") + self.assertEqual(channel[OffsetAmplitude].offset, offs, "Invalid offset for channel {}".format(i)) + self.assertEqual(channel[OffsetAmplitude].amplitude, ampl, "Invalid amplitude for channel {}".format(i)) def test_channel_tuples(self): for group_size in [2, 4, 8]: @@ -245,9 +245,10 @@ def test_channel_tuples(self): # Check if channels and channel tuples are connected right for i, channel in enumerate(self.device.channels): self.assertEqual(channel.channel_tuple.idn, i // group_size, - f"Invalid channel tuple {channel.channel_tuple.idn} for channel {i}") + "Invalid channel tuple {} for channel {}".format(channel.channel_tuple.idn, i)) self.assertTrue(channel in channel.channel_tuple.channels, - f"Channel {i} not in its parent channel tuple {channel.channel_tuple.idn}") + "Channel {} not in its parent channel tuple {}".format(i, channel.channel_tuple.idn)) + self.assertEqual(len(self.device.channel_tuples), 1, "Invalid number of channel tuples") def test_error_handling(self): From 8547b96b37c913086eb8bebc107056408a216e5f Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 16:31:44 +0100 Subject: [PATCH 040/107] Using qupulse.utils.types.Collection instead of typing.Collection to keep py3.5-compatibility --- qupulse/hardware/awgs/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 2325117a7..dd73e7bca 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod -from typing import Collection, Optional +from typing import Optional from qupulse.hardware.awgs.base_features import Feature, FeatureAble +from qupulse.utils.types import Collection __all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature", From 7833c50c0bcf4092399a45187f3e13afc6a4fe7c Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Wed, 5 Feb 2020 16:41:38 +0100 Subject: [PATCH 041/107] Using qupulse.utils.types.Collection instead of typing.Collection to keep py3.5-compatibility --- tests/hardware/awg_base_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 40fb2ef76..aaceb6a96 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -1,4 +1,4 @@ -from typing import Callable, Collection, Iterable, List, Optional, Set, Tuple +from typing import Callable, Iterable, List, Optional, Set, Tuple import unittest import warnings @@ -7,6 +7,7 @@ from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel from qupulse.hardware.awgs.features import ChannelSynchronization, ProgramManagement, OffsetAmplitude, \ AmplitudeOffsetHandling +from qupulse.utils.types import Collection warnings.simplefilter("ignore", UserWarning) From a1c5f3ae0372893793c94b980bfff41c343e948d Mon Sep 17 00:00:00 2001 From: "Lukas.Lankes" <l.lankes@fz-juelich.de> Date: Thu, 6 Feb 2020 07:47:39 +0100 Subject: [PATCH 042/107] Replaced imports of awgs.base with awgs.old_base --- qupulse/_program/seqc.py | 3 ++- qupulse/hardware/awgs/zihdawg.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/qupulse/_program/seqc.py b/qupulse/_program/seqc.py index 7aa06d06c..f8ee5673b 100644 --- a/qupulse/_program/seqc.py +++ b/qupulse/_program/seqc.py @@ -33,8 +33,9 @@ from qupulse.utils import replace_multiple from qupulse._program.waveforms import Waveform from qupulse._program._loop import Loop + from qupulse._program.volatile import VolatileRepetitionCount, VolatileProperty -from qupulse.hardware.awgs.base import ProgramEntry +from qupulse.hardware.awgs.old_base import ProgramEntry try: import zhinst.utils diff --git a/qupulse/hardware/awgs/zihdawg.py b/qupulse/hardware/awgs/zihdawg.py index 681b6c7e3..a7b1968f3 100644 --- a/qupulse/hardware/awgs/zihdawg.py +++ b/qupulse/hardware/awgs/zihdawg.py @@ -19,10 +19,11 @@ from qupulse.utils.types import ChannelID, TimeType, time_from_float from qupulse._program._loop import Loop, make_compatible from qupulse._program.seqc import HDAWGProgramManager, UserRegister -from qupulse.hardware.awgs.base import AWG, ChannelNotFoundException, AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs.old_base import AWG, ChannelNotFoundException, AWGAmplitudeOffsetHandling from qupulse.pulses.parameters import ConstantParameter + def valid_channel(function_object): """Check if channel is a valid AWG channels. Expects channel to be 2nd argument after self.""" @functools.wraps(function_object) From 9a445f13f3e627ed7adfc935fe9695a5c8314140 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 19 Feb 2020 15:49:26 +0100 Subject: [PATCH 043/107] Basic communication works - but Tabor driver not yet completely rewritten/implemented --- .gitignore | 5 +- qupulse/hardware/awgs/__init__.py | 2 +- qupulse/hardware/awgs/features/__init__.py | 0 .../awgs/features/amplitude_offset_feature.py | 30 - .../awgs/features/device_mirror_feature.py | 22 - qupulse/hardware/awgs/old_tabor.py | 35 +- qupulse/hardware/awgs/tabor.py | 1269 ++++------------- tests/hardware/tabor_dummy_based_tests.py | 2 +- tests/hardware/tabor_exex_test.py | 6 +- tests/hardware/tabor_simulator_based_tests.py | 12 +- 10 files changed, 299 insertions(+), 1084 deletions(-) delete mode 100644 qupulse/hardware/awgs/features/__init__.py delete mode 100644 qupulse/hardware/awgs/features/amplitude_offset_feature.py delete mode 100644 qupulse/hardware/awgs/features/device_mirror_feature.py diff --git a/.gitignore b/.gitignore index 3cf04d7a2..d8ce04dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ dist/* doc/source/examples/.ipynb_checkpoints/* **.asv *.orig -.idea/ \ No newline at end of file +.idea/ +.mypy_cache/* +.gitignore +tests/hardware/WX2184C.exe \ No newline at end of file diff --git a/qupulse/hardware/awgs/__init__.py b/qupulse/hardware/awgs/__init__.py index d3566be14..028f34cd4 100644 --- a/qupulse/hardware/awgs/__init__.py +++ b/qupulse/hardware/awgs/__init__.py @@ -4,7 +4,7 @@ __all__ = ["install_requirements"] try: - from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair + from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborChannelTuple __all__.extend(["TaborAWGRepresentation", "TaborChannelPair"]) except ImportError: pass diff --git a/qupulse/hardware/awgs/features/__init__.py b/qupulse/hardware/awgs/features/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/qupulse/hardware/awgs/features/amplitude_offset_feature.py b/qupulse/hardware/awgs/features/amplitude_offset_feature.py deleted file mode 100644 index 647be5426..000000000 --- a/qupulse/hardware/awgs/features/amplitude_offset_feature.py +++ /dev/null @@ -1,30 +0,0 @@ -from collections import Callable - -from qupulse.hardware.awgs.base import AWGChannelFeature - - -class ChannelAmplitudeOffsetFeature(AWGChannelFeature): - def __init__(self, offset_get: Callable[[], float], offset_set: Callable[[float], None], - amp_get: Callable[[], float], amp_set: Callable[[float], None]): - """Storing all callables, to call them if needed below""" - super().__init__() - self._get_offset = offset_get - self._set_offset = offset_set - self._get_amp = amp_get - self._set_amp = amp_set - - def get_offset(self) -> float: - """Forwarding call to callable object, which was provided threw __init__""" - return self._get_offset() - - def set_offset(self, offset: float) -> None: - """Forwarding call to callable object, which was provided threw __init__""" - self._set_offset(offset) - - def get_amplitude(self) -> float: - """Forwarding call to callable object, which was provided threw __init__""" - return self._get_amp() - - def set_amplitude(self, amplitude: float) -> None: - """Forwarding call to callable object, which was provided threw __init__""" - self._set_amp(amplitude) diff --git a/qupulse/hardware/awgs/features/device_mirror_feature.py b/qupulse/hardware/awgs/features/device_mirror_feature.py deleted file mode 100644 index fac7315f5..000000000 --- a/qupulse/hardware/awgs/features/device_mirror_feature.py +++ /dev/null @@ -1,22 +0,0 @@ -from collections import Callable - -import typing - -from qupulse.hardware.awgs.base import AWGFeature - - -class DeviceMirrorFeature(AWGFeature): - def __init__(self, main_instrument: Callable, mirrored_instruments: Callable, - all_devices: Callable): - self._main_instrument = main_instrument - self._mirrored_instruments = mirrored_instruments - self._all_devices = all_devices - - def main_instrument(self) -> object: - return self.main_instrument() - - def mirrored_instruments(self) -> typing.Any: - return self.mirrored_instruments() - - def all_devices(self) -> typing.Any: - return self.all_devices() diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index 7c0d16587..d8eb6ccf3 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -24,7 +24,7 @@ assert(sys.byteorder == 'little') -__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] +__all__ = ['TaborDevice', 'TaborChannelTuple'] class TaborSegment: @@ -367,7 +367,7 @@ def waveform_mode(self) -> str: return self.__waveform_mode -class TaborAWGRepresentation: +class TaborDevice: def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ :param instr_addr: Instrument address that is forwarded to teawag @@ -389,15 +389,15 @@ def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, re self.initialize() - self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') - self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + self._channel_pair_AB = TaborChannelTuple(self, (1, 2), str(instr_addr) + '_AB') + self._channel_pair_CD = TaborChannelTuple(self, (3, 4), str(instr_addr) + '_CD') @property - def channel_pair_AB(self) -> 'TaborChannelPair': + def channel_pair_AB(self) -> 'TaborChannelTuple': return self._channel_pair_AB @property - def channel_pair_CD(self) -> 'TaborChannelPair': + def channel_pair_CD(self) -> 'TaborChannelTuple': return self._channel_pair_CD @property @@ -608,11 +608,12 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: ('program', TaborProgram)]) -def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], - Any]: +def with_configuration_guard(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[[ + 'TaborChannelTuple'], + Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" @functools.wraps(function_object) - def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: if channel_pair._configuration_guard_count == 0: channel_pair._enter_config_mode() channel_pair._configuration_guard_count += 1 @@ -627,10 +628,10 @@ def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: return guarding_method -def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: +def with_select(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[['TaborChannelTuple'], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @functools.wraps(function_object) - def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + def selector(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: channel_pair.select() return function_object(channel_pair, *args, **kwargs) @@ -764,8 +765,8 @@ def from_builtin(cls, data: dict) -> 'PlottableProgram': return cls(waveforms, data['seq_tables'], data['adv_seq_table']) -class TaborChannelPair(AWG): - def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): +class TaborChannelTuple(AWG): + def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identifier: str): super().__init__(identifier) self._device = weakref.ref(tabor_device) @@ -805,7 +806,7 @@ def total_capacity(self) -> int: return int(self.device.dev_properties['max_arb_mem']) // 2 @property - def device(self) -> TaborAWGRepresentation: + def device(self) -> TaborDevice: return self._device() def free_program(self, name: str) -> TaborProgramMemory: @@ -1321,12 +1322,12 @@ class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" - def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): + def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): super().__init__(*args) self.device = device def reset_device(self): - if isinstance(self.device, TaborAWGRepresentation): + if isinstance(self.device, TaborDevice): self.device.reset() - elif isinstance(self.device, TaborChannelPair): + elif isinstance(self.device, TaborChannelTuple): self.device.clear() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 7878cb712..d3ed4ef9b 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,13 +1,16 @@ -import fractions import functools -import weakref - -import itertools -import operator -from typing import List, Tuple, Set, NamedTuple, Callable, Optional, Any, Sequence, cast, Generator, Union, Dict, \ - Iterable -from enum import Enum +from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple from collections import OrderedDict +import numpy as np +from qupulse import ChannelID +from qupulse._program._loop import Loop + +from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ + ProgramManagement +from qupulse.hardware.util import voltage_to_uint16, make_combined_wave +from qupulse.utils.types import Collection +from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel +from typing import Sequence # Provided by Tabor electronics for python 2.7 # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech @@ -36,355 +39,19 @@ ######################################################################################################################## class TaborSegment: - """Represents one segment of two channels on the device. Convenience class.""" - - __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') - - def __init__(self, - ch_a: Optional[np.ndarray], - ch_b: Optional[np.ndarray], - marker_a: Optional[np.ndarray], - marker_b: Optional[np.ndarray]): - if ch_a is None and ch_b is None: - raise TaborException('Empty TaborSegments are not allowed') - if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): - raise TaborException('Channel entries to have to have the same length') - - self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) - self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) - - self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) - self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) - - if marker_a is not None and len(marker_a) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') - if marker_b is not None and len(marker_b) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') - - @classmethod - def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': - data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) - data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() - return cls.from_binary_data(data_a, data_b) - - @classmethod - def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': - ch_b = data_b - - channel_mask = np.uint16(2 ** 14 - 1) - ch_a = np.bitwise_and(data_a, channel_mask) - - marker_a_mask = np.uint16(2 ** 14) - marker_b_mask = np.uint16(2 ** 15) - marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) - - marker_a = np.bitwise_and(marker_data, marker_a_mask) - marker_b = np.bitwise_and(marker_data, marker_b_mask) - - return cls(ch_a=ch_a, - ch_b=ch_b, - marker_a=marker_a, - marker_b=marker_b) - - # TODO: Marker ueberarbeiten - def __hash__(self) -> int: - return hash(tuple(0 if data is None else bytes(data) - for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) - - def __eq__(self, other: 'TaborSegment'): - def compare_markers(marker_1, marker_2): - if marker_1 is None: - if marker_2 is None: - return True - else: - return not np.any(marker_2) - - elif marker_2 is None: - return not np.any(marker_1) - - else: - return np.array_equal(marker_1, marker_2) - - return (np.array_equal(self.ch_a, other.ch_a) and - np.array_equal(self.ch_b, other.ch_b) and - compare_markers(self.marker_a, other.marker_a) and - compare_markers(self.marker_b, other.marker_b)) - - @property - def data_a(self) -> np.ndarray: - """channel_data and marker data""" - if self.marker_a is None and self.marker_b is None: - return self.ch_a - - if self.ch_a is None: - raise NotImplementedError('What data should be used in a?') - - # copy channel information - data = np.array(self.ch_a) - - if self.marker_a is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) - - if self.marker_b is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) - - return data - - @property - def data_b(self) -> np.ndarray: - """channel_data and marker data""" - return self.ch_b - - @property - def num_points(self) -> int: - return len(self.ch_b) if self.ch_a is None else len(self.ch_a) - - def get_as_binary(self) -> np.ndarray: - assert not (self.ch_a is None or self.ch_b is None) - return make_combined_wave([self]) - - -class TaborSequencing(Enum): - SINGLE = 1 - ADVANCED = 2 - + pass # TODO: to implement class TaborProgram: - def __init__(self, - program: Loop, - device_properties, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]]): - if len(channels) != device_properties['chan_per_part']: - raise TaborException( - 'TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) - if len(markers) != device_properties['chan_per_part']: - raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) - channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker - for marker in - markers if - marker is not None) - self._program = program - - self.__waveform_mode = None - self._channels = tuple(channels) - self._markers = tuple(markers) - self.__used_channels = channel_set - self.__device_properties = device_properties - - self._waveforms = [] # type: List[MultiChannelWaveform] - self._sequencer_tables = [] - self._advanced_sequencer_table = [] + pass # TODO: to implement - if self.program.repetition_count > 1: - self.program.encapsulate() - if self.program.depth() > 1: - self.setup_advanced_sequence_mode() - self.__waveform_mode = TaborSequencing.ADVANCED - else: - if self.program.depth() == 0: - self.program.encapsulate() - self.setup_single_sequence_mode() - self.__waveform_mode = TaborSequencing.SINGLE - - @property - def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._markers - - @property - def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._channels - - def sampled_segments(self, - sample_rate: fractions.Fraction, - voltage_amplitude: Tuple[float, float], - voltage_offset: Tuple[float, float], - voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], - Sequence[int]]: - sample_rate = fractions.Fraction(sample_rate, 10 ** 9) - - time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) - - if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): - raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') - - def voltage_to_data(waveform, time, channel): - if self._channels[channel]: - return voltage_to_uint16( - voltage_transformation[channel]( - waveform.get_sampled(channel=self._channels[channel], - sample_times=time)), - voltage_amplitude[channel], - voltage_offset[channel], - resolution=14) - else: - return np.full_like(time, 8192, dtype=np.uint16) - - def get_marker_data(waveform: MultiChannelWaveform, time, marker): - if self._markers[marker]: - markerID = self._markers[marker] - return waveform.get_sampled(channel=markerID, sample_times=time) != 0 - else: - return np.full_like(time, False, dtype=bool) - - segments = np.empty_like(self._waveforms, dtype=TaborSegment) - for i, waveform in enumerate(self._waveforms): - t = time_array[:segment_lengths[i]] - marker_time = t[::2] - segment_a = voltage_to_data(waveform, t, 0) - segment_b = voltage_to_data(waveform, t, 1) - assert (len(segment_a) == len(t)) - assert (len(segment_b) == len(t)) - marker_a = get_marker_data(waveform, marker_time, 0) - marker_b = get_marker_data(waveform, marker_time, 1) - segments[i] = TaborSegment(ch_a=segment_a, - ch_b=segment_b, - marker_a=marker_a, - marker_b=marker_b) - return segments, segment_lengths - - def setup_single_sequence_mode(self) -> None: - assert self.program.depth() == 1 - - sequencer_table = [] - waveforms = OrderedDict() - - for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in self.program): - if waveform in waveforms: - waveform_index = waveforms[waveform] - else: - waveform_index = len(waveforms) - waveforms[waveform] = waveform_index - sequencer_table.append((repetition_count, waveform_index, 0)) - - self._waveforms = tuple(waveforms.keys()) - self._sequencer_tables = [sequencer_table] - self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] - - def setup_advanced_sequence_mode(self) -> None: - assert self.program.depth() > 1 - assert self.program.repetition_count == 1 - - self.program.flatten_and_balance(2) - - min_seq_len = self.__device_properties['min_seq_len'] - max_seq_len = self.__device_properties['max_seq_len'] - - def check_merge_with_next(program, n): - if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and - len(program[n]) + len(program[n + 1]) < max_seq_len): - program[n][len(program[n]):] = program[n + 1][:] - program[n + 1:n + 2] = [] - return True - return False - - def check_partial_unroll(program, n): - st = program[n] - if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: - if sum(entry.repetition_count for entry in st) < min_seq_len: - st.unroll_children() - while len(st) < min_seq_len: - st.split_one_child() - return True - return False - - i = 0 - while i < len(self.program): - self.program[i].assert_tree_integrity() - if len(self.program[i]) > max_seq_len: - raise TaborException('The algorithm is not smart enough to make sequence tables shorter') - elif len(self.program[i]) < min_seq_len: - assert self.program[i].repetition_count > 0 - if self.program[i].repetition_count == 1: - # check if merging with neighbour is possible - if i > 0 and check_merge_with_next(self.program, i - 1): - pass - elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): - pass - - # check if (partial) unrolling is possible - elif check_partial_unroll(self.program, i): - i += 1 - - # check if sequence table can be extended by unrolling a neighbor - elif (i > 0 - and self.program[i - 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): - self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] - self.program[i - 1].repetition_count -= 1 - - elif (i + 1 < len(self.program) - and self.program[i + 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): - self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] - self.program[i + 1].repetition_count -= 1 - - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - elif check_partial_unroll(self.program, i): - i += 1 - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - else: - i += 1 - - for sequence_table in self.program: - assert len(sequence_table) >= self.__device_properties['min_seq_len'] - assert len(sequence_table) <= self.__device_properties['max_seq_len'] - - advanced_sequencer_table = [] - sequencer_tables = [] - waveforms = OrderedDict() - for sequencer_table_loop in self.program: - current_sequencer_table = [] - for waveform, repetition_count in ( - (waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in sequencer_table_loop): - if waveform in waveforms: - wf_index = waveforms[waveform] - else: - wf_index = len(waveforms) - waveforms[waveform] = wf_index - current_sequencer_table.append((repetition_count, wf_index, 0)) - - if current_sequencer_table in sequencer_tables: - sequence_no = sequencer_tables.index(current_sequencer_table) + 1 - else: - sequence_no = len(sequencer_tables) + 1 - sequencer_tables.append(current_sequencer_table) - - advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) - - self._advanced_sequencer_table = advanced_sequencer_table - self._sequencer_tables = sequencer_tables - self._waveforms = tuple(waveforms.keys()) - - @property - def program(self) -> Loop: - return self._program - - def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: - return self._sequencer_tables - - def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: - """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" - return self._advanced_sequencer_table - - @property - def waveform_mode(self) -> str: - return self.__waveform_mode - - -def with_configuration_guard(function_object: Callable[['AWGChannelTupleTabor', Any], Any]) -> Callable[ - ['AWGChannelTupleTabor'], Any]: +# TODO: How does this work? +def with_configuration_guard(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[ + ['TaborChannelTuple'], Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" @functools.wraps(function_object) - def guarding_method(channel_pair: 'AWGChannelTupleTabor', *args, **kwargs) -> Any: + def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: if channel_pair._configuration_guard_count == 0: channel_pair._enter_config_mode() channel_pair._configuration_guard_count += 1 @@ -399,182 +66,67 @@ def guarding_method(channel_pair: 'AWGChannelTupleTabor', *args, **kwargs) -> An return guarding_method -def with_select(function_object: Callable[['AWGChannelTupleTabor', Any], Any]) -> Callable[['AWGChannelTupleTabor'], Any]: +# TODO: How does this work? +def with_select(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[['TaborChannelTuple'], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @functools.wraps(function_object) - def selector(channel_pair: 'AWGChannelTupleTabor', *args, **kwargs) -> Any: + def selector(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: channel_pair.select() return function_object(channel_pair, *args, **kwargs) return selector -class PlottableProgram: - TableEntry = NamedTuple('TableEntry', [('repetition_count', int), - ('element_number', int), - ('jump_flag', int)]) - - def __init__(self, - segments: List[TaborSegment], - sequence_tables: List[List[Tuple[int, int, int]]], - advanced_sequence_table: List[Tuple[int, int, int]]): - self._segments = segments - self._sequence_tables = [[self.TableEntry(*sequence_table_entry) - for sequence_table_entry in sequence_table] - for sequence_table in sequence_tables] - self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) - for adv_seq_entry in advanced_sequence_table] - - @classmethod - def from_read_data(cls, waveforms: List[np.ndarray], - sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], - advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': - return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], - [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], - cls._reformat_rep_seg_jump(advanced_sequence_table)) - - @classmethod - def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: - return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) - for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) - - def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: - if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): - adv_seq_tab = self._advanced_sequence_table[1:] - else: - adv_seq_tab = self._advanced_sequence_table +TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), + ('program', TaborProgram)]) - # remove idle pulse at end - if with_last_idles: - return adv_seq_tab - else: - while adv_seq_tab[-1] == (1, 1, 0): - adv_seq_tab = adv_seq_tab[:-1] - return adv_seq_tab - - def _iter_segment_table_entry(self, - with_first_idle=False, - with_last_idles=False) -> Generator[TableEntry, None, None]: - for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): - for _ in range(sequence_repeat): - yield from self._sequence_tables[sequence_no - 1] - - def iter_waveforms_and_repetitions(self, - channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): - yield ch_getter(self._segments[segment_no - 1]), segment_repeat - - def iter_samples(self, channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[np.uint16, None, None]: - for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): - waveform = list(waveform) - for _ in range(repetition): - yield from waveform - - def get_as_single_waveform(self, channel: int, - max_total_length: int = 10 ** 9, - with_marker: bool = False) -> Optional[np.ndarray]: - waveforms = self.get_waveforms(channel, with_marker=with_marker) - repetitions = self.get_repetitions() - waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) - - total_length = (repetitions * waveform_lengths).sum() - if total_length > max_total_length: - return None - - result = np.empty(total_length, dtype=np.uint16) - c_idx = 0 - for wf, rep in zip(waveforms, repetitions): - mem = wf.size * rep - target = result[c_idx:c_idx + mem] - - target = target.reshape((rep, wf.size)) - target[:, :] = wf[np.newaxis, :] - c_idx += mem - return result - - def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: - if with_marker: - ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] - else: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1]) - for _, segment_no, _ in self._iter_segment_table_entry()] - - def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1])] - - def get_repetitions(self) -> np.ndarray: - return np.fromiter((segment_repeat - for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) - - def __eq__(self, other): - for ch in (0, 1): - for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), - other.iter_samples(ch, True, False)): - if x != y: - return False - return True - - def to_builtin(self) -> dict: - waveforms = [[wf.data_a.tolist() for wf in self._segments], - [wf.data_b.tolist() for wf in self._segments]] - return {'waveforms': waveforms, - 'seq_tables': self._sequence_tables, - 'adv_seq_table': self._advanced_sequence_table} - - @classmethod - def from_builtin(cls, data: dict) -> 'PlottableProgram': - waveforms = data['waveforms'] - waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) - for data_a, data_b in zip(*waveforms)] - return cls(waveforms, data['seq_tables'], data['adv_seq_table']) +######################################################################################################################## +# Device +######################################################################################################################## +# Features +# TODO: implement Synchronization Feature for Tabor Devices +""" +class TaborChannelSynchronization(ChannelSynchronization): + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = device + def synchronize_channels(self, group_size: int) -> None: + pass # TODO: to implement +""" -######################################################################################################################## -class AWGDeviceDeviceTabor(AWGDevice): +# Implementation +class TaborDevice(AWGDevice): def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ - :param device_name: Name of the device - :param instr_addr: Instrument address that is forwarded to teawag - :param paranoia_level: Paranoia level that is forwarded to teawg - :param external_trigger: Not supported yet - :param reset: - :param mirror_addresses: + :param device_name: Name of the device + :param instr_addr: Instrument address that is forwarded to teawag + :param paranoia_level: Paranoia level that is forwarded to teawg + :param external_trigger: Not supported yet + :param reset: + :param mirror_addresses: addresses of multiple device which can be controlled at once """ - # New - ################# - super().__init__(device_name) + super().__init__(device_name) self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) - - # TODO: Mirrorfeature ueberarbeiten - self.add_feature(DeviceMirrorFeature(self.main_instrument, self.mirrored_instruments, self.all_devices)) - self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) self._coupled = None + self._clock_marker = [0, 0, 0, 0] # TODO: What are clock markers used for? - self._clock_marker = [0, 0, 0, 0] - # TODO: Wird der Parametername wirklich noch benoetigt? - # TODO: Startet i bei 1 oder 0? - self._channels = [AWGChannelTabor("test", i, self) for i in range(4)] - self._channel_tuples = [] + # Channel + self._channels = [TaborChannel(i + 1, self) for i in range(4)] - # TODO: Braucht man den alten identifier noch? - self._channel_tuples.append(AWGChannelTupleTabor(1, self, self._channels[0:1])) - self._channel_tuples.append(AWGChannelTupleTabor(2, self, self._channels[2:3])) + # ChannelTuple TODO: ChannelMarker fehlen / bzw. Liste leer + self._channel_tuples = [] + self._channel_tuples.append(TaborChannelTuple(1, self, self.channels[0:1], [])) + self._channel_tuples.append(TaborChannelTuple(2, self, self.channels[2:3], [])) - # self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') - # self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + # ChannelMarker + self._channel_marker = [] if external_trigger: raise NotImplementedError() # pragma: no cover @@ -582,90 +134,78 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external if reset: self.send_cmd(':RES') - # TODO: ggf. ueberarbeiten - self.initialize() + self.initialize() # TODO: ggf. ueberarbeiten + + # Trotzdem noch eine Ampitude Methode? + + # Trotzdem noch eine Offset Methode? - # New - ########################################################################################### def cleanup(self) -> None: - for channel_tuple in self._channel_tuples: - # TODO: clear oder cleanup - channel_tuple.cleanup() + pass # TODO: to implement + # TODO: Kann Collection auch noch spezialiseirt werden? @property - def channels(self) -> Iterable["AWGChannel"]: + def channels(self) -> Collection["TaborChannel"]: return self._channels @property - def channel_tuples(self) -> Iterable["AWGChannelTuple"]: - return self._channel_tuples - - # Old - ########################################################################################### - - # TODO: nicht mehr benötigt oder? + def marker_channels(self) -> Collection["AWGMarkerChannel"]: + return self._channel_marker - # @property - # def channel_pair_AB(self) -> 'TaborChannelPair': - # return self._channel_pair_AB - - # @property - # def channel_pair_CD(self) -> 'TaborChannelPair': - # return self._channel_pair_CD + @property + def channel_tuples(self) -> Collection["AWGChannelTuple"]: + return self._channel_tuples - # @property + @property def main_instrument(self) -> teawg.TEWXAwg: return self._instr - #@property + @property def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: return self._mirrors - #@property - def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr,) + self._mirrors - @property def paranoia_level(self) -> int: return self._instr.paranoia_level - # TODO: Paranoia als Feature? - - # TODO: MirrorFeature @paranoia_level.setter def paranoia_level(self, val): - for instr in self[DeviceMirrorFeature].all_devices: + for instr in self.all_devices: instr.paranoia_level = val @property def dev_properties(self) -> dict: return self._instr.dev_properties - # TODO: MirrorFeature + @property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr,) + self._mirrors + def send_cmd(self, cmd_str, paranoia_level=None): - for instr in self[DeviceMirrorFeature].all_devices: + print(self.all_devices) + for instr in self.all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self[DeviceMirrorFeature].all_devices) + return tuple(instr.send_query(query_str) for instr in self.all_devices) else: return self._instr.send_query(query_str) def send_binary_data(self, pref, bin_dat, paranoia_level=None): - for instr in self[DeviceMirrorFeature].all_devices: + for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): - for instr in self[DeviceMirrorFeature].all_devices: + for instr in self.all_devices: instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): - for instr in self[DeviceMirrorFeature].all_devices: + for instr in self.all_devices: instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): - for instr in self[DeviceMirrorFeature].all_devices: + for instr in self.all_devices: instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) @@ -676,6 +216,7 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: paranoia_level = self.paranoia_level if paranoia_level < 3: + # TODO: unsolved Reference super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover else: cmd_str = cmd_str.rstrip() @@ -703,9 +244,9 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: def get_status_table(self) -> Dict[str, Union[str, float, int]]: """Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame - Returns: - An ordered dictionary with the results - """ + Returns: + An ordered dictionary with the results + """ name_query_type_list = [('channel', ':INST:SEL?', int), ('coupling', ':OUTP:COUP?', str), ('volt_dc', ':SOUR:VOLT:LEV:AMPL:DC?', float), @@ -734,52 +275,40 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): - self.select_channel(ch) + # TODO: select Channel und Marker fehlen im Device + self._select_channel(ch) self.select_marker((ch - 1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data - # TODO: Schleifen ersetzen - # for channel in self._channels: - # channel.select_channel() - @property def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover - # def select_channel(self, channel) -> None: - # if channel not in (1, 2, 3, 4): - # raise TaborException('Invalid channel: {}'.format(channel)) - # - # self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) - - def select_marker(self, marker: int) -> None: - """Select marker 1 or 2 of the currently active channel pair.""" - if marker not in (1, 2): - raise TaborException('Invalid marker: {}'.format(marker)) - self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker)) - - # def sample_rate(self, channel) -> int: - # if channel not in (1, 2, 3, 4): - # raise TaborException('Invalid channel: {}'.format(channel)) - # return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) - - # def get_amplitude(self, channel) -> float: - # if channel not in (1, 2, 3, 4): - # raise TaborException('Invalid channel: {}'.format(channel)) - # coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) - # if coupling == 'DC': - # return float(self.send_query(':VOLT?')) - # elif coupling == 'HV': - # return float(self.send_query(':VOLT:HV?')) - # else: - # raise TaborException('Unknown coupling: {}'.format(coupling)) - - # def get_offset(self, channel: int) -> float: - # if channel not in (1, 2, 3, 4): - # raise TaborException('Invalid channel: {}'.format(channel)) - # return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) + # TODO: soll man ein Channel Objekt oder eine ChannelNummer mitgeben? -> intern, das was am besten fuer die Umsetzung ist + def _select_channel(self, channel_nr: int) -> None: + if channel_nr not in range(1, len(self.channels)): + raise TaborException('Invalid channel: {}'.format(channel_nr)) + + self.send_cmd(':INST:SEL {channel}'.format(channel=channel_nr)) + + def _select_marker(self, marker_nr: int) -> None: + # TODO: right name for the parameter? + """Select marker a marker of the currently active channel pair.""" + if marker_nr not in range(1, len(self.channel_tuples[1].marker_channels)): + raise TaborException('Invalid marker: {}'.format(marker_nr)) + + self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker_nr)) + + # wird die Methode noch gebraucht? + def _sample_rate(self, channel_nr: int) -> int: + if channel_nr not in range(1, len(self.channels)): + raise TaborException('Invalid channel: {}'.format(channel_nr)) + + return int(self.channels[channel_nr].channel_tuple.sample_rate) + + # def setter_sample_rate implementieren? def enable(self) -> None: self.send_cmd(':ENAB') @@ -804,9 +333,6 @@ def initialize(self) -> None: self.send_cmd(':INST:SEL 3') self.send_cmd(setup_command) - - # TODO: Oder direkt in ein cleanup des Geraets? - # TODO: Wieso funktionier clear() fuer ChannelTuple nicht def reset(self) -> None: self.send_cmd(':RES') self._coupled = None @@ -814,9 +340,6 @@ def reset(self) -> None: for channel_tuple in self.channel_tuples: channel_tuple.clear() - # self.channel_pair_AB.clear() - # self.channel_pair_CD.clear() - def trigger(self) -> None: self.send_cmd(':TRIG') @@ -831,42 +354,136 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: raise TaborException('No device capable of device data read') -TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), - ('program', TaborProgram)]) +######################################################################################################################## +# Channel +######################################################################################################################## +# Features +class TaborOffsetAmplitude(OffsetAmplitude): + def __init__(self, channel: "TaborChannel"): + super().__init__() + self._parent = channel + + @property + def offset(self) -> float: + return float( + self._parent.device.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=self._parent.idn))) + @offset.setter + def offset(self, offset: float) -> None: + pass # TODO: to implement + @property + def amplitude(self) -> float: + coupling = self._parent.device.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=self._parent.idn)) + if coupling == 'DC': + return float(self._parent.device.send_query(':VOLT?')) + elif coupling == 'HV': + return float(self._parent.device.send_query(':VOLT:HV?')) + else: + raise TaborException('Unknown coupling: {}'.format(coupling)) + + @amplitude.setter + def amplitude(self, amplitude: float) -> None: + pass # TODO: to implement + + @property + def amplitude_offset_handling(self) -> str: + pass # TODO: to implement -class AWGChannelTupleTabor(AWGChannelTuple): - def __init__(self, idn: int, tabor_device: AWGDeviceDeviceTabor, channels: Iterable["AWGChannelTabor"]): - # New - ########### + @amplitude_offset_handling.setter + def amplitude_offset_handling(self, amp_offs_handling: str) -> None: + pass # TODO: to implement + + +# Implementation +class TaborChannel(AWGChannel): + def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) - self._device = tabor_device - self._channels = tuple(channels) - # TODO: Standart Sample Rate? - # Old - ########### - # TODO: Braucht man die alte Vererbung? - # super().__init__(identifier) - self._device = weakref.ref(tabor_device) + self._device = device + + self.add_feature(TaborOffsetAmplitude(self)) + + @property + def device(self) -> TaborDevice: + return self._device + + @property + def channel_tuple(self) -> Optional[AWGChannelTuple]: + pass + + def _set_channel_tuple(self, channel_tuple) -> None: + pass + + +######################################################################################################################## +# ChannelTuple +######################################################################################################################## +# Features +class TaborProgramManagement(ProgramManagement): + def __init__(self, channel_tuple: "TaborChannelTuple", ): + super().__init__() + self._programs = {} + self._armed_program = None + self._parent = channel_tuple + + def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], + force: bool = False) -> None: + pass # TODO: to implement + + def remove(self, name: str) -> None: + """Remove a program from the AWG. + + Also discards all waveforms referenced only by the program identified by name. + + Args: + name (str): The name of the program to remove. + """ + self._parent.free_program(name) + self._parent.cleanup() + + def clear(self) -> None: + pass # TODO: to implement + + def arm(self, name: Optional[str]) -> None: + self._parent._arm() + + @property + def programs(self) -> Set[str]: + pass # TODO: to implement + + +class TaborChannelTuple(AWGChannelTuple): + def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], + marker_channels: Iterable["TaborMarkerChannel"]): + # TODO: hat das weglassen des alten String identifier Auswirkungen? + # TODO: zugeordneter MarkerChannel + + super().__init__(idn) + self._device = device # TODO: weakref.ref(device) can't be used like in the old driver self._configuration_guard_count = 0 self._is_in_config_mode = False - # TODO: Fehler der jetzt beim synchronisiern geworfen wird - # if channels not in ((1, 2), (3, 4)): - # raise ValueError('Invalid channel pair: {}'.format(channels)) + # TODO: Ueberpreufung macht keinen Sinn + #if channels not in self._device.channel_tuples: + # raise ValueError('Invalid channel pair: {}'.format(channels)) + self._channels = tuple(channels) - # self._channels = channels + self._marker_channels = tuple(marker_channels) + self.add_feature(TaborProgramManagement(self)) + #TODO: Kommentar beenden + """ self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, output_offset=0., resolution=14), voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, output_offset=0., resolution=14), None, None) + """ self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -882,49 +499,38 @@ def __init__(self, idn: int, tabor_device: AWGDeviceDeviceTabor, channels: Itera self._internal_paranoia_level = 0 - self.clear() + self[TaborProgramManagement].clear() - # New - #################################################################################################################### - # TODO: Samplerate ist Channel oder Channeltuple aufgabe? - # @property - # def sample_rate(self) -> float: - # pass - # Bearbeitet @property - def device(self) -> AWGDevice: - return self._device() + def device(self) -> TaborDevice: + return self._device - # Bearbeitet @property - def channels(self) -> Iterable["AWGChannel"]: + def channels(self) -> Collection["AWGChannel"]: return self._channels - # Old - #################################################################################################################### + + @property + def marker_channels(self) -> Collection["AWGMarkerChannel"]: + return self._marker_channels + + @property + def sample_rate(self) -> float: + pass # TODO: to implement + def select(self) -> None: - self.device.send_cmd(':INST:SEL {}'.format(self._channels[0]), - paranoia_level=self.internal_paranoia_level) + pass # TODO: to implement @property def total_capacity(self) -> int: return int(self.device.dev_properties['max_arb_mem']) // 2 def free_program(self, name: str) -> TaborProgramMemory: - if name is None: - raise TaborException('Removing "None" program is forbidden.') - program = self._known_programs.pop(name) - self._segment_references[program.waveform_to_segment] -= 1 - if self._current_program == name: - self.change_armed_program(None) - return program + pass # TODO: to implement - def _restore_program(self, name: str, program: TaborProgram) -> None: - if name in self._known_programs: - raise ValueError('Program cannot be restored as it is already known.') - self._segment_references[program.waveform_to_segment] += 1 - self._known_programs[name] = program + def _restore_program(self) -> None: + pass # TODO: to implement @property def _segment_reserved(self) -> np.ndarray: @@ -968,215 +574,23 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences - @with_select - def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - return self.device.get_readable_device(simulator=True).read_adv_seq_table() + # upload im Feature def read_complete_program(self) -> PlottableProgram: return PlottableProgram.from_read_data(self.read_waveforms(), self.read_sequence_tables(), self.read_advanced_sequencer_table()) - @with_configuration_guard - @with_select - def upload(self, name: str, - program: Loop, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]], - voltage_transformation: Tuple[Callable, Callable], - force: bool = False) -> None: - """Upload a program to the AWG. - - The policy is to prefer amending the unknown waveforms to overwriting old ones.""" - - if len(channels) != self.num_channels: - raise ValueError('Channel ID not specified') - if len(markers) != self.num_markers: - raise ValueError('Markers not specified') - if len(voltage_transformation) != self.num_channels: - raise ValueError('Wrong number of voltage transformations') - - # adjust program to fit criteria - sample_rate = self.device._channels[0].sample_rate() - make_compatible(program, - minimal_waveform_length=192, - waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) - - if name in self._known_programs: - if force: - self.free_program(name) - else: - raise ValueError('{} is already known on {}'.format(name, self.identifier)) - try: - # parse to tabor program - tabor_program = TaborProgram(program, - channels=tuple(channels), - markers=markers, - device_properties=self.device.dev_properties) - - #TODO: Iterable does not implements __get_item__ - # They call the peak to peak range amplitude - - ranges = (self.device.channels[0][ChannelAmplitudeOffsetFeature].getAmplitude(), - self.device.channels[1][ChannelAmplitudeOffsetFeature].getAmplitude()) - - # ranges = (self.device.amplitude(self._channels[0]), - # self.device.amplitude(self._channels[1])) - - voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) - - if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: - voltage_offsets = (0, 0) - elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets = (self.device._channels[0][ChannelAmplitudeOffsetFeature].get_offset(), - self.device._channels[1][ChannelAmplitudeOffsetFeature].get_offset()) - else: - raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) - - segments, segment_lengths = tabor_program.sampled_segments(sample_rate=sample_rate, - voltage_amplitude=voltage_amplitudes, - voltage_offset=voltage_offsets, - voltage_transformation=voltage_transformation) - - waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, - segment_lengths) - except: - if to_restore: - self._restore_program(*to_restore) - if to_restore_was_armed: - self.change_armed_program(sname) - raise - - self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 - - for wf_index in np.flatnonzero(to_insert > 0): - segment_index = to_insert[wf_index] - self._upload_segment(to_insert[wf_index], segments[wf_index]) - waveform_to_segment[wf_index] = segment_index - - if np.any(to_amend): - segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] - waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) - - self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, - program=tabor_program) - - @with_configuration_guard - @with_select - def clear(self) -> None: - """Delete all segments and clear memory""" - self.device._channels[0].select_channel() - self.device.send_cmd(':TRAC:DEL:ALL') - self.device.send_cmd(':SOUR:SEQ:DEL:ALL') - self.device.send_cmd(':ASEQ:DEL') - - - self.device.send_cmd(':TRAC:DEF 1, 192', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:SEL 1', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) - - self._segment_lengths = 192 * np.ones(1, dtype=np.uint32) - self._segment_capacity = 192 * np.ones(1, dtype=np.uint32) - self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) - self._segment_references = np.ones(1, dtype=np.uint32) - - self._advanced_sequence_table = [] - self._sequencer_tables = [] - - self._known_programs = dict() - self.change_armed_program(None) + # clear im Feature def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ np.ndarray, np.ndarray, np.ndarray]: - """ - 1. Find known segments - 2. Find empty spaces with fitting length - 3. Find empty spaces with bigger length - 4. Amend remaining segments - :param segments: - :param segment_lengths: - :return: - """ - segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) - - waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) - - # separate into known and unknown - unknown = (waveform_to_segment == -1) - known = ~unknown - - known_pos_in_memory = waveform_to_segment[known] - - assert len(known_pos_in_memory) == 0 or np.all( - self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) - - new_reference_counter = self._segment_references.copy() - new_reference_counter[known_pos_in_memory] += 1 - - to_upload_size = np.sum(segment_lengths[unknown] + 16) - free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) - if free_points_in_total < to_upload_size: - raise MemoryError('Not enough free memory', - free_points_in_total, - to_upload_size, - self._free_points_in_total) - - to_amend = cast(np.ndarray, unknown) - to_insert = np.full(len(segments), fill_value=-1, dtype=np.int64) - - reserved_indices = np.flatnonzero(new_reference_counter > 0) - first_free = reserved_indices[-1] + 1 if len(reserved_indices) else 0 - - free_segments = new_reference_counter[:first_free] == 0 - free_segment_count = np.sum(free_segments) - - # look for a free segment place with the same length - for segment_idx in np.flatnonzero(to_amend): - if free_segment_count == 0: - break - - pos_of_same_length = np.logical_and(free_segments, - segment_lengths[segment_idx] == self._segment_capacity[:first_free]) - idx_same_length = np.argmax(pos_of_same_length) - if pos_of_same_length[idx_same_length]: - free_segments[idx_same_length] = False - free_segment_count -= 1 - - to_amend[segment_idx] = False - to_insert[segment_idx] = idx_same_length - - # try to find places that are larger than the segments to fit in starting with the large segments and large - # free spaces - segment_indices = np.flatnonzero(to_amend)[np.argsort(segment_lengths[to_amend])[::-1]] - capacities = self._segment_capacity[:first_free] - for segment_idx in segment_indices: - free_capacities = capacities[free_segments] - free_segments_indices = np.flatnonzero(free_segments)[np.argsort(free_capacities)[::-1]] - - if len(free_segments_indices) == 0: - break - - fitting_segment = np.argmax((free_capacities >= segment_lengths[segment_idx])[::-1]) - fitting_segment = free_segments_indices[fitting_segment] - if self._segment_capacity[fitting_segment] >= segment_lengths[segment_idx]: - free_segments[fitting_segment] = False - to_amend[segment_idx] = False - to_insert[segment_idx] = fitting_segment - - free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) - if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: - raise MemoryError('Fragmentation does not allow upload.', - np.sum(segment_lengths[to_amend] + 16), - free_points_at_end, - self._free_points_at_end) - - return waveform_to_segment, to_amend, to_insert + pass # TODO: to implement @with_select @with_configuration_guard def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: + # TODO: Why is the proptery for device not used? if self._segment_references[segment_index] > 0: raise ValueError('Reference count not zero') if segment.num_points > self._segment_capacity[segment_index]: @@ -1261,16 +675,7 @@ def cleanup(self) -> None: except Exception as e: raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e - def remove(self, name: str) -> None: - """Remove a program from the AWG. - - Also discards all waveforms referenced only by the program identified by name. - - Args: - name (str): The name of the program to remove. - """ - self.free_program(name) - self.cleanup() + # remove im Feature @with_configuration_guard def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: @@ -1329,21 +734,13 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu _ = self.device.main_instrument._visa_inst.query('*OPC?') def set_marker_state(self, marker: int, active: bool) -> None: - """Sets the marker state of this channel pair. - According to the manual one cannot turn them off/on separately.""" - command_string = ':INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}' - command_string = command_string.format( - channel=self._channels[0], - marker=(1, 2)[marker], - active='ON' if active else 'OFF') - self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) + pass # TODO: to implement def set_channel_state(self, channel, active) -> None: - command_string = ':INST:SEL {}; :OUTP {}'.format(self._channels[channel], 'ON' if active else 'OFF') - self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) + pass # TODO: to implement @with_select - def arm(self, name: str) -> None: + def _arm(self, name: str) -> None: if self._current_program == name: self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) else: @@ -1358,56 +755,7 @@ def set_program_sequence_table(self, name, new_sequence_table): @with_select @with_configuration_guard def change_armed_program(self, name: Optional[str]) -> None: - if name is None: - sequencer_tables = [self._idle_sequence_table] - advanced_sequencer_table = [(1, 1, 0)] - else: - waveform_to_segment_index, program = self._known_programs[name] - waveform_to_segment_number = waveform_to_segment_index + 1 - - # translate waveform number to actual segment - sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for (rep_count, wf_index, jump_flag) in sequencer_table] - for sequencer_table in program.get_sequencer_tables()] - - # insert idle sequence - sequencer_tables = [self._idle_sequence_table] + sequencer_tables - - # adjust advanced sequence table entries by idle sequence table offset - advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) - for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] - - if program.waveform_mode == TaborSequencing.SINGLE: - assert len(advanced_sequencer_table) == 1 - assert len(sequencer_tables) == 2 - - while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: - assert advanced_sequencer_table[0][0] == 1 - sequencer_tables[1].append((1, 1, 0)) - - # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table - - while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: - advanced_sequencer_table.append((1, 1, 0)) - - # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd('SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self._sequencer_tables = [] - self.device.send_cmd('ASEQ:DEL', paranoia_level=self.internal_paranoia_level) - self._advanced_sequence_table = [] - - # download all sequence tables - for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) - self.device.download_sequencer_table(sequencer_table) - self._sequencer_tables = sequencer_tables - self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) - - self.device.download_adv_seq_table(advanced_sequencer_table) - self._advanced_sequence_table = advanced_sequencer_table - - self._current_program = name + pass # TODO: to implement @with_select def run_current_program(self) -> None: @@ -1421,16 +769,17 @@ def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(program for program in self._known_programs.keys()) - # Bearbeitet + @property + def sample_rate(self) -> float: + pass # TODO: to implement + @property def num_channels(self) -> int: - """Returns the number of channels of a certain channel tuple""" - return len(self._channels) + return len(self.channels) - # TODO: ? - Jeder Channel hat genau einen Marker oder? @property def num_markers(self) -> int: - return len(self._channels) + pass # TODO: to implement def _enter_config_mode(self) -> None: """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the @@ -1438,7 +787,7 @@ def _enter_config_mode(self) -> None: When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip.""" if self._is_in_config_mode is False: - # 1. Select channel pair + # 1. Selct channel pair # 2. Select DC as function shape # 3. Select build-in waveform mode @@ -1458,124 +807,38 @@ def _enter_config_mode(self) -> None: self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = True - - # TODO: In Channel Touple aendern @with_select def _exit_config_mode(self) -> None: - """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" - - sel_ch = ':INST:SEL {}'.format(self._channels[0]) - aseq_cmd = ':SOUR:FUNC:MODE ASEQ;SEQ:SEL 1' - - cmds = [sel_ch, aseq_cmd] - - if self.device.is_coupled(): - # Coupled -> switch all channels at once - if self._channels == (1, 2): - other_channel_pair = self.device.channel_pair_CD - else: - assert self._channels == (3, 4) - other_channel_pair = self.device.channel_pair_AB + pass # TODO: to implement - if not other_channel_pair._is_in_config_mode: - cmds.append(':OUTP:ALL ON') - else: - # ch 0 already selected - cmds.append(':OUTP ON; :INST:SEL {}; :OUTP ON'.format(self._channels[1])) - - cmds.append(':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') - cmds.append(':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') - cmd = ';'.join(cmds) - self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = False - - - -class AWGChannelTabor(AWGChannel): - # Union aus String und Int in der alten Darstellung - - # TODO: doppelter identifier wegen identifier des alten ChannelPairs + Marker - def __init__(self, name: str, idn: int, tabor_device: AWGDeviceDeviceTabor, marker=False): +######################################################################################################################## +# Marker Channel +######################################################################################################################## +class TaborMarkerChannel(AWGMarkerChannel): + def __init__(self, idn: int, device: TaborDevice, channel_tuple: TaborChannelTuple): super().__init__(idn) - - self.add_feature( - ChannelAmplitudeOffsetFeature(self._get_offset, self._set_offset, self._get_amplitude, self._set_amplitude)) - - self.name = name - self.marker = marker - - self._device = tabor_device - self._channel_tuple: Optional[AWGChannelTupleTabor] = None - - # TODO: Amplitude und Offset Feature - - def select_channel(self) -> None: - self.send_cmd(':INST:SEL {channel}'.format(channel=self.idn)) - - def get_sample_rate(self) -> int: - return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=self.idn)))) - - def _get_offset(self) -> float: - return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=self.idn))) - - # TODO: Noch implementieren - def _set_offset(self, offset: float) -> None: - pass - - def _get_amplitude(self) -> float: - coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=self.idn)) - if coupling == 'DC': - return float(self.send_query(':VOLT?')) - elif coupling == 'HV': - return float(self.send_query(':VOLT:HV?')) - else: - raise TaborException('Unknown coupling: {}'.format(coupling)) - - # TODO: Noch implementieren - def _set_amplitude(self, amplitude: float) -> None: - pass + self._device = device + self._channel_tuple = channel_tuple @property def device(self) -> AWGDevice: - return self._device + pass @property def channel_tuple(self) -> Optional[AWGChannelTuple]: - return self._channel_tuple + pass def _set_channel_tuple(self, channel_tuple) -> None: - self._channel_tuple = channel_tuple + pass ######################################################################################################################## - class TaborException(Exception): pass -# TODO: Anpassen class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" - - # TODO: so okay? - evtl noch anpassen - isinstance auche fuer channel? - def __init__(self, *args, device: FeatureAble): - super().__init__(*args) - self.device = device - - def reset_device(self): - if isinstance(self.device, AWGDeviceDeviceTabor): - self.device.reset() - elif isinstance(self.device, AWGChannelTupleTabor): - self.device.clear() - - # def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): - # super().__init__(*args) - # self.device = device - # - # def reset_device(self): - # if isinstance(self.device, TaborAWGRepresentation): - # self.device.reset() - # elif isinstance(self.device, TaborChannelPair): - # self.device.clear() + pass # TODO: to implement diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py index b03debfd1..687e4f902 100644 --- a/tests/hardware/tabor_dummy_based_tests.py +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -9,7 +9,7 @@ import numpy as np from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling -from qupulse.hardware.awgs.tabor import TaborProgram, TaborAWGRepresentation, TaborProgramMemory +from qupulse.hardware.awgs.old_tabor import TaborProgram, TaborDevice from tests.hardware.dummy_modules import import_package diff --git a/tests/hardware/tabor_exex_test.py b/tests/hardware/tabor_exex_test.py index 0e9e98537..b3d6fc2dd 100644 --- a/tests/hardware/tabor_exex_test.py +++ b/tests/hardware/tabor_exex_test.py @@ -72,10 +72,10 @@ def get_window(card): class TaborTests(unittest.TestCase): @unittest.skip def test_all(self): - from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborAWGRepresentation + from qupulse.hardware.awgs.old_tabor import TaborChannelTuple, TaborDevice #import warnings - tawg = TaborAWGRepresentation(r'USB0::0x168C::0x2184::0000216488::INSTR') - tchannelpair = TaborChannelPair(tawg, (1, 2), 'TABOR_AB') + tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') + tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') tawg.paranoia_level = 2 #warnings.simplefilter('error', Warning) diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 124a2914d..cff5966b5 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -7,9 +7,8 @@ import pytabor import numpy as np -from qupulse.hardware.awgs.tabor import TaborAWGRepresentation, TaborChannelPair -from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry -from typing import List, Tuple, Optional, Any +from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, PlottableProgram + class TaborSimulatorManager: def __init__(self, @@ -56,9 +55,9 @@ def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_ti time.sleep(0.1) def connect(self): - self.instrument = TaborAWGRepresentation('127.0.0.1', - reset=True, - paranoia_level=2) + self.instrument = TaborDevice('127.0.0.1', + reset=True, + paranoia_level=2) if self.instrument.main_instrument.visa_inst is None: raise RuntimeError('Could not connect to simulator') @@ -187,6 +186,7 @@ def setUp(self): self.channel_pair = TaborChannelPair(self.instrument, (1, 2), 'tabor_unit_test') + def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: @staticmethod From 22a94732a488b4b4874fe47c3ec42cf41705a00e Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 4 Mar 2020 14:30:09 +0100 Subject: [PATCH 044/107] simulator based unit tests run without a problem --- qupulse/hardware/awgs/base.py | 6 +- qupulse/hardware/awgs/features.py | 33 +- qupulse/hardware/awgs/old_tabor.py | 124 +- qupulse/hardware/awgs/tabor.py | 1060 ++++++++++++++--- .../tabor_backward_compatibility_tests.py | 2 +- .../old_tabor_simulator_based_tests.py | 238 ++++ tests/hardware/tabor_simulator_based_tests.py | 65 +- 7 files changed, 1272 insertions(+), 256 deletions(-) create mode 100644 tests/hardware/old_tabor_simulator_based_tests.py diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index dd73e7bca..e5a066095 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -18,7 +18,7 @@ def __init__(self): class AWGChannelFeature(Feature, ABC): """Base class for features that are used for `AWGChannel`s""" def __init__(self): - super().__init__(AWGChannel) + super().__init__(_BaseAWGChannel) class AWGChannelTupleFeature(Feature, ABC): @@ -121,7 +121,7 @@ def marker_channels(self) -> Collection["AWGMarkerChannel"]: raise NotImplementedError() -class _BaseAWGChannel(ABC): +class _BaseAWGChannel(FeatureAble[AWGChannelFeature], ABC): """Base class for a single channel of an AWG""" def __init__(self, idn: int): @@ -160,7 +160,7 @@ def _set_channel_tuple(self, channel_tuple) -> None: raise NotImplementedError() -class AWGChannel(_BaseAWGChannel, FeatureAble[AWGChannelFeature], ABC): +class AWGChannel(_BaseAWGChannel, ABC): """Base class for a single channel of an AWG""" @property def name(self) -> str: diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 9cc280364..d21e1527f 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -34,8 +34,9 @@ def upload(self, name: str, channels: Tuple[Optional[ChannelID], ...], markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], - force: bool=False) -> None: - """Upload a program to the AWG. + force: bool = False) -> None: + """ + Upload a program to the AWG. Physically uploads all waveforms required by the program - excluding those already present - to the device and sets up playback sequences accordingly. @@ -56,7 +57,8 @@ def upload(self, name: str, @abstractmethod def remove(self, name: str) -> None: - """Remove a program from the AWG. + """ + Remove a program from the AWG. Also discards all waveforms referenced only by the program identified by name. @@ -67,7 +69,8 @@ def remove(self, name: str) -> None: @abstractmethod def clear(self) -> None: - """Removes all programs and waveforms from the AWG. + """ + Removes all programs and waveforms from the AWG. Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! """ @@ -125,8 +128,10 @@ def amplitude(self, amplitude: float) -> None: @property @abstractmethod def amplitude_offset_handling(self) -> str: - """Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and - offset settings are constant or if these should be optimized by the driver""" + """ + Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and + offset settings are constant or if these should be optimized by the driver + """ raise NotImplementedError() @amplitude_offset_handling.setter @@ -136,3 +141,19 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` """ raise NotImplementedError() + + +class ActivatableChannels(AWGChannelFeature): + @property + @abstractmethod + def status(self) -> bool: + raise NotImplementedError() + + @status.setter + @abstractmethod + def status(self, channel_status: bool) -> None: + """ + Sets the current state of a channel + channel_state: the new state of a channel + """ + raise NotImplementedError() diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index d8eb6ccf3..8697ee0dd 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -20,9 +20,7 @@ from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling - -assert(sys.byteorder == 'little') - +assert (sys.byteorder == 'little') __all__ = ['TaborDevice', 'TaborChannelTuple'] @@ -48,14 +46,14 @@ def __init__(self, self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) - if marker_a is not None and len(marker_a)*2 != self.num_points: + if marker_a is not None and len(marker_a) * 2 != self.num_points: raise TaborException('Marker A has to have half of the channels length') - if marker_b is not None and len(marker_b)*2 != self.num_points: + if marker_b is not None and len(marker_b) * 2 != self.num_points: raise TaborException('Marker A has to have half of the channels length') @classmethod def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': - data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1, )) + data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() return cls.from_binary_data(data_a, data_b) @@ -63,12 +61,12 @@ def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': ch_b = data_b - channel_mask = np.uint16(2**14 - 1) + channel_mask = np.uint16(2 ** 14 - 1) ch_a = np.bitwise_and(data_a, channel_mask) - marker_a_mask = np.uint16(2**14) - marker_b_mask = np.uint16(2**15) - marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1, )) + marker_a_mask = np.uint16(2 ** 14) + marker_b_mask = np.uint16(2 ** 15) + marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) marker_a = np.bitwise_and(marker_data, marker_a_mask) marker_b = np.bitwise_and(marker_data, marker_b_mask) @@ -152,7 +150,8 @@ def __init__(self, raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker for marker in - markers if marker is not None) + markers if + marker is not None) self._program = program self.__waveform_mode = None @@ -191,7 +190,7 @@ def sampled_segments(self, voltage_offset: Tuple[float, float], voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], Sequence[int]]: - sample_rate = fractions.Fraction(sample_rate, 10**9) + sample_rate = fractions.Fraction(sample_rate, 10 ** 9) time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) @@ -263,8 +262,8 @@ def setup_advanced_sequence_mode(self) -> None: max_seq_len = self.__device_properties['max_seq_len'] def check_merge_with_next(program, n): - if (program[n].repetition_count == 1 and program[n+1].repetition_count == 1 and - len(program[n]) + len(program[n+1]) < max_seq_len): + if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and + len(program[n]) + len(program[n + 1]) < max_seq_len): program[n][len(program[n]):] = program[n + 1][:] program[n + 1:n + 2] = [] return True @@ -289,9 +288,9 @@ def check_partial_unroll(program, n): assert self.program[i].repetition_count > 0 if self.program[i].repetition_count == 1: # check if merging with neighbour is possible - if i > 0 and check_merge_with_next(self.program, i-1): + if i > 0 and check_merge_with_next(self.program, i - 1): pass - elif i+1 < len(self.program) and check_merge_with_next(self.program, i): + elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): pass # check if (partial) unrolling is possible @@ -301,15 +300,15 @@ def check_partial_unroll(program, n): # check if sequence table can be extended by unrolling a neighbor elif (i > 0 and self.program[i - 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i-1]) < max_seq_len): - self.program[i][:0] = self.program[i-1].copy_tree_structure()[:] + and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): + self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] self.program[i - 1].repetition_count -= 1 - elif (i+1 < len(self.program) - and self.program[i+1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i+1]) < max_seq_len): - self.program[i][len(self.program[i]):] = self.program[i+1].copy_tree_structure()[:] - self.program[i+1].repetition_count -= 1 + elif (i + 1 < len(self.program) + and self.program[i + 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): + self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] + self.program[i + 1].repetition_count -= 1 else: raise TaborException('The algorithm is not smart enough to make this sequence table longer') @@ -423,7 +422,7 @@ def dev_properties(self) -> dict: @property def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr, ) + self._mirrors + return (self._instr,) + self._mirrors def send_cmd(self, cmd_str, paranoia_level=None): for instr in self.all_devices: @@ -518,7 +517,7 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): self.select_channel(ch) - self.select_marker((ch-1) % 2 + 1) + self.select_marker((ch - 1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data @@ -575,15 +574,14 @@ def initialize(self) -> None: # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) setup_command = ( - ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " - ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") self.send_cmd(':INST:SEL 1') self.send_cmd(setup_command) self.send_cmd(':INST:SEL 3') self.send_cmd(setup_command) - def reset(self) -> None: self.send_cmd(':RES') self.initialize() @@ -612,6 +610,7 @@ def with_configuration_guard(function_object: Callable[['TaborChannelTuple', Any 'TaborChannelTuple'], Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" + @functools.wraps(function_object) def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: if channel_pair._configuration_guard_count == 0: @@ -630,6 +629,7 @@ def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: def with_select(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[['TaborChannelTuple'], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" + @functools.wraps(function_object) def selector(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: channel_pair.select() @@ -705,28 +705,28 @@ def iter_samples(self, channel: int, yield from waveform def get_as_single_waveform(self, channel: int, - max_total_length: int=10**9, - with_marker: bool=False) -> Optional[np.ndarray]: + max_total_length: int = 10 ** 9, + with_marker: bool = False) -> Optional[np.ndarray]: waveforms = self.get_waveforms(channel, with_marker=with_marker) repetitions = self.get_repetitions() waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) - total_length = (repetitions*waveform_lengths).sum() + total_length = (repetitions * waveform_lengths).sum() if total_length > max_total_length: return None result = np.empty(total_length, dtype=np.uint16) c_idx = 0 for wf, rep in zip(waveforms, repetitions): - mem = wf.size*rep - target = result[c_idx:c_idx+mem] + mem = wf.size * rep + target = result[c_idx:c_idx + mem] target = target.reshape((rep, wf.size)) target[:, :] = wf[np.newaxis, :] c_idx += mem return result - def get_waveforms(self, channel: int, with_marker: bool=False) -> List[np.ndarray]: + def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: if with_marker: ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] else: @@ -768,7 +768,7 @@ def from_builtin(cls, data: dict) -> 'PlottableProgram': class TaborChannelTuple(AWG): def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identifier: str): super().__init__(identifier) - self._device = weakref.ref(tabor_device) + self._device = weakref.ref(tabor_device) self._configuration_guard_count = 0 self._is_in_config_mode = False @@ -778,11 +778,11 @@ def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identif self._channels = channels self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), None, None) + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), None, None) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -882,7 +882,7 @@ def upload(self, name: str, channels: Tuple[Optional[ChannelID], Optional[ChannelID]], markers: Tuple[Optional[ChannelID], Optional[ChannelID]], voltage_transformation: Tuple[Callable, Callable], - force: bool=False) -> None: + force: bool = False) -> None: """Upload a program to the AWG. The policy is to prefer amending the unknown waveforms to overwriting old ones.""" @@ -899,7 +899,7 @@ def upload(self, name: str, make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10**9)) + sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) # helper to restore previous state if upload is impossible to_restore = None @@ -920,13 +920,13 @@ def upload(self, name: str, channels=tuple(channels), markers=markers, device_properties=self.device.dev_properties) - + # They call the peak to peak range amplitude ranges = (self.device.amplitude(self._channels[0]), self.device.amplitude(self._channels[1])) - voltage_amplitudes = (ranges[0]/2, ranges[1]/2) - + voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) + if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets = (0, 0) elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: @@ -934,7 +934,7 @@ def upload(self, name: str, self.device.offset(self._channels[1])) else: raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) - + segments, segment_lengths = tabor_program.sampled_segments(sample_rate=sample_rate, voltage_amplitude=voltage_amplitudes, voltage_offset=voltage_offsets, @@ -977,8 +977,8 @@ def clear(self) -> None: self.device.send_cmd(':TRAC:MODE COMB') self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) - self._segment_lengths = 192*np.ones(1, dtype=np.uint32) - self._segment_capacity = 192*np.ones(1, dtype=np.uint32) + self._segment_lengths = 192 * np.ones(1, dtype=np.uint32) + self._segment_capacity = 192 * np.ones(1, dtype=np.uint32) self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) self._segment_references = np.ones(1, dtype=np.uint32) @@ -988,7 +988,8 @@ def clear(self) -> None: self._known_programs = dict() self.change_armed_program(None) - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ + np.ndarray, np.ndarray, np.ndarray]: """ 1. Find known segments 2. Find empty spaces with fitting length @@ -1008,7 +1009,8 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths known_pos_in_memory = waveform_to_segment[known] - assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + assert len(known_pos_in_memory) == 0 or np.all( + self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) new_reference_counter = self._segment_references.copy() new_reference_counter[known_pos_in_memory] += 1 @@ -1035,7 +1037,8 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths if free_segment_count == 0: break - pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + pos_of_same_length = np.logical_and(free_segments, + segment_lengths[segment_idx] == self._segment_capacity[:first_free]) idx_same_length = np.argmax(pos_of_same_length) if pos_of_same_length[idx_same_length]: free_segments[idx_same_length] = False @@ -1123,7 +1126,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i])) + self.device.send_cmd(':TRAC:DEF {},{}'.format(i + 1, segment_lengths[i])) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -1138,7 +1141,7 @@ def cleanup(self) -> None: """Discard all segments after the last which is still referenced""" reserved_indices = np.flatnonzero(self._segment_references > 0) old_end = len(self._segment_lengths) - new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 + new_end = reserved_indices[-1] + 1 if len(reserved_indices) else 0 self._segment_lengths = self._segment_lengths[:new_end] self._segment_capacity = self._segment_capacity[:new_end] self._segment_hashes = self._segment_hashes[:new_end] @@ -1148,8 +1151,8 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) - for i in range(chunk_start, min(chunk_start+chunk_size, old_end)))) + self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i + 1) + for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) except Exception as e: raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e @@ -1203,7 +1206,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # translate waveform number to actual segment sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) for (rep_count, wf_index, jump_flag) in sequencer_table] - for sequencer_table in program.get_sequencer_tables()] + for sequencer_table in program.get_sequencer_tables()] # insert idle sequence sequencer_tables = [self._idle_sequence_table] + sequencer_tables @@ -1234,7 +1237,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i+1)) + self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) self.device.download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables self.device.send_cmd('SEQ:SEL 1') @@ -1281,7 +1284,7 @@ def _enter_config_mode(self) -> None: self.device.send_cmd(':OUTP:ALL OFF') else: self.device.send_cmd(':INST:SEL {}; :OUTP OFF; :INST:SEL {}; :OUTP OFF'.format(*self._channels)) - + self.set_marker_state(0, False) self.set_marker_state(1, False) self.device.send_cmd(':SOUR:FUNC:MODE FIX') @@ -1318,6 +1321,7 @@ def _exit_config_mode(self) -> None: class TaborException(Exception): pass + class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" @@ -1327,7 +1331,7 @@ def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): self.device = device def reset_device(self): - if isinstance(self.device, TaborDevice): + if isinstance(self.device, TaborDevice): self.device.reset() elif isinstance(self.device, TaborChannelTuple): self.device.clear() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index d3ed4ef9b..1a9b66af8 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,13 +1,16 @@ +import fractions import functools -from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple +import sys +from enum import Enum +from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple, cast from collections import OrderedDict import numpy as np from qupulse import ChannelID -from qupulse._program._loop import Loop - +from qupulse._program._loop import Loop, make_compatible +from qupulse._program.waveforms import MultiChannelWaveform from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ - ProgramManagement -from qupulse.hardware.util import voltage_to_uint16, make_combined_wave + ProgramManagement, ActivatableChannels +from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times from qupulse.utils.types import Collection from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel from typing import Sequence @@ -38,20 +41,444 @@ ######################################################################################################################## +# What does this mean? +assert (sys.byteorder == "little") + +# Anpassen +__all__ = ["TaborDevice", "TaborChannelTuple"] + + class TaborSegment: - pass # TODO: to implement + # __slots__ = ("channel_list", "marker_list") + # + # def __init__(self, channel_list: List[Optional[np.ndarray]], marker_list: List[Optional[np.ndarray]]): + # """ + # :param channel_list: + # :param marker_list: + # ??? Beides Listen mit jeweils Spannungswerten zu den einzelden Channlen/Markern + # """ + # + # # TODO: check if the channel and the marker count is valid + # + # # Check if at least on channel is not None + # ch_not_empty = False + # for channel in channel_list: + # if channel is not None: + # ch_not_empty = True + # break + # if ch_not_empty: + # raise TaborException("Empty TaborSegments are not allowed") + # + # # Check if all channels that aren't None are the same length + # comparison_length = self.num_points + # for channel in channel_list: + # if channel is not None: + # comparison_length = len(channel) + # break + # for channel in channel_list: + # if channel is not None: + # if not (len(channel) == comparison_length): # can be ignored. this case isn't possible + # raise TaborException("Channel entries to have to have the same length") + # + # self.channel_list = List[np.asarray(Optional[np.ndarray], dtype=np.uint16)] + # self.marker_list = List[np.asarray(Optional[np.ndarray], dtype=bool)] + # # TODO: is it possible like that. It's only possible when python works with references + # for channel in channel_list: + # self.channel_list.add(None if channel is None else np.asarray(channel, dtype=np.uint16)) + # for marker in marker_list: + # self.marker_list.add(None if marker is None else np.asarray(marker, dtype=bool)) + # + # # Check all markers have half the length of the channels + # for idx, marker in enumerate(marker_list): + # if marker is not None and len(marker) * 2 != self.num_points: + # raise TaborException("Marker {} has to have half of the channels length".format(idx + 1)) + # # +1 to match the idn of the marker channel objects + # + # @classmethod + # def from_binary_segment(cls, segment_data: np.ndarray) -> "TaborSegment": + # pass # TODO: to implement + # + # @classmethod + # def from_binary_data(cls, date_list: List[np.ndarray]) -> "TaborSegment": + # channel_list: List[Optional[np.ndarray]] + # marker_list: List[Optional[np.ndarray]] + # + # # TODO: to implement + # + # return cls(channel_list, marker_list) + # + # def __hash__(self) -> int: + # # TODO: is this possible? + # return hash(tuple((self.channel_list, self.marker_list))) + # + # def __eq__(self, other: "TaborSegment"): + # pass # TODO: to implement + # + # def data(self, data_nr: int): + # pass # TODO: to implement + # + # @property + # def num_points(self) -> int: + # """ + # Method that returns the length of the first Channel that is not None + # :return: length of the first Channel that is not None + # """ + # for channel in self.channel_list: + # if channel is not None: + # return len(channel) + # + # def get_as_binary(self) -> np.ndarray: + # pass # TODO: to implement + + """Represents one segment of two channels on the device. Convenience class.""" + + __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') + + def __init__(self, + ch_a: Optional[np.ndarray], + ch_b: Optional[np.ndarray], + marker_a: Optional[np.ndarray], + marker_b: Optional[np.ndarray]): + if ch_a is None and ch_b is None: + raise TaborException('Empty TaborSegments are not allowed') + if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): + raise TaborException('Channel entries to have to have the same length') + + self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) + self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) + + self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) + self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) + + if marker_a is not None and len(marker_a) * 2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + if marker_b is not None and len(marker_b) * 2 != self.num_points: + raise TaborException('Marker A has to have half of the channels length') + + @classmethod + def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': + data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) + data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() + return cls.from_binary_data(data_a, data_b) + + @classmethod + def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': + ch_b = data_b + + channel_mask = np.uint16(2 ** 14 - 1) + ch_a = np.bitwise_and(data_a, channel_mask) + + marker_a_mask = np.uint16(2 ** 14) + marker_b_mask = np.uint16(2 ** 15) + marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) + + marker_a = np.bitwise_and(marker_data, marker_a_mask) + marker_b = np.bitwise_and(marker_data, marker_b_mask) + + return cls(ch_a=ch_a, + ch_b=ch_b, + marker_a=marker_a, + marker_b=marker_b) + + def __hash__(self) -> int: + return hash(tuple(0 if data is None else bytes(data) + for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) + + def __eq__(self, other: 'TaborSegment'): + def compare_markers(marker_1, marker_2): + if marker_1 is None: + if marker_2 is None: + return True + else: + return not np.any(marker_2) + + elif marker_2 is None: + return not np.any(marker_1) + + else: + return np.array_equal(marker_1, marker_2) + + return (np.array_equal(self.ch_a, other.ch_a) and + np.array_equal(self.ch_b, other.ch_b) and + compare_markers(self.marker_a, other.marker_a) and + compare_markers(self.marker_b, other.marker_b)) + + @property + def data_a(self) -> np.ndarray: + """channel_data and marker data""" + if self.marker_a is None and self.marker_b is None: + return self.ch_a + + if self.ch_a is None: + raise NotImplementedError('What data should be used in a?') + + # copy channel information + data = np.array(self.ch_a) + + if self.marker_a is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) + + if self.marker_b is not None: + data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) + + return data + + @property + def data_b(self) -> np.ndarray: + """channel_data and marker data""" + return self.ch_b + + @property + def num_points(self) -> int: + return len(self.ch_b) if self.ch_a is None else len(self.ch_a) + + def get_as_binary(self) -> np.ndarray: + assert not (self.ch_a is None or self.ch_b is None) + return make_combined_wave([self]) + + +class TaborSequencing(Enum): + SINGLE = 1 + ADVANCED = 2 + class TaborProgram: - pass # TODO: to implement + def __init__(self, + program: Loop, + device_properties, + channels, + markers): + # TODO: entspricht channels und markers den Vorgaben der device_properties + kommentare entfernen bzw. Code ersetzen + + # channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker + # for marker in + # markers if + # marker is not None) + + self._program = program + + self.__waveform_mode = None + # self._channels = tuple(channels) + # self._markers = tuple(markers) + # self.__used_channels = channel_set + self.__device_properties = device_properties + + self._waveforms = List["MultiChannelWaveform"] + self._sequencer_tables = [] + self._advanced_sequencer_table = [] + + if self.program.repetition_count > 1: + self.program.encapsulate() + + if self.program.depth() > 1: + self.setup_advanced_sequence_mode() + self.__waveform_mode = TaborSequencing.ADVANCED + else: + if self.program.depth() == 0: + self.program.encapsulate() + self.setup_single_sequence_mode() + self.__waveform_mode = TaborSequencing.SINGLE + + @property + def markers(self): + return self._markers + # TODO: typing + + @property + def channels(self): + return self._channels + # TODO: typing + + def sampled_segments(self, + sample_rate: fractions.Fraction, + voltage_amplitude: Tuple[float, float], + voltage_offset: Tuple[float, float], + voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], + Sequence[int]]: + sample_rate = fractions.Fraction(sample_rate, 10 ** 9) + + time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) + + if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): + raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') + + # TODO: ueberpruefen + def voltage_to_data(waveform, time, channel): + if self._channels[channel]: + return voltage_to_uint16( + voltage_transformation[channel]( + waveform.get_sampled(channel=self._channels[channel], + sample_times=time)), + voltage_amplitude[channel], + voltage_offset[channel], + resolution=14) + else: + return np.full_like(time, 8192, dtype=np.uint16) + + def get_marker_data(waveform: MultiChannelWaveform, time, marker): + if self._markers[marker]: + markerID = self._markers[marker] + return waveform.get_sampled(channel=markerID, sample_times=time) != 0 + else: + return np.full_like(time, False, dtype=bool) + + segments = np.empty_like(self._waveforms, dtype=TaborSegment) + for i, waveform in enumerate(self._waveforms): + t = time_array[:segment_lengths[i]] + marker_time = t[::2] + segment_a = voltage_to_data(waveform, t, 0) + segment_b = voltage_to_data(waveform, t, 1) + assert (len(segment_a) == len(t)) + assert (len(segment_b) == len(t)) + marker_a = get_marker_data(waveform, marker_time, 0) + marker_b = get_marker_data(waveform, marker_time, 1) + segments[i] = TaborSegment(ch_a=segment_a, + ch_b=segment_b, + marker_a=marker_a, + marker_b=marker_b) + return segments, segment_lengths + + def setup_single_sequence_mode(self) -> None: + assert self.program.depth() == 1 + + sequencer_table = [] + waveforms = OrderedDict() + + for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in self.program): + if waveform in waveforms: + waveform_index = waveforms[waveform] + else: + waveform_index = len(waveforms) + waveforms[waveform] = waveform_index + sequencer_table.append((repetition_count, waveform_index, 0)) + + self._waveforms = tuple(waveforms.keys()) + self._sequencer_tables = [sequencer_table] + self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] + + def setup_advanced_sequence_mode(self) -> None: + assert self.program.depth() > 1 + assert self.program.repetition_count == 1 + + self.program.flatten_and_balance(2) + + min_seq_len = self.__device_properties['min_seq_len'] + max_seq_len = self.__device_properties['max_seq_len'] + + def check_merge_with_next(program, n): + if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and + len(program[n]) + len(program[n + 1]) < max_seq_len): + program[n][len(program[n]):] = program[n + 1][:] + program[n + 1:n + 2] = [] + return True + return False + + def check_partial_unroll(program, n): + st = program[n] + if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: + if sum(entry.repetition_count for entry in st) < min_seq_len: + st.unroll_children() + while len(st) < min_seq_len: + st.split_one_child() + return True + return False + + i = 0 + while i < len(self.program): + self.program[i].assert_tree_integrity() + if len(self.program[i]) > max_seq_len: + raise TaborException('The algorithm is not smart enough to make sequence tables shorter') + elif len(self.program[i]) < min_seq_len: + assert self.program[i].repetition_count > 0 + if self.program[i].repetition_count == 1: + # check if merging with neighbour is possible + if i > 0 and check_merge_with_next(self.program, i - 1): + pass + elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): + pass + + # check if (partial) unrolling is possible + elif check_partial_unroll(self.program, i): + i += 1 + + # check if sequence table can be extended by unrolling a neighbor + elif (i > 0 + and self.program[i - 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): + self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] + self.program[i - 1].repetition_count -= 1 + + elif (i + 1 < len(self.program) + and self.program[i + 1].repetition_count > 1 + and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): + self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] + self.program[i + 1].repetition_count -= 1 + + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + elif check_partial_unroll(self.program, i): + i += 1 + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + else: + i += 1 + + for sequence_table in self.program: + assert len(sequence_table) >= self.__device_properties['min_seq_len'] + assert len(sequence_table) <= self.__device_properties['max_seq_len'] + + advanced_sequencer_table = [] + sequencer_tables = [] + waveforms = OrderedDict() + for sequencer_table_loop in self.program: + current_sequencer_table = [] + for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), + waveform_loop.repetition_count) + for waveform_loop in sequencer_table_loop): + if waveform in waveforms: + wf_index = waveforms[waveform] + else: + wf_index = len(waveforms) + waveforms[waveform] = wf_index + current_sequencer_table.append((repetition_count, wf_index, 0)) + + if current_sequencer_table in sequencer_tables: + sequence_no = sequencer_tables.index(current_sequencer_table) + 1 + else: + sequence_no = len(sequencer_tables) + 1 + sequencer_tables.append(current_sequencer_table) + + advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) + + self._advanced_sequencer_table = advanced_sequencer_table + self._sequencer_tables = sequencer_tables + self._waveforms = tuple(waveforms.keys()) + + @property + def program(self) -> Loop: + return self._program + + def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: + return self._sequencer_tables + + def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: + return self._advanced_sequencer_table + + @property + def waveform_mode(self) -> str: + return self.__waveform_mode + # TODO: typing # TODO: How does this work? -def with_configuration_guard(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[ - ['TaborChannelTuple'], Any]: +def with_configuration_guard(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[ + ["TaborChannelTuple"], Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" @functools.wraps(function_object) - def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: + def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: if channel_pair._configuration_guard_count == 0: channel_pair._enter_config_mode() channel_pair._configuration_guard_count += 1 @@ -67,25 +494,26 @@ def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: # TODO: How does this work? -def with_select(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[['TaborChannelTuple'], Any]: +def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @functools.wraps(function_object) - def selector(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: + def selector(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: channel_pair.select() return function_object(channel_pair, *args, **kwargs) return selector -TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), - ('program', TaborProgram)]) +TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), + ("program", TaborProgram)]) ######################################################################################################################## # Device ######################################################################################################################## # Features -# TODO: implement Synchronization Feature for Tabor Devices +# TODO: maybe implement Synchronization Feature for Tabor Devices + """ class TaborChannelSynchronization(ChannelSynchronization): def __init__(self, device: "TaborDevice"): @@ -116,44 +544,51 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._coupled = None self._clock_marker = [0, 0, 0, 0] # TODO: What are clock markers used for? + if reset: + self.send_cmd(":RES") # Channel self._channels = [TaborChannel(i + 1, self) for i in range(4)] + # ChannelMarker + self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] + # ChannelTuple TODO: ChannelMarker fehlen / bzw. Liste leer - self._channel_tuples = [] - self._channel_tuples.append(TaborChannelTuple(1, self, self.channels[0:1], [])) - self._channel_tuples.append(TaborChannelTuple(2, self, self.channels[2:3], [])) - # ChannelMarker - self._channel_marker = [] + self._channel_tuples = [] + self._channel_tuples.append(TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2])) + self._channel_tuples.append(TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4])) if external_trigger: raise NotImplementedError() # pragma: no cover - if reset: - self.send_cmd(':RES') - self.initialize() # TODO: ggf. ueberarbeiten - # Trotzdem noch eine Ampitude Methode? - - # Trotzdem noch eine Offset Methode? - def cleanup(self) -> None: - pass # TODO: to implement + """This will be called automatically in __del__""" + # TODO: this method shoud be called in __del__ + # for tuple in self.channel_tuples: + # tuple.cleanup() + # for channel in self.channels: + # channel.clear() + # for marker_ch in self.marker_channels: + # marker_ch.clear() + + # is this needed? + # self.channels.clear() + # self.marker_channels.clear() + # self.channel_tuples.clear() - # TODO: Kann Collection auch noch spezialiseirt werden? @property def channels(self) -> Collection["TaborChannel"]: return self._channels @property - def marker_channels(self) -> Collection["AWGMarkerChannel"]: + def marker_channels(self) -> Collection["TaborMarkerChannel"]: return self._channel_marker @property - def channel_tuples(self) -> Collection["AWGChannelTuple"]: + def channel_tuples(self) -> Collection["TaborChannelTuple"]: return self._channel_tuples @property @@ -182,7 +617,7 @@ def all_devices(self) -> Sequence[teawg.TEWXAwg]: return (self._instr,) + self._mirrors def send_cmd(self, cmd_str, paranoia_level=None): - print(self.all_devices) + print(cmd_str) for instr in self.all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) @@ -196,15 +631,15 @@ def send_binary_data(self, pref, bin_dat, paranoia_level=None): for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) - def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): + def download_segment_lengths(self, seg_len_list, pref=":SEGM:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) - def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): + def download_sequencer_table(self, seq_table, pref=":SEQ:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) - def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): + def download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) @@ -222,62 +657,65 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: cmd_str = cmd_str.rstrip() if len(cmd_str) > 0: - ask_str = cmd_str + '; *OPC?; :SYST:ERR?' + ask_str = cmd_str + "; *OPC?; :SYST:ERR?" else: - ask_str = '*OPC?; :SYST:ERR?' + ask_str = "*OPC?; :SYST:ERR?" - *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(';') + *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(";") - error_code, error_msg = error_code_msg.split(',') + error_code, error_msg = error_code_msg.split(",") error_code = int(error_code) if error_code != 0: - _ = self._visa_inst.ask('*CLS; *OPC?') + _ = self._visa_inst.ask("*CLS; *OPC?") if error_code == -450: # query queue overflow self.send_cmd(cmd_str) else: - raise RuntimeError('Cannot execute command: {}\n{}: {}'.format(cmd_str, error_code, error_msg)) + raise RuntimeError("Cannot execute command: {}\n{}: {}".format(cmd_str, error_code, error_msg)) assert len(answers) == 0 def get_status_table(self) -> Dict[str, Union[str, float, int]]: - """Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame - - Returns: - An ordered dictionary with the results - """ - name_query_type_list = [('channel', ':INST:SEL?', int), - ('coupling', ':OUTP:COUP?', str), - ('volt_dc', ':SOUR:VOLT:LEV:AMPL:DC?', float), - ('volt_hv', ':VOLT:HV?', float), - ('offset', ':VOLT:OFFS?', float), - ('outp', ':OUTP?', str), - ('mode', ':SOUR:FUNC:MODE?', str), - ('shape', ':SOUR:FUNC:SHAPE?', str), - ('dc_offset', ':SOUR:DC?', float), - ('freq_rast', ':FREQ:RAST?', float), - - ('gated', ':INIT:GATE?', str), - ('continuous', ':INIT:CONT?', str), - ('continuous_enable', ':INIT:CONT:ENAB?', str), - ('continuous_source', ':INIT:CONT:ENAB:SOUR?', str), - ('marker_source', ':SOUR:MARK:SOUR?', str), - ('seq_jump_event', ':SOUR:SEQ:JUMP:EVEN?', str), - ('seq_adv_mode', ':SOUR:SEQ:ADV?', str), - ('aseq_adv_mode', ':SOUR:ASEQ:ADV?', str), - - ('marker', ':SOUR:MARK:SEL?', int), - ('marker_high', ':MARK:VOLT:HIGH?', str), - ('marker_low', ':MARK:VOLT:LOW?', str), - ('marker_width', ':MARK:WIDT?', int), - ('marker_state', ':MARK:STAT?', str)] + """ + Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame + + Returns: + An ordered dictionary with the results + """ + name_query_type_list = [("channel", ":INST:SEL?", int), + ("coupling", ":OUTP:COUP?", str), + ("volt_dc", ":SOUR:VOLT:LEV:AMPL:DC?", float), + ("volt_hv", ":VOLT:HV?", float), + ("offset", ":VOLT:OFFS?", float), + ("outp", ":OUTP?", str), + ("mode", ":SOUR:FUNC:MODE?", str), + ("shape", ":SOUR:FUNC:SHAPE?", str), + ("dc_offset", ":SOUR:DC?", float), + ("freq_rast", ":FREQ:RAST?", float), + + ("gated", ":INIT:GATE?", str), + ("continuous", ":INIT:CONT?", str), + ("continuous_enable", ":INIT:CONT:ENAB?", str), + ("continuous_source", ":INIT:CONT:ENAB:SOUR?", str), + ("marker_source", ":SOUR:MARK:SOUR?", str), + ("seq_jump_event", ":SOUR:SEQ:JUMP:EVEN?", str), + ("seq_adv_mode", ":SOUR:SEQ:ADV?", str), + ("aseq_adv_mode", ":SOUR:ASEQ:ADV?", str), + + ("marker", ":SOUR:MARK:SEL?", int), + ("marker_high", ":MARK:VOLT:HIGH?", str), + ("marker_low", ":MARK:VOLT:LOW?", str), + ("marker_width", ":MARK:WIDT?", int), + ("marker_state", ":MARK:STAT?", str)] data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): # TODO: select Channel und Marker fehlen im Device - self._select_channel(ch) - self.select_marker((ch - 1) % 2 + 1) + self.channels[ch - 1].select() + # self._select_channel(ch) + self.marker_channels[(ch - 1) % 2].select() + # self.select_marker((ch - 1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data @@ -286,35 +724,35 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover - # TODO: soll man ein Channel Objekt oder eine ChannelNummer mitgeben? -> intern, das was am besten fuer die Umsetzung ist - def _select_channel(self, channel_nr: int) -> None: - if channel_nr not in range(1, len(self.channels)): - raise TaborException('Invalid channel: {}'.format(channel_nr)) - - self.send_cmd(':INST:SEL {channel}'.format(channel=channel_nr)) - - def _select_marker(self, marker_nr: int) -> None: - # TODO: right name for the parameter? - """Select marker a marker of the currently active channel pair.""" - if marker_nr not in range(1, len(self.channel_tuples[1].marker_channels)): - raise TaborException('Invalid marker: {}'.format(marker_nr)) - - self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker_nr)) + # # TODO: soll man ein Channel Objekt oder eine ChannelNummer mitgeben? -> intern, das was am besten fuer die Umsetzung ist + # def _select_channel(self, channel_nr: int) -> None: + # if channel_nr not in range(1, len(self.channels)): + # raise TaborException("Invalid channel: {}".format(channel_nr)) + # + # self.send_cmd(":INST:SEL {channel}".format(channel=channel_nr)) + # + # def _select_marker(self, marker_nr: int) -> None: + # # TODO: right name for the parameter? + # """Select marker a marker of the currently active channel pair.""" + # if marker_nr not in range(1, len(self.channel_tuples[1].marker_channels)): + # raise TaborException("Invalid marker: {}".format(marker_nr)) + # + # self.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=marker_nr)) # wird die Methode noch gebraucht? def _sample_rate(self, channel_nr: int) -> int: if channel_nr not in range(1, len(self.channels)): - raise TaborException('Invalid channel: {}'.format(channel_nr)) + raise TaborException("Invalid channel: {}".format(channel_nr)) return int(self.channels[channel_nr].channel_tuple.sample_rate) - # def setter_sample_rate implementieren? + # TODO: setter_sample_rate implementieren? def enable(self) -> None: - self.send_cmd(':ENAB') + self.send_cmd(":ENAB") def abort(self) -> None: - self.send_cmd(':ABOR') + self.send_cmd(":ABOR") def initialize(self) -> None: # 1. Select channel @@ -328,9 +766,9 @@ def initialize(self) -> None: ":INIT:GATE OFF; :INIT:CONT ON; " ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") - self.send_cmd(':INST:SEL 1') + self.send_cmd(":INST:SEL 1") self.send_cmd(setup_command) - self.send_cmd(':INST:SEL 3') + self.send_cmd(":INST:SEL 3") self.send_cmd(setup_command) def reset(self) -> None: @@ -338,10 +776,10 @@ def reset(self) -> None: self._coupled = None self.initialize() for channel_tuple in self.channel_tuples: - channel_tuple.clear() + channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: - self.send_cmd(':TRIG') + self.send_cmd(":TRIG") def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: for device in self.all_devices: @@ -351,7 +789,7 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: return device else: return device - raise TaborException('No device capable of device data read') + raise TaborException("No device capable of device data read") ######################################################################################################################## @@ -366,7 +804,7 @@ def __init__(self, channel: "TaborChannel"): @property def offset(self) -> float: return float( - self._parent.device.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=self._parent.idn))) + self._parent.device.send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent.idn))) @offset.setter def offset(self, offset: float) -> None: @@ -374,17 +812,18 @@ def offset(self, offset: float) -> None: @property def amplitude(self) -> float: - coupling = self._parent.device.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=self._parent.idn)) - if coupling == 'DC': - return float(self._parent.device.send_query(':VOLT?')) - elif coupling == 'HV': - return float(self._parent.device.send_query(':VOLT:HV?')) + coupling = self._parent.device.send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent.idn)) + if coupling == "DC": + return float(self._parent.device.send_query(":VOLT?")) + elif coupling == "HV": + return float(self._parent.device.send_query(":VOLT:HV?")) else: - raise TaborException('Unknown coupling: {}'.format(coupling)) + raise TaborException("Unknown coupling: {}".format(coupling)) @amplitude.setter def amplitude(self, amplitude: float) -> None: - pass # TODO: to implement + self._parent.device.send_cmd(":INST:SEL {channel}; :OUTP:COUP DC".format(channel=self._parent.idn)) + self._parent.device.send_cmd(":VOLT {amp}".format(amp=amplitude)) @property def amplitude_offset_handling(self) -> str: @@ -395,6 +834,22 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: pass # TODO: to implement +class TaborChannelActivatable(ActivatableChannels): + def __init__(self, marker_channel: "TaborMarkerChannel"): + super().__init__() + self.parent = marker_channel + + @property + def status(self) -> bool: + pass # TODO: to implement + + @status.setter + def status(self, channel_status: bool) -> None: + command_string = ":INST:SEL {ch_id}; :OUTP {output}".format(ch_id=self.parent.idn, + output="ON" if channel_status else "OFF") + self.parent.device.send_cmd(command_string) + + # Implementation class TaborChannel(AWGChannel): def __init__(self, idn: int, device: TaborDevice): @@ -404,16 +859,21 @@ def __init__(self, idn: int, device: TaborDevice): self.add_feature(TaborOffsetAmplitude(self)) + _channel_tuple: "TaborChannelTuple" + @property def device(self) -> TaborDevice: return self._device @property def channel_tuple(self) -> Optional[AWGChannelTuple]: - pass + return self._channel_tuple def _set_channel_tuple(self, channel_tuple) -> None: - pass + self._channel_tuple = channel_tuple + + def select(self) -> None: + self.device.send_cmd(":INST:SEL {channel}".format(channel=self.idn)) ######################################################################################################################## @@ -427,13 +887,38 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): self._armed_program = None self._parent = channel_tuple + @property + def programs(self) -> Set[str]: + pass # TODO: to implement + + @with_configuration_guard + @with_select def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: - pass # TODO: to implement + """ + Upload a program to the AWG. + + The policy is to prefer amending the unknown waveforms to overwriting old ones. + """ + if len(channels) != self.num_channels: + raise ValueError("Channel ID not specified") + if len(markers) != self.num_markers: + raise ValueError("Markers not specified") + if len(voltage_transformation) != self.num_channels: + raise ValueError("Wrong number of voltage transformations") + + # adjust program to fit criteria + # TODO: samplerate wird jetzt anders verwendet, also nicht mehr im device selber - Ist die sample rate ueber alle Channel gleich oder wieso wird oft die von channel[0] benutzt + sample_rate = self._parent.device.sample_rate(self._channels[0]) + make_compatible(program, + minimal_waveform_length=192, + waveform_quantum=16, + sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) def remove(self, name: str) -> None: - """Remove a program from the AWG. + """ + Remove a program from the AWG. Also discards all waveforms referenced only by the program identified by name. @@ -444,14 +929,38 @@ def remove(self, name: str) -> None: self._parent.cleanup() def clear(self) -> None: - pass # TODO: to implement + """Delete all segments and clear memory""" - def arm(self, name: Optional[str]) -> None: - self._parent._arm() + # self._parent.device.select_channel(self._channels[0]) + self._parent.device.channels[0].select() + self._parent.device.send_cmd(':TRAC:DEL:ALL') + self._parent.device.send_cmd(':SOUR:SEQ:DEL:ALL') + self._parent.device.send_cmd(':ASEQ:DEL') - @property - def programs(self) -> Set[str]: - pass # TODO: to implement + self._parent.device.send_cmd(':TRAC:DEF 1, 192') + self._parent.device.send_cmd(':TRAC:SEL 1') + self._parent.device.send_cmd(':TRAC:MODE COMB') + self._parent.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._parent._idle_segment.get_as_binary()) + + self._parent._segment_lengths = 192 * np.ones(1, dtype=np.uint32) + self._parent._segment_capacity = 192 * np.ones(1, dtype=np.uint32) + self._parent._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._parent._idle_segment) + self._parent._segment_references = np.ones(1, dtype=np.uint32) + + self._parent._advanced_sequence_table = [] + self._parent._sequencer_tables = [] + + self._parent._known_programs = dict() + self._parent.change_armed_program(None) + + pass # TODO: check/finish implementation + + @with_select + def arm(self, name: Optional[str]) -> None: + if self._parent._current_program == name: + self._parent.device.send_cmd("SEQ:SEL 1") + else: + self._parent.change_armed_program(name) class TaborChannelTuple(AWGChannelTuple): @@ -467,23 +976,27 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self._is_in_config_mode = False # TODO: Ueberpreufung macht keinen Sinn - #if channels not in self._device.channel_tuples: - # raise ValueError('Invalid channel pair: {}'.format(channels)) + # if channels not in self._device.channel_tuples: + # raise ValueError("Invalid channel pair: {}".format(channels)) self._channels = tuple(channels) - self._marker_channels = tuple(marker_channels) + for channel in self.channels: + channel._set_channel_tuple(self) + for marker_ch in self.marker_channels: + marker_ch._set_channel_tuple(self) + self.add_feature(TaborProgramManagement(self)) - #TODO: Kommentar beenden - """ + # TODO: Kommentar beenden + self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, output_offset=0., resolution=14), voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, output_offset=0., resolution=14), None, None) - """ + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -501,36 +1014,51 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self[TaborProgramManagement].clear() + def select(self) -> None: + # TODO: keep this? + self.channels[0].select() @property def device(self) -> TaborDevice: return self._device @property - def channels(self) -> Collection["AWGChannel"]: + def channels(self) -> Collection["TaborChannel"]: return self._channels @property - def marker_channels(self) -> Collection["AWGMarkerChannel"]: + def marker_channels(self) -> Collection["TaborMarkerChannel"]: return self._marker_channels @property def sample_rate(self) -> float: - pass # TODO: to implement + # haben wirklich alle Channel eines Tupels die selbe sample rate? + return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) + """ def select(self) -> None: pass # TODO: to implement + """ @property def total_capacity(self) -> int: - return int(self.device.dev_properties['max_arb_mem']) // 2 + return int(self.device.dev_properties["max_arb_mem"]) // 2 def free_program(self, name: str) -> TaborProgramMemory: - pass # TODO: to implement + if name is None: + raise TaborException('Removing "None" program is forbidden.') + program = self._known_programs.pop(name) + self._segment_references[program.waveform_to_segment] -= 1 + if self._current_program == name: + self.change_armed_program(None) + return program - def _restore_program(self) -> None: - pass # TODO: to implement + def _restore_program(self, name: str, program: TaborProgram) -> None: + if name in self._known_programs: + raise ValueError('Program cannot be restored as it is already known.') + self._segment_references[program.waveform_to_segment] += 1 + self._known_programs[name] = program @property def _segment_reserved(self) -> np.ndarray: @@ -552,9 +1080,11 @@ def _free_points_at_end(self) -> int: def read_waveforms(self) -> List[np.ndarray]: device = self.device.get_readable_device(simulator=True) - old_segment = device.send_query(':TRAC:SEL?') + old_segment = device.send_query(":TRAC:SEL?") waveforms = [] - uploaded_waveform_indices = np.flatnonzero(self._segment_references) + 1 + uploaded_waveform_indices = np.flatnonzero( + self._segment_references) + 1 # uploaded_waveform_indices sind gleich wie im alten Treiber + for segment in uploaded_waveform_indices: device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) @@ -565,15 +1095,19 @@ def read_waveforms(self) -> List[np.ndarray]: def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: device = self.device.get_readable_device(simulator=True) - old_sequence = device.send_query(':SEQ:SEL?') + old_sequence = device.send_query(":SEQ:SEL?") sequences = [] - uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 + uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 # array ist im alten Treiber gleich for sequence in uploaded_sequence_indices: device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences + @with_select + def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + return self.device.get_readable_device(simulator=True).read_adv_seq_table() + # upload im Feature def read_complete_program(self) -> PlottableProgram: @@ -585,19 +1119,105 @@ def read_complete_program(self) -> PlottableProgram: def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ np.ndarray, np.ndarray, np.ndarray]: - pass # TODO: to implement + """ + 1. Find known segments + 2. Find empty spaces with fitting length + 3. Find empty spaces with bigger length + 4. Amend remaining segments + :param segments: + :param segment_lengths: + :return: + """ + + # TODO: ueberpreufen + + segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) + + waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) + + # separate into known and unknown + unknown = (waveform_to_segment == -1) + known = ~unknown + + known_pos_in_memory = waveform_to_segment[known] + + assert len(known_pos_in_memory) == 0 or np.all( + self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + + new_reference_counter = self._segment_references.copy() + new_reference_counter[known_pos_in_memory] += 1 + + to_upload_size = np.sum(segment_lengths[unknown] + 16) + free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) + if free_points_in_total < to_upload_size: + raise MemoryError('Not enough free memory', + free_points_in_total, + to_upload_size, + self._free_points_in_total) + + to_amend = cast(np.ndarray, unknown) + to_insert = np.full(len(segments), fill_value=-1, dtype=np.int64) + + reserved_indices = np.flatnonzero(new_reference_counter > 0) + first_free = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + + free_segments = new_reference_counter[:first_free] == 0 + free_segment_count = np.sum(free_segments) + + # look for a free segment place with the same length + for segment_idx in np.flatnonzero(to_amend): + if free_segment_count == 0: + break + + pos_of_same_length = np.logical_and(free_segments, + segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + idx_same_length = np.argmax(pos_of_same_length) + if pos_of_same_length[idx_same_length]: + free_segments[idx_same_length] = False + free_segment_count -= 1 + + to_amend[segment_idx] = False + to_insert[segment_idx] = idx_same_length + + # try to find places that are larger than the segments to fit in starting with the large segments and large + # free spaces + segment_indices = np.flatnonzero(to_amend)[np.argsort(segment_lengths[to_amend])[::-1]] + capacities = self._segment_capacity[:first_free] + for segment_idx in segment_indices: + free_capacities = capacities[free_segments] + free_segments_indices = np.flatnonzero(free_segments)[np.argsort(free_capacities)[::-1]] + + if len(free_segments_indices) == 0: + break + + fitting_segment = np.argmax((free_capacities >= segment_lengths[segment_idx])[::-1]) + fitting_segment = free_segments_indices[fitting_segment] + if self._segment_capacity[fitting_segment] >= segment_lengths[segment_idx]: + free_segments[fitting_segment] = False + to_amend[segment_idx] = False + to_insert[segment_idx] = fitting_segment + + free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) + if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: + raise MemoryError('Fragmentation does not allow upload.', + np.sum(segment_lengths[to_amend] + 16), + free_points_at_end, + self._free_points_at_end) + + return waveform_to_segment, to_amend, to_insert @with_select @with_configuration_guard def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: # TODO: Why is the proptery for device not used? if self._segment_references[segment_index] > 0: - raise ValueError('Reference count not zero') + raise ValueError("Reference count not zero") if segment.num_points > self._segment_capacity[segment_index]: - raise ValueError('Cannot upload segment here.') + raise ValueError("Cannot upload segment here.") segment_no = segment_index + 1 +<<<<<<< HEAD self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points @@ -607,7 +1227,7 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + self.device.send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) self._segment_references[segment_index] = 1 self._segment_hashes[segment_index] = hash(segment) @@ -645,7 +1265,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(':TRAC:DEF {},{}'.format(i + 1, segment_lengths[i])) + self.device.send_cmd(":TRAC:DEF {},{}".format(i + 1, segment_lengths[i])) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -670,13 +1290,14 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i + 1) + self.device.send_cmd("; ".join("TRAC:DEL {}".format(i + 1) for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) except Exception as e: - raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e + raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e # remove im Feature + @with_configuration_guard def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: """ Joins the given commands into one and executes it with configuration guard. @@ -746,6 +1367,8 @@ def _arm(self, name: str) -> None: else: self.change_armed_program(name) + # arm im Feature + def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table @@ -755,31 +1378,76 @@ def set_program_sequence_table(self, name, new_sequence_table): @with_select @with_configuration_guard def change_armed_program(self, name: Optional[str]) -> None: - pass # TODO: to implement + if name is None: + sequencer_tables = [self._idle_sequence_table] + advanced_sequencer_table = [(1, 1, 0)] + else: + waveform_to_segment_index, program = self._known_programs[name] + waveform_to_segment_number = waveform_to_segment_index + 1 + + # translate waveform number to actual segment + sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) + for (rep_count, wf_index, jump_flag) in sequencer_table] + for sequencer_table in program.get_sequencer_tables()] + + # insert idle sequence + sequencer_tables = [self._idle_sequence_table] + sequencer_tables + + # adjust advanced sequence table entries by idle sequence table offset + advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) + for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] + + if program.waveform_mode == TaborSequencing.SINGLE: + assert len(advanced_sequencer_table) == 1 + assert len(sequencer_tables) == 2 + + while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: + assert advanced_sequencer_table[0][0] == 1 + sequencer_tables[1].append((1, 1, 0)) + + # insert idle sequence in advanced sequence table + advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table + + while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: + advanced_sequencer_table.append((1, 1, 0)) + + # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs + self.device.send_cmd('SEQ:DEL:ALL') + self._sequencer_tables = [] + self.device.send_cmd('ASEQ:DEL') + self._advanced_sequence_table = [] + + # download all sequence tables + for i, sequencer_table in enumerate(sequencer_tables): + self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) + self.device.download_sequencer_table(sequencer_table) + self._sequencer_tables = sequencer_tables + self.device.send_cmd('SEQ:SEL 1') + + self.device.download_adv_seq_table(advanced_sequencer_table) + self._advanced_sequence_table = advanced_sequencer_table + + self._current_program = name @with_select def run_current_program(self) -> None: if self._current_program: self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) else: - raise RuntimeError('No program active') + raise RuntimeError("No program active") @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(program for program in self._known_programs.keys()) - @property - def sample_rate(self) -> float: - pass # TODO: to implement - @property def num_channels(self) -> int: return len(self.channels) @property def num_markers(self) -> int: - pass # TODO: to implement + return len(self.marker_channels) def _enter_config_mode(self) -> None: """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the @@ -794,11 +1462,15 @@ def _enter_config_mode(self) -> None: if self.device.is_coupled(): out_cmd = ':OUTP:ALL OFF' else: - self.device.send_cmd(':INST:SEL {}; :OUTP OFF; :INST:SEL {}; :OUTP OFF'.format(*self._channels)) + cmd = "" + for channel in self.channels: + cmd = cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) + self.device.send_cmd(cmd[:-1]) + + for marker_ch in self.marker_channels: + marker_ch[TaborMarkerChannelActivatable].status = False - self.set_marker_state(0, False) - self.set_marker_state(1, False) - self.device.send_cmd(':SOUR:FUNC:MODE FIX') + self.device.send_cmd(":SOUR:FUNC:MODE FIX") wf_mode_cmd = ':SOUR:FUNC:MODE FIX' @@ -809,28 +1481,90 @@ def _enter_config_mode(self) -> None: @with_select def _exit_config_mode(self) -> None: - pass # TODO: to implement + """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" + + # TODO: change implementation for channel synchronisation feature + + if self.device.send_query(":INST:COUP:STAT?") == "ON": + # Coupled -> switch all channels at once + other_channel_tuple: TaborChannelTuple + if self.channels == self.device.channel_tuples[0].channels: + other_channel_tuple = self.device.channel_tuples[1] + else: + other_channel_tuple = self.device.channel_tuples[0] + + if not other_channel_tuple._is_in_config_mode: + self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device.send_cmd(":SEQ:SEL 1") + self.device.send_cmd(":OUTP:ALL ON") + + else: + self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device.send_cmd(":SEQ:SEL 1") + + cmd = "" + for channel in self.channels: + cmd = cmd + ":INST:SEL {}; :OUTP ON;".format(channel.idn) + self.device.send_cmd(cmd[:-1]) + + # self.device.send_cmd(":INST:SEL {}; :OUTP ON; :INST:SEL {}; :OUTP ON".format(*self._channels)) + + #self.marker_channels[0][TaborMarkerChannelActivatable].status = True # TODO: Fehler hier wird nichts gemacht!!! + #self.marker_channels[1][TaborMarkerChannelActivatable].status = True + + for marker_ch in self.marker_channels: + marker_ch[TaborMarkerChannelActivatable].status = True + + self._is_in_config_mode = False ######################################################################################################################## # Marker Channel ######################################################################################################################## +# Features +class TaborMarkerChannelActivatable(ActivatableChannels): + def __init__(self, marker_channel: "TaborMarkerChannel"): + super().__init__() + self._parent = marker_channel + + @property + def status(self) -> bool: + raise NotImplementedError + + @status.setter + def status(self, channel_state: bool) -> None: + command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}" + command_string = command_string.format( + channel=self._parent.channel_tuple.channels[0].idn, + # TODO: Bearbeiten - self._parent.channel_tuple.channels[0].idn, device durch channel_tuple ersetzt + marker=self._parent.channel_tuple.marker_channels.index(self._parent)+1, # marker=self._parent.idn, TODO: bei + active="ON" if channel_state else "OFF") + self._parent.device.send_cmd(command_string) + + +# Implementation class TaborMarkerChannel(AWGMarkerChannel): - def __init__(self, idn: int, device: TaborDevice, channel_tuple: TaborChannelTuple): + def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) self._device = device - self._channel_tuple = channel_tuple + + self.add_feature(TaborMarkerChannelActivatable(self)) + + _channel_tuple: "TaborChannelTuple" @property - def device(self) -> AWGDevice: - pass + def device(self) -> TaborDevice: + return self._device @property - def channel_tuple(self) -> Optional[AWGChannelTuple]: - pass + def channel_tuple(self) -> Optional[TaborChannelTuple]: + return self._channel_tuple def _set_channel_tuple(self, channel_tuple) -> None: - pass + self._channel_tuple = channel_tuple + + def select(self) -> None: + self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=self.idn)) ######################################################################################################################## @@ -841,4 +1575,14 @@ class TaborException(Exception): class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" - pass # TODO: to implement + + def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): + super().__init__(*args) + self.device = device + + def reset_device(self): + if isinstance(self.device, TaborDevice): + self.device.reset() + elif isinstance(self.device, TaborChannelTuple): + self.device.cleanup() + self.device.clear() diff --git a/tests/backward_compatibility/tabor_backward_compatibility_tests.py b/tests/backward_compatibility/tabor_backward_compatibility_tests.py index 0e78099b5..18f43e5fc 100644 --- a/tests/backward_compatibility/tabor_backward_compatibility_tests.py +++ b/tests/backward_compatibility/tabor_backward_compatibility_tests.py @@ -5,7 +5,7 @@ import importlib.util import sys -from tests.hardware.tabor_simulator_based_tests import TaborSimulatorManager +from tests.hardware.old_tabor_simulator_based_tests import TaborSimulatorManager from tests.hardware.dummy_devices import DummyDAC from qupulse.serialization import Serializer, FilesystemBackend, PulseStorage diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py new file mode 100644 index 000000000..b2fab40bb --- /dev/null +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -0,0 +1,238 @@ +import unittest +import subprocess +import time +import platform +import os + +import pytabor +import numpy as np + +from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, PlottableProgram + + +class TaborSimulatorManager: + def __init__(self, + simulator_executable='WX2184C.exe', + simulator_path=os.path.realpath(os.path.dirname(__file__))): + self.simulator_executable = simulator_executable + self.simulator_path = simulator_path + + self.started_simulator = False + + self.simulator_process = None + self.instrument = None + + def kill_running_simulators(self): + command = 'Taskkill', '/IM {simulator_executable}'.format(simulator_executable=self.simulator_executable) + try: + subprocess.run([command], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except FileNotFoundError: + pass + + @property + def simulator_full_path(self): + return os.path.join(self.simulator_path, self.simulator_executable) + + def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_time=30): + if try_connecting_to_existing_simulator: + if pytabor.open_session('127.0.0.1') is not None: + return + + if not os.path.isfile(self.simulator_full_path): + raise RuntimeError('Cannot locate simulator executable.') + + self.kill_running_simulators() + + self.simulator_process = subprocess.Popen([self.simulator_full_path, '/switch-on', '/gui-in-tray']) + + start = time.time() + while pytabor.open_session('127.0.0.1') is None: + if self.simulator_process.returncode: + raise RuntimeError('Simulator exited with return code {}'.format(self.simulator_process.returncode)) + if time.time() - start > max_wait_time: + raise RuntimeError('Could not connect to simulator') + time.sleep(0.1) + + def connect(self): + self.instrument = TaborDevice('127.0.0.1', + reset=True, + paranoia_level=2) + + if self.instrument.main_instrument.visa_inst is None: + raise RuntimeError('Could not connect to simulator') + return self.instrument + + def disconnect(self): + for device in self.instrument.all_devices: + device.close() + self.instrument = None + + def __del__(self): + if self.started_simulator and self.simulator_process: + self.simulator_process.kill() + + +@unittest.skipIf(platform.system() != 'Windows', "Simulator currently only available on Windows :(") +class TaborSimulatorBasedTest(unittest.TestCase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.instrument = None + + @classmethod + def setUpClass(cls): + cls.simulator_manager = TaborSimulatorManager('WX2184C.exe', os.path.dirname(__file__)) + try: + cls.simulator_manager.start_simulator() + except RuntimeError as err: + raise unittest.SkipTest(*err.args) from err + + @classmethod + def tearDownClass(cls): + del cls.simulator_manager + + def setUp(self): + self.instrument = self.simulator_manager.connect() + + def tearDown(self): + self.instrument.reset() + self.simulator_manager.disconnect() + + +class TaborAWGRepresentationTests(TaborSimulatorBasedTest): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def test_sample_rate(self): + for ch in (1, 2, 3, 4): + self.assertIsInstance(self.instrument.sample_rate(ch), int) + + with self.assertRaises(TaborException): + self.instrument.sample_rate(0) + + self.instrument.send_cmd(':INST:SEL 1') + self.instrument.send_cmd(':FREQ:RAST 2.3e9') + + self.assertEqual(2300000000, self.instrument.sample_rate(1)) + + def test_amplitude(self): + for ch in (1, 2, 3, 4): + self.assertIsInstance(self.instrument.amplitude(ch), float) + + with self.assertRaises(TaborException): + self.instrument.amplitude(0) + + self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') + self.instrument.send_cmd(':VOLT 0.7') + + self.assertAlmostEqual(.7, self.instrument.amplitude(1)) + + def test_select_marker(self): + with self.assertRaises(TaborException): + self.instrument.select_marker(6) + + self.instrument.select_marker(2) + selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.assertEqual(selected, '2') + + self.instrument.select_marker(1) + selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.assertEqual(selected, '1') + + def test_select_channel(self): + with self.assertRaises(TaborException): + self.instrument.select_channel(6) + + self.instrument.select_channel(1) + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') + + self.instrument.select_channel(4) + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') + + +class TaborMemoryReadTests(TaborSimulatorBasedTest): + def setUp(self): + super().setUp() + + ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) + ramp_down = ramp_up[::-1] + zero = np.ones(192, dtype=np.uint16) * 2**13 + sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) + + self.segments = [TaborSegment(ramp_up, ramp_up, None, None), + TaborSegment(ramp_down, zero, None, None), + TaborSegment(sine, sine, None, None)] + + self.zero_segment = TaborSegment(zero, zero, None, None) + + # program 1 + self.sequence_tables = [[(10, 0, 0), (10, 1, 0), (10, 0, 0), (10, 1, 0)], + [(1, 0, 0), (1, 1, 0), (1, 0, 0), (1, 1, 0)]] + self.advanced_sequence_table = [(1, 1, 0), (1, 2, 0)] + + self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') + + def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): + class DummyProgram: + @staticmethod + def get_sequencer_tables(): + return sequencer_tables + + @staticmethod + def get_advanced_sequencer_table(): + return advanced_sequencer_table + + markers = (None, None) + channels = (1, 2) + + waveform_mode = mode + + self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) + self.channel_pair.change_armed_program('dummy_program') + + def test_read_waveforms(self): + self.channel_pair._amend_segments(self.segments) + + waveforms = self.channel_pair.read_waveforms() + + segments = [TaborSegment.from_binary_segment(waveform) + for waveform in waveforms] + + expected = [self.zero_segment, *self.segments] + + for ex, r in zip(expected, segments): + ex1, ex2 = ex.data_a, ex.data_b + r1, r2 = r.data_a, r.data_b + np.testing.assert_equal(ex1, r1) + np.testing.assert_equal(ex2, r2) + + self.assertEqual(expected, segments) + + def test_read_sequence_tables(self): + self.channel_pair._amend_segments(self.segments) + self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) + + sequence_tables = self.channel_pair.read_sequence_tables() + + actual_sequece_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) + for rep, index, jump in table] + for table in self.sequence_tables] + + expected = list(tuple(np.asarray(d) + for d in zip(*table)) + for table in actual_sequece_tables) + + np.testing.assert_equal(sequence_tables, expected) + + def test_read_advanced_sequencer_table(self): + self.channel_pair._amend_segments(self.segments) + self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) + + actual_advanced_table = [(1, 1, 1)] + [(rep, idx+1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + + expected = list(np.asarray(d) + for d in zip(*actual_advanced_table)) + + advanced_table = self.channel_pair.read_advanced_sequencer_table() + np.testing.assert_equal(advanced_table, expected) diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index cff5966b5..0731549b5 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -7,7 +7,8 @@ import pytabor import numpy as np -from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, PlottableProgram +from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ + TaborOffsetAmplitude class TaborSimulatorManager: @@ -20,7 +21,7 @@ def __init__(self, self.started_simulator = False self.simulator_process = None - self.instrument = None + self.instrument: TaborDevice = None def kill_running_simulators(self): command = 'Taskkill', '/IM {simulator_executable}'.format(simulator_executable=self.simulator_executable) @@ -54,8 +55,9 @@ def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_ti raise RuntimeError('Could not connect to simulator') time.sleep(0.1) - def connect(self): - self.instrument = TaborDevice('127.0.0.1', + def connect(self) -> TaborDevice: + self.instrument = TaborDevice("testDevice", + "127.0.0.1", reset=True, paranoia_level=2) @@ -78,7 +80,8 @@ class TaborSimulatorBasedTest(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.instrument = None + # self.instrument = None + self.instrument: TaborDevice @classmethod def setUpClass(cls): @@ -115,49 +118,51 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_sample_rate(self): - for ch in (1, 2, 3, 4): - self.assertIsInstance(self.instrument.sample_rate(ch), int) + # for ch in (1, 2, 3, 4): + # self.assertIsInstance(self.instrument.sample_rate(ch), int) + # for ch_tuple in self.instrument.channel_tuples: + # self.assertIsInstance(ch_tuple.sample_rate,int) - with self.assertRaises(TaborException): - self.instrument.sample_rate(0) + # with self.assertRaises(TaborException): + # self.instrument.sample_rate(0) self.instrument.send_cmd(':INST:SEL 1') self.instrument.send_cmd(':FREQ:RAST 2.3e9') - self.assertEqual(2300000000, self.instrument.sample_rate(1)) + # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) def test_amplitude(self): - for ch in (1, 2, 3, 4): - self.assertIsInstance(self.instrument.amplitude(ch), float) + # for ch in (1, 2, 3, 4): + # self.assertIsInstance(self.instrument.amplitude(ch), float) - with self.assertRaises(TaborException): - self.instrument.amplitude(0) + for channel in self.instrument.channels: + self.assertIsInstance(channel[TaborOffsetAmplitude].amplitude, float) self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') self.instrument.send_cmd(':VOLT 0.7') - self.assertAlmostEqual(.7, self.instrument.amplitude(1)) + self.assertAlmostEqual(.7, self.instrument.channels[0][TaborOffsetAmplitude].amplitude) def test_select_marker(self): - with self.assertRaises(TaborException): - self.instrument.select_marker(6) + with self.assertRaises(IndexError): + self.instrument.marker_channels[6].select() - self.instrument.select_marker(2) + self.instrument.marker_channels[1].select() selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') - self.instrument.select_marker(1) + self.instrument.marker_channels[0].select() selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): - with self.assertRaises(TaborException): - self.instrument.select_channel(6) + with self.assertRaises(IndexError): + self.instrument.channels[6].select() - self.instrument.select_channel(1) + self.instrument.channels[0].select() self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') - self.instrument.select_channel(4) + self.instrument.channels[3].select() self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') @@ -165,10 +170,10 @@ class TaborMemoryReadTests(TaborSimulatorBasedTest): def setUp(self): super().setUp() - ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) + ramp_up = np.linspace(0, 2 ** 14 - 1, num=192, dtype=np.uint16) ramp_down = ramp_up[::-1] - zero = np.ones(192, dtype=np.uint16) * 2**13 - sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) + zero = np.ones(192, dtype=np.uint16) * 2 ** 13 + sine = ((np.sin(np.linspace(0, 2 * np.pi, 192 + 64)) + 1) / 2 * (2 ** 14 - 1)).astype(np.uint16) self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), TaborSegment.from_sampled(ramp_down, zero, None, None), @@ -184,7 +189,9 @@ def setUp(self): self.sequence_tables = self.to_new_sequencer_tables(self.sequence_tables_raw) self.advanced_sequence_table = self.to_new_advanced_sequencer_table(self.advanced_sequence_table) - self.channel_pair = TaborChannelPair(self.instrument, (1, 2), 'tabor_unit_test') + # TODO: darf man das so ersetzen + # self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') + self.channel_pair = self.instrument.channel_tuples[0] def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): @@ -214,6 +221,7 @@ def update_volatile_parameters(parameters): def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) + #waveforms sind schon nicht gleich zum alten Treiber waveforms = self.channel_pair.read_waveforms() segments = [TaborSegment.from_binary_segment(waveform) @@ -249,12 +257,13 @@ def test_read_advanced_sequencer_table(self): self.channel_pair._amend_segments(self.segments) self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) - actual_advanced_table = [(1, 1, 1)] + [(rep, idx+1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) advanced_table = self.channel_pair.read_advanced_sequencer_table() + np.testing.assert_equal(advanced_table, expected) def test_set_volatile_parameter(self): From 20fbc61ac9e154e2a764e2abb4f05aa54b6e284e Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 5 Mar 2020 11:03:30 +0100 Subject: [PATCH 045/107] simulator based unit tests run, but in the last UNIT-Test a Exception is thrown but ignored --- qupulse/hardware/awgs/features.py | 15 ++- qupulse/hardware/awgs/tabor.py | 100 ++++-------------- tests/hardware/tabor_simulator_based_tests.py | 1 + 3 files changed, 34 insertions(+), 82 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index d21e1527f..9fc57c02d 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -78,8 +78,10 @@ def clear(self) -> None: @abstractmethod def arm(self, name: Optional[str]) -> None: - """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current - program.""" + """ + Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current + program. + """ raise NotImplementedError() @property @@ -147,13 +149,18 @@ class ActivatableChannels(AWGChannelFeature): @property @abstractmethod def status(self) -> bool: + """ + Returns the the state a channel has at the moment. A channel is either activated or deactivated + """ raise NotImplementedError() @status.setter @abstractmethod def status(self, channel_status: bool) -> None: """ - Sets the current state of a channel - channel_state: the new state of a channel + Sets the current state of a channel. + + Args: + channel_status: the new state of a channel """ raise NotImplementedError() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 1a9b66af8..693637406 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,6 +1,7 @@ import fractions import functools import sys +import weakref from enum import Enum from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple, cast from collections import OrderedDict @@ -45,7 +46,7 @@ assert (sys.byteorder == "little") # Anpassen -__all__ = ["TaborDevice", "TaborChannelTuple"] +__all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] class TaborSegment: @@ -469,7 +470,6 @@ def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: @property def waveform_mode(self) -> str: return self.__waveform_mode - # TODO: typing # TODO: How does this work? @@ -498,12 +498,16 @@ def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> C """Asserts the channel pair is selcted when the wrapped function is called""" @functools.wraps(function_object) - def selector(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: - channel_pair.select() - return function_object(channel_pair, *args, **kwargs) + def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: + channel_tuple.select() + return function_object(channel_tuple, *args, **kwargs) return selector +class PlottableProgram: + TableEntry = NamedTuple('TableEntry', [('repetition_count', int), + ('element_number', int), + ('jump_flag', int)]) TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), ("program", TaborProgram)]) @@ -535,7 +539,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external :param paranoia_level: Paranoia level that is forwarded to teawg :param external_trigger: Not supported yet :param reset: - :param mirror_addresses: addresses of multiple device which can be controlled at once + :param mirror_addresses: addresses of multiple device which can be controlled at once. For example you can a simulator and a real Device at once """ super().__init__(device_name) @@ -553,12 +557,14 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # ChannelMarker self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] - # ChannelTuple TODO: ChannelMarker fehlen / bzw. Liste leer + # ChannelTuple self._channel_tuples = [] self._channel_tuples.append(TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2])) self._channel_tuples.append(TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4])) + + if external_trigger: raise NotImplementedError() # pragma: no cover @@ -566,18 +572,8 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external def cleanup(self) -> None: """This will be called automatically in __del__""" - # TODO: this method shoud be called in __del__ - # for tuple in self.channel_tuples: - # tuple.cleanup() - # for channel in self.channels: - # channel.clear() - # for marker_ch in self.marker_channels: - # marker_ch.clear() - - # is this needed? - # self.channels.clear() - # self.marker_channels.clear() - # self.channel_tuples.clear() + for channel_tuple in self.channel_tuples: + channel_tuple.cleanup() #TODO: Fehler mit dem letzten Unit Test der aber ignoriert wird @property def channels(self) -> Collection["TaborChannel"]: @@ -617,8 +613,8 @@ def all_devices(self) -> Sequence[teawg.TEWXAwg]: return (self._instr,) + self._mirrors def send_cmd(self, cmd_str, paranoia_level=None): - print(cmd_str) for instr in self.all_devices: + print(instr) instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: @@ -713,9 +709,7 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: for ch in (1, 2, 3, 4): # TODO: select Channel und Marker fehlen im Device self.channels[ch - 1].select() - # self._select_channel(ch) self.marker_channels[(ch - 1) % 2].select() - # self.select_marker((ch - 1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data @@ -724,30 +718,6 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover - # # TODO: soll man ein Channel Objekt oder eine ChannelNummer mitgeben? -> intern, das was am besten fuer die Umsetzung ist - # def _select_channel(self, channel_nr: int) -> None: - # if channel_nr not in range(1, len(self.channels)): - # raise TaborException("Invalid channel: {}".format(channel_nr)) - # - # self.send_cmd(":INST:SEL {channel}".format(channel=channel_nr)) - # - # def _select_marker(self, marker_nr: int) -> None: - # # TODO: right name for the parameter? - # """Select marker a marker of the currently active channel pair.""" - # if marker_nr not in range(1, len(self.channel_tuples[1].marker_channels)): - # raise TaborException("Invalid marker: {}".format(marker_nr)) - # - # self.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=marker_nr)) - - # wird die Methode noch gebraucht? - def _sample_rate(self, channel_nr: int) -> int: - if channel_nr not in range(1, len(self.channels)): - raise TaborException("Invalid channel: {}".format(channel_nr)) - - return int(self.channels[channel_nr].channel_tuple.sample_rate) - - # TODO: setter_sample_rate implementieren? - def enable(self) -> None: self.send_cmd(":ENAB") @@ -909,8 +879,7 @@ def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], raise ValueError("Wrong number of voltage transformations") # adjust program to fit criteria - # TODO: samplerate wird jetzt anders verwendet, also nicht mehr im device selber - Ist die sample rate ueber alle Channel gleich oder wieso wird oft die von channel[0] benutzt - sample_rate = self._parent.device.sample_rate(self._channels[0]) + sample_rate = self._parent.device.channel_tuples[0].sample_rate make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, @@ -931,7 +900,6 @@ def remove(self, name: str) -> None: def clear(self) -> None: """Delete all segments and clear memory""" - # self._parent.device.select_channel(self._channels[0]) self._parent.device.channels[0].select() self._parent.device.send_cmd(':TRAC:DEL:ALL') self._parent.device.send_cmd(':SOUR:SEQ:DEL:ALL') @@ -953,8 +921,6 @@ def clear(self) -> None: self._parent._known_programs = dict() self._parent.change_armed_program(None) - pass # TODO: check/finish implementation - @with_select def arm(self, name: Optional[str]) -> None: if self._parent._current_program == name: @@ -966,18 +932,12 @@ def arm(self, name: Optional[str]) -> None: class TaborChannelTuple(AWGChannelTuple): def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], marker_channels: Iterable["TaborMarkerChannel"]): - # TODO: hat das weglassen des alten String identifier Auswirkungen? - # TODO: zugeordneter MarkerChannel - super().__init__(idn) self._device = device # TODO: weakref.ref(device) can't be used like in the old driver self._configuration_guard_count = 0 self._is_in_config_mode = False - # TODO: Ueberpreufung macht keinen Sinn - # if channels not in self._device.channel_tuples: - # raise ValueError("Invalid channel pair: {}".format(channels)) self._channels = tuple(channels) self._marker_channels = tuple(marker_channels) @@ -988,8 +948,6 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self.add_feature(TaborProgramManagement(self)) - # TODO: Kommentar beenden - self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, output_offset=0., resolution=14), @@ -1033,14 +991,9 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: @property def sample_rate(self) -> float: - # haben wirklich alle Channel eines Tupels die selbe sample rate? + #TODO: keep sample_rate in tuple or put it in the channel return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) - """ - def select(self) -> None: - pass # TODO: to implement - """ - @property def total_capacity(self) -> int: return int(self.device.dev_properties["max_arb_mem"]) // 2 @@ -1097,7 +1050,7 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] old_sequence = device.send_query(":SEQ:SEL?") sequences = [] - uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 # array ist im alten Treiber gleich + uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) @@ -1108,6 +1061,7 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.device.get_readable_device(simulator=True).read_adv_seq_table() + # upload im Feature def read_complete_program(self) -> PlottableProgram: @@ -1117,6 +1071,7 @@ def read_complete_program(self) -> PlottableProgram: # clear im Feature + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ np.ndarray, np.ndarray, np.ndarray]: """ @@ -1128,9 +1083,6 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths :param segment_lengths: :return: """ - - # TODO: ueberpreufen - segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) @@ -1209,7 +1161,6 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths @with_select @with_configuration_guard def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: - # TODO: Why is the proptery for device not used? if self._segment_references[segment_index] > 0: raise ValueError("Reference count not zero") if segment.num_points > self._segment_capacity[segment_index]: @@ -1217,7 +1168,6 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: segment_no = segment_index + 1 -<<<<<<< HEAD self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points @@ -1507,11 +1457,6 @@ def _exit_config_mode(self) -> None: cmd = cmd + ":INST:SEL {}; :OUTP ON;".format(channel.idn) self.device.send_cmd(cmd[:-1]) - # self.device.send_cmd(":INST:SEL {}; :OUTP ON; :INST:SEL {}; :OUTP ON".format(*self._channels)) - - #self.marker_channels[0][TaborMarkerChannelActivatable].status = True # TODO: Fehler hier wird nichts gemacht!!! - #self.marker_channels[1][TaborMarkerChannelActivatable].status = True - for marker_ch in self.marker_channels: marker_ch[TaborMarkerChannelActivatable].status = True @@ -1536,8 +1481,7 @@ def status(self, channel_state: bool) -> None: command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}" command_string = command_string.format( channel=self._parent.channel_tuple.channels[0].idn, - # TODO: Bearbeiten - self._parent.channel_tuple.channels[0].idn, device durch channel_tuple ersetzt - marker=self._parent.channel_tuple.marker_channels.index(self._parent)+1, # marker=self._parent.idn, TODO: bei + marker=self._parent.channel_tuple.marker_channels.index(self._parent)+1, active="ON" if channel_state else "OFF") self._parent.device.send_cmd(command_string) diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 0731549b5..89742a855 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -292,3 +292,4 @@ def test_set_volatile_parameter(self): expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) np.testing.assert_equal(advanced_table, expected) + From 24fc13a03566d16cbcab9a171dd74ad5b0c051e7 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 5 Mar 2020 11:15:38 +0100 Subject: [PATCH 046/107] simulator based unit tests run, but in the last UNIT-Test a Exception is thrown but ignored --- qupulse/hardware/awgs/tabor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 693637406..a68692cca 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -614,7 +614,6 @@ def all_devices(self) -> Sequence[teawg.TEWXAwg]: def send_cmd(self, cmd_str, paranoia_level=None): for instr in self.all_devices: - print(instr) instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: From 507d60a32b18cbec563ca689aa8ec5b94b1ce591 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 5 Mar 2020 14:18:56 +0100 Subject: [PATCH 047/107] removed a few old comments --- qupulse/hardware/awgs/tabor.py | 17 +++++++++-------- tests/hardware/tabor_simulator_based_tests.py | 4 +--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index a68692cca..a320154d7 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -504,11 +504,13 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: return selector + class PlottableProgram: TableEntry = NamedTuple('TableEntry', [('repetition_count', int), ('element_number', int), ('jump_flag', int)]) + TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), ("program", TaborProgram)]) @@ -559,11 +561,10 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # ChannelTuple - self._channel_tuples = [] - self._channel_tuples.append(TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2])) - self._channel_tuples.append(TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4])) - - + self._channel_tuples = [ + TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2]), + TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4]) + ] if external_trigger: raise NotImplementedError() # pragma: no cover @@ -573,7 +574,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external def cleanup(self) -> None: """This will be called automatically in __del__""" for channel_tuple in self.channel_tuples: - channel_tuple.cleanup() #TODO: Fehler mit dem letzten Unit Test der aber ignoriert wird + channel_tuple.cleanup() # TODO: Fehler mit dem letzten Unit Test der aber ignoriert wird @property def channels(self) -> Collection["TaborChannel"]: @@ -990,7 +991,7 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: @property def sample_rate(self) -> float: - #TODO: keep sample_rate in tuple or put it in the channel + # TODO: keep sample_rate in tuple or put it in the channel return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) @property @@ -1480,7 +1481,7 @@ def status(self, channel_state: bool) -> None: command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}" command_string = command_string.format( channel=self._parent.channel_tuple.channels[0].idn, - marker=self._parent.channel_tuple.marker_channels.index(self._parent)+1, + marker=self._parent.channel_tuple.marker_channels.index(self._parent) + 1, active="ON" if channel_state else "OFF") self._parent.device.send_cmd(command_string) diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 89742a855..a109656e7 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -132,9 +132,6 @@ def test_sample_rate(self): # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) def test_amplitude(self): - # for ch in (1, 2, 3, 4): - # self.assertIsInstance(self.instrument.amplitude(ch), float) - for channel in self.instrument.channels: self.assertIsInstance(channel[TaborOffsetAmplitude].amplitude, float) @@ -191,6 +188,7 @@ def setUp(self): # TODO: darf man das so ersetzen # self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') + self.channel_pair = self.instrument.channel_tuples[0] From 08ca68e19ce263791a138287d0cfefb3c09195bd Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 6 Mar 2020 15:50:09 +0100 Subject: [PATCH 048/107] added new comments --- qupulse/hardware/awgs/base.py | 4 +- qupulse/hardware/awgs/tabor.py | 358 ++++++++++++++++++++++++++------- 2 files changed, 287 insertions(+), 75 deletions(-) diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index e5a066095..ba2213f8e 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -38,8 +38,8 @@ def __init__(self, name: str): super().__init__() self._name = name - def __del__(self): - self.cleanup() + #def __del__(self): + # self.cleanup() @property def name(self) -> str: diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index a320154d7..974201693 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,9 +1,10 @@ import fractions import functools +import itertools +import operator import sys -import weakref from enum import Enum -from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple, cast +from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple, cast, Generator from collections import OrderedDict import numpy as np from qupulse import ChannelID @@ -45,7 +46,6 @@ # What does this mean? assert (sys.byteorder == "little") -# Anpassen __all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] @@ -70,7 +70,7 @@ class TaborSegment: # if ch_not_empty: # raise TaborException("Empty TaborSegments are not allowed") # - # # Check if all channels that aren't None are the same length + # # Check if all channels that aren"t None are the same length # comparison_length = self.num_points # for channel in channel_list: # if channel is not None: @@ -78,12 +78,12 @@ class TaborSegment: # break # for channel in channel_list: # if channel is not None: - # if not (len(channel) == comparison_length): # can be ignored. this case isn't possible + # if not (len(channel) == comparison_length): # can be ignored. this case isn"t possible # raise TaborException("Channel entries to have to have the same length") # # self.channel_list = List[np.asarray(Optional[np.ndarray], dtype=np.uint16)] # self.marker_list = List[np.asarray(Optional[np.ndarray], dtype=bool)] - # # TODO: is it possible like that. It's only possible when python works with references + # # TODO: is it possible like that. It"s only possible when python works with references # for channel in channel_list: # self.channel_list.add(None if channel is None else np.asarray(channel, dtype=np.uint16)) # for marker in marker_list: @@ -133,7 +133,7 @@ class TaborSegment: """Represents one segment of two channels on the device. Convenience class.""" - __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') + __slots__ = ("ch_a", "ch_b", "marker_a", "marker_b") def __init__(self, ch_a: Optional[np.ndarray], @@ -141,9 +141,9 @@ def __init__(self, marker_a: Optional[np.ndarray], marker_b: Optional[np.ndarray]): if ch_a is None and ch_b is None: - raise TaborException('Empty TaborSegments are not allowed') + raise TaborException("Empty TaborSegments are not allowed") if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): - raise TaborException('Channel entries to have to have the same length') + raise TaborException("Channel entries to have to have the same length") self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) @@ -152,18 +152,18 @@ def __init__(self, self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) if marker_a is not None and len(marker_a) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') + raise TaborException("Marker A has to have half of the channels length") if marker_b is not None and len(marker_b) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') + raise TaborException("Marker A has to have half of the channels length") @classmethod - def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': + def from_binary_segment(cls, segment_data: np.ndarray) -> "TaborSegment": data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() return cls.from_binary_data(data_a, data_b) @classmethod - def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': + def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> "TaborSegment": ch_b = data_b channel_mask = np.uint16(2 ** 14 - 1) @@ -185,7 +185,7 @@ def __hash__(self) -> int: return hash(tuple(0 if data is None else bytes(data) for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) - def __eq__(self, other: 'TaborSegment'): + def __eq__(self, other: "TaborSegment"): def compare_markers(marker_1, marker_2): if marker_1 is None: if marker_2 is None: @@ -211,7 +211,7 @@ def data_a(self) -> np.ndarray: return self.ch_a if self.ch_a is None: - raise NotImplementedError('What data should be used in a?') + raise NotImplementedError("What data should be used in a?") # copy channel information data = np.array(self.ch_a) @@ -301,9 +301,8 @@ def sampled_segments(self, time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): - raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') + raise TaborException("At least one waveform has a length that is smaller 192 or not a multiple of 16") - # TODO: ueberpruefen def voltage_to_data(waveform, time, channel): if self._channels[channel]: return voltage_to_uint16( @@ -340,6 +339,10 @@ def get_marker_data(waveform: MultiChannelWaveform, time, marker): return segments, segment_lengths def setup_single_sequence_mode(self) -> None: + # TODO: comment missing + """ + + """ assert self.program.depth() == 1 sequencer_table = [] @@ -360,13 +363,17 @@ def setup_single_sequence_mode(self) -> None: self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] def setup_advanced_sequence_mode(self) -> None: + # TODO: comment missing + """ + + """ assert self.program.depth() > 1 assert self.program.repetition_count == 1 self.program.flatten_and_balance(2) - min_seq_len = self.__device_properties['min_seq_len'] - max_seq_len = self.__device_properties['max_seq_len'] + min_seq_len = self.__device_properties["min_seq_len"] + max_seq_len = self.__device_properties["max_seq_len"] def check_merge_with_next(program, n): if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and @@ -390,7 +397,7 @@ def check_partial_unroll(program, n): while i < len(self.program): self.program[i].assert_tree_integrity() if len(self.program[i]) > max_seq_len: - raise TaborException('The algorithm is not smart enough to make sequence tables shorter') + raise TaborException("The algorithm is not smart enough to make sequence tables shorter") elif len(self.program[i]) < min_seq_len: assert self.program[i].repetition_count > 0 if self.program[i].repetition_count == 1: @@ -418,17 +425,17 @@ def check_partial_unroll(program, n): self.program[i + 1].repetition_count -= 1 else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') + raise TaborException("The algorithm is not smart enough to make this sequence table longer") elif check_partial_unroll(self.program, i): i += 1 else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') + raise TaborException("The algorithm is not smart enough to make this sequence table longer") else: i += 1 for sequence_table in self.program: - assert len(sequence_table) >= self.__device_properties['min_seq_len'] - assert len(sequence_table) <= self.__device_properties['max_seq_len'] + assert len(sequence_table) >= self.__device_properties["min_seq_len"] + assert len(sequence_table) <= self.__device_properties["max_seq_len"] advanced_sequencer_table = [] sequencer_tables = [] @@ -462,13 +469,25 @@ def program(self) -> Loop: return self._program def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: + # TODO: comment missing + """ + + """ return self._sequencer_tables def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: + # TODO: comment missing + """ + + """ return self._advanced_sequencer_table @property def waveform_mode(self) -> str: + # TODO: comment missing + """ + + """ return self.__waveform_mode @@ -493,7 +512,6 @@ def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: return guarding_method -# TODO: How does this work? def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @@ -506,9 +524,130 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: class PlottableProgram: - TableEntry = NamedTuple('TableEntry', [('repetition_count', int), - ('element_number', int), - ('jump_flag', int)]) + TableEntry = NamedTuple("TableEntry", [("repetition_count", int), + ("element_number", int), + ("jump_flag", int)]) + + def __init__(self, + segments: List[TaborSegment], + sequence_tables: List[List[Tuple[int, int, int]]], + advanced_sequence_table: List[Tuple[int, int, int]]): + self._segments = segments + self._sequence_tables = [[self.TableEntry(*sequence_table_entry) + for sequence_table_entry in sequence_table] + for sequence_table in sequence_tables] + self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) + for adv_seq_entry in advanced_sequence_table] + + @classmethod + def from_read_data(cls, waveforms: List[np.ndarray], + sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], + advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': + return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], + [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], + cls._reformat_rep_seg_jump(advanced_sequence_table)) + + @classmethod + def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: + return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) + for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) + + def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: + if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): + adv_seq_tab = self._advanced_sequence_table[1:] + else: + adv_seq_tab = self._advanced_sequence_table + + # remove idle pulse at end + if with_last_idles: + return adv_seq_tab + else: + while adv_seq_tab[-1] == (1, 1, 0): + adv_seq_tab = adv_seq_tab[:-1] + return adv_seq_tab + + def _iter_segment_table_entry(self, + with_first_idle=False, + with_last_idles=False) -> Generator[TableEntry, None, None]: + for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): + for _ in range(sequence_repeat): + yield from self._sequence_tables[sequence_no - 1] + + def iter_waveforms_and_repetitions(self, + channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): + yield ch_getter(self._segments[segment_no - 1]), segment_repeat + + def iter_samples(self, channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[np.uint16, None, None]: + for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): + waveform = list(waveform) + for _ in range(repetition): + yield from waveform + + def get_as_single_waveform(self, channel: int, + max_total_length: int = 10 ** 9, + with_marker: bool = False) -> Optional[np.ndarray]: + waveforms = self.get_waveforms(channel, with_marker=with_marker) + repetitions = self.get_repetitions() + waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) + + total_length = (repetitions * waveform_lengths).sum() + if total_length > max_total_length: + return None + + result = np.empty(total_length, dtype=np.uint16) + c_idx = 0 + for wf, rep in zip(waveforms, repetitions): + mem = wf.size * rep + target = result[c_idx:c_idx + mem] + + target = target.reshape((rep, wf.size)) + target[:, :] = wf[np.newaxis, :] + c_idx += mem + return result + + def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: + if with_marker: + ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] + else: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1]) + for _, segment_no, _ in self._iter_segment_table_entry()] + + def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1])] + + def get_repetitions(self) -> np.ndarray: + return np.fromiter((segment_repeat + for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) + + def __eq__(self, other): + for ch in (0, 1): + for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), + other.iter_samples(ch, True, False)): + if x != y: + return False + return True + + def to_builtin(self) -> dict: + waveforms = [[wf.data_a.tolist() for wf in self._segments], + [wf.data_b.tolist() for wf in self._segments]] + return {'waveforms': waveforms, + 'seq_tables': self._sequence_tables, + 'adv_seq_table': self._advanced_sequence_table} + + @classmethod + def from_builtin(cls, data: dict) -> 'PlottableProgram': + waveforms = data['waveforms'] + waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) + for data_a, data_b in zip(*waveforms)] + return cls(waveforms, data['seq_tables'], data['adv_seq_table']) TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), @@ -536,12 +675,16 @@ class TaborDevice(AWGDevice): def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ - :param device_name: Name of the device - :param instr_addr: Instrument address that is forwarded to teawag - :param paranoia_level: Paranoia level that is forwarded to teawg - :param external_trigger: Not supported yet - :param reset: - :param mirror_addresses: addresses of multiple device which can be controlled at once. For example you can a simulator and a real Device at once + Constructor for a Tabor device + + Args: + device_name (str): Name of the device + instr_addr: Instrument address that is forwarded to teawag + paranoia_level (int): Paranoia level that is forwarded to teawg + external_trigger (bool): Not supported yet + reset (bool): + mirror_addresses: list of devices on which the same things as on the main device are done. For example you can a simulator and a real Device at once + """ super().__init__(device_name) @@ -560,7 +703,6 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] # ChannelTuple - self._channel_tuples = [ TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2]), TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4]) @@ -569,12 +711,12 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external if external_trigger: raise NotImplementedError() # pragma: no cover - self.initialize() # TODO: ggf. ueberarbeiten + self.initialize() # TODO: change for synchronisation-feature def cleanup(self) -> None: - """This will be called automatically in __del__""" + # TODO: split cleanup in to two different methods for channel_tuple in self.channel_tuples: - channel_tuple.cleanup() # TODO: Fehler mit dem letzten Unit Test der aber ignoriert wird + channel_tuple.cleanup() @property def channels(self) -> Collection["TaborChannel"]: @@ -647,7 +789,6 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: paranoia_level = self.paranoia_level if paranoia_level < 3: - # TODO: unsolved Reference super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover else: cmd_str = cmd_str.rstrip() @@ -707,7 +848,6 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): - # TODO: select Channel und Marker fehlen im Device self.channels[ch - 1].select() self.marker_channels[(ch - 1) % 2].select() for name, query, dtype in name_query_type_list: @@ -719,16 +859,24 @@ def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover def enable(self) -> None: + """ + This method immediately generates the selected output waveform, if the device is in continuous and armed + run mode. + """ self.send_cmd(":ENAB") def abort(self) -> None: + """ + With abort you can terminate the current generation of the output waveform. When the output waveform is + terminated the output starts generating an idle waveform. + """ self.send_cmd(":ABOR") def initialize(self) -> None: # 1. Select channel # 2. Turn off gated mode # 3. continous mode - # 4. Armed mode (onlz generate waveforms after enab command) + # 4. Armed mode (only generate waveforms after enab command) # 5. Expect enable signal from (USB / LAN / GPIB) # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) @@ -742,8 +890,13 @@ def initialize(self) -> None: self.send_cmd(setup_command) def reset(self) -> None: + """ + Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and + all channel tuples are cleared. + """ self.send_cmd(':RES') self._coupled = None + self.initialize() for channel_tuple in self.channel_tuples: channel_tuple[TaborProgramManagement].clear() @@ -752,6 +905,16 @@ def trigger(self) -> None: self.send_cmd(":TRIG") def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: + """ + A method to get the first readable device out of all devices. + A readable device is a device which you can read data from like a simulator. + + Returns: + The first readable device out of all devices + + Throws: + TaborException: this exception is thrown if there is no readable device in the list of all devices + """ for device in self.all_devices: if device.fw_ver >= 3.0: if simulator: @@ -827,19 +990,28 @@ def __init__(self, idn: int, device: TaborDevice): self._device = device + # adding Features self.add_feature(TaborOffsetAmplitude(self)) _channel_tuple: "TaborChannelTuple" @property def device(self) -> TaborDevice: + """Returns the device that the channel belongs to""" return self._device @property def channel_tuple(self) -> Optional[AWGChannelTuple]: + """Returns the channel tuple that this channel belongs to""" return self._channel_tuple def _set_channel_tuple(self, channel_tuple) -> None: + """ + The channel tuple 'channel_tuple' is assigned to this channel + + Args: + channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to + """ self._channel_tuple = channel_tuple def select(self) -> None: @@ -901,14 +1073,14 @@ def clear(self) -> None: """Delete all segments and clear memory""" self._parent.device.channels[0].select() - self._parent.device.send_cmd(':TRAC:DEL:ALL') - self._parent.device.send_cmd(':SOUR:SEQ:DEL:ALL') - self._parent.device.send_cmd(':ASEQ:DEL') + self._parent.device.send_cmd(":TRAC:DEL:ALL") + self._parent.device.send_cmd(":SOUR:SEQ:DEL:ALL") + self._parent.device.send_cmd(":ASEQ:DEL") - self._parent.device.send_cmd(':TRAC:DEF 1, 192') - self._parent.device.send_cmd(':TRAC:SEL 1') - self._parent.device.send_cmd(':TRAC:MODE COMB') - self._parent.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._parent._idle_segment.get_as_binary()) + self._parent.device.send_cmd(":TRAC:DEF 1, 192") + self._parent.device.send_cmd(":TRAC:SEL 1") + self._parent.device.send_cmd(":TRAC:MODE COMB") + self._parent.device.send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent._idle_segment.get_as_binary()) self._parent._segment_lengths = 192 * np.ones(1, dtype=np.uint32) self._parent._segment_capacity = 192 * np.ones(1, dtype=np.uint32) @@ -923,6 +1095,12 @@ def clear(self) -> None: @with_select def arm(self, name: Optional[str]) -> None: + """ + The program 'name' armed + + Args: + name (str): the program the device should change to + """ if self._parent._current_program == name: self._parent.device.send_cmd("SEQ:SEL 1") else: @@ -941,11 +1119,13 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self._channels = tuple(channels) self._marker_channels = tuple(marker_channels) + # the channel and channel marker are assigned to this channel tuple for channel in self.channels: channel._set_channel_tuple(self) for marker_ch in self.marker_channels: marker_ch._set_channel_tuple(self) + # adding Features self.add_feature(TaborProgramManagement(self)) self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), @@ -973,25 +1153,27 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self[TaborProgramManagement].clear() def select(self) -> None: - # TODO: keep this? + """The channel tuple is selected, which means that the first channel of the channel tuple is selected""" self.channels[0].select() @property def device(self) -> TaborDevice: + """Returns the device that the channel tuple belongs to""" return self._device @property def channels(self) -> Collection["TaborChannel"]: + """Returns all channels of the channel tuple""" return self._channels @property def marker_channels(self) -> Collection["TaborMarkerChannel"]: + """Returns all marker channels of the channel tuple""" return self._marker_channels @property def sample_rate(self) -> float: - # TODO: keep sample_rate in tuple or put it in the channel return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) @property @@ -1000,7 +1182,7 @@ def total_capacity(self) -> int: def free_program(self, name: str) -> TaborProgramMemory: if name is None: - raise TaborException('Removing "None" program is forbidden.') + raise TaborException("Removing 'None' program is forbidden.") program = self._known_programs.pop(name) self._segment_references[program.waveform_to_segment] -= 1 if self._current_program == name: @@ -1009,7 +1191,7 @@ def free_program(self, name: str) -> TaborProgramMemory: def _restore_program(self, name: str, program: TaborProgram) -> None: if name in self._known_programs: - raise ValueError('Program cannot be restored as it is already known.') + raise ValueError("Program cannot be restored as it is already known.") self._segment_references[program.waveform_to_segment] += 1 self._known_programs[name] = program @@ -1036,7 +1218,7 @@ def read_waveforms(self) -> List[np.ndarray]: old_segment = device.send_query(":TRAC:SEL?") waveforms = [] uploaded_waveform_indices = np.flatnonzero( - self._segment_references) + 1 # uploaded_waveform_indices sind gleich wie im alten Treiber + self._segment_references) + 1 for segment in uploaded_waveform_indices: device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) @@ -1062,6 +1244,7 @@ def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndar return self.device.get_readable_device(simulator=True).read_adv_seq_table() + # upload im Feature def read_complete_program(self) -> PlottableProgram: @@ -1071,17 +1254,21 @@ def read_complete_program(self) -> PlottableProgram: # clear im Feature - - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ - np.ndarray, np.ndarray, np.ndarray]: + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ + Tuple[np.ndarray, np.ndarray, np.ndarray]: + # TODO: comment was not finished """ 1. Find known segments 2. Find empty spaces with fitting length 3. Find empty spaces with bigger length 4. Amend remaining segments - :param segments: - :param segment_lengths: - :return: + + Args: + segments (Sequence): + segment_length (Sequence): + + Returns: + """ segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) @@ -1102,7 +1289,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths to_upload_size = np.sum(segment_lengths[unknown] + 16) free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) if free_points_in_total < to_upload_size: - raise MemoryError('Not enough free memory', + raise MemoryError("Not enough free memory", free_points_in_total, to_upload_size, self._free_points_in_total) @@ -1151,7 +1338,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: - raise MemoryError('Fragmentation does not allow upload.', + raise MemoryError("Fragmentation does not allow upload.", np.sum(segment_lengths[to_amend] + 16), free_points_at_end, self._free_points_at_end) @@ -1199,6 +1386,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: paranoia_level=self.internal_paranoia_level) self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) segment_lengths = np.concatenate((self._segment_lengths, new_lengths)) @@ -1245,9 +1433,6 @@ def cleanup(self) -> None: except Exception as e: raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e - # remove im Feature - - @with_configuration_guard def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: """ Joins the given commands into one and executes it with configuration guard. @@ -1328,6 +1513,7 @@ def set_program_sequence_table(self, name, new_sequence_table): @with_select @with_configuration_guard def change_armed_program(self, name: Optional[str]) -> None: + """The armed program of the channel tuple is change to the program with the name 'name'""" if name is None: sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] @@ -1351,28 +1537,28 @@ def change_armed_program(self, name: Optional[str]) -> None: assert len(advanced_sequencer_table) == 1 assert len(sequencer_tables) == 2 - while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: + while len(sequencer_tables[1]) < self.device.dev_properties["min_seq_len"]: assert advanced_sequencer_table[0][0] == 1 sequencer_tables[1].append((1, 1, 0)) # insert idle sequence in advanced sequence table advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table - while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: + while len(advanced_sequencer_table) < self.device.dev_properties["min_aseq_len"]: advanced_sequencer_table.append((1, 1, 0)) # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd('SEQ:DEL:ALL') + self.device.send_cmd("SEQ:DEL:ALL") self._sequencer_tables = [] - self.device.send_cmd('ASEQ:DEL') + self.device.send_cmd("ASEQ:DEL") self._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) + self.device.send_cmd("SEQ:SEL {}".format(i + 1)) self.device.download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables - self.device.send_cmd('SEQ:SEL 1') + self.device.send_cmd("SEQ:SEL 1") self.device.download_adv_seq_table(advanced_sequencer_table) self._advanced_sequence_table = advanced_sequencer_table @@ -1381,6 +1567,12 @@ def change_armed_program(self, name: Optional[str]) -> None: @with_select def run_current_program(self) -> None: + """ + This method starts running the active program + + Throws: + RuntimeError: This exception is thrown if there is no active program for this device + """ if self._current_program: self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) else: @@ -1393,10 +1585,16 @@ def programs(self) -> Set[str]: @property def num_channels(self) -> int: + """ + Returns the number of channels that belong to the channel tuple + """ return len(self.channels) @property def num_markers(self) -> int: + """ + Returns the number of marker channels that belong to the channel tuple + """ return len(self.marker_channels) def _enter_config_mode(self) -> None: @@ -1492,22 +1690,34 @@ def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) self._device = device + # adding Features self.add_feature(TaborMarkerChannelActivatable(self)) _channel_tuple: "TaborChannelTuple" @property def device(self) -> TaborDevice: + """Returns the device that this marker channel belongs to""" return self._device @property def channel_tuple(self) -> Optional[TaborChannelTuple]: + """Returns the channel tuple that this marker channel belongs to""" return self._channel_tuple - def _set_channel_tuple(self, channel_tuple) -> None: + def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: + """ + The channel tuple 'channel_tuple' is assigned to this marker channel + + Args: + channel_tuple (TaborChannelTuple): the channel tuple that this marker channel belongs to + """ self._channel_tuple = channel_tuple def select(self) -> None: + """ + This marker channel is selected and is now the active channel marker of the device + """ self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=self.idn)) @@ -1517,8 +1727,10 @@ class TaborException(Exception): class TaborUndefinedState(TaborException): - """If this exception is raised the attached tabor device is in an undefined state. - It is highly recommended to call reset it.""" + """ + If this exception is raised the attached tabor device is in an undefined state. + It is highly recommended to call reset it. + """ def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): super().__init__(*args) From bd0f2e64d5748c06449e4d81726b4edefd6aa35a Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 10 Mar 2020 09:29:24 +0100 Subject: [PATCH 049/107] channel tuple adapter was added --- qupulse/hardware/awgs/base.py | 6 +++ .../hardware/awgs/channel_tuple_wrapper.py | 53 +++++++++++++++++++ qupulse/hardware/awgs/tabor.py | 19 +++++++ 3 files changed, 78 insertions(+) create mode 100644 qupulse/hardware/awgs/channel_tuple_wrapper.py diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index ba2213f8e..29e168aea 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from typing import Optional +from qupulse.hardware.awgs import channel_tuple_wrapper from qupulse.hardware.awgs.base_features import Feature, FeatureAble from qupulse.utils.types import Collection @@ -82,6 +83,11 @@ def __init__(self, idn: int): self._idn = idn + @property + @abstractmethod + def channel_tuple_adapter(self) -> channel_tuple_wrapper: + pass + @property def idn(self) -> int: """Returns the identification number of a channel tuple""" diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py new file mode 100644 index 000000000..f40d7b5ed --- /dev/null +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -0,0 +1,53 @@ +from typing import Tuple, Optional, Callable, Set + +from qupulse._program._loop import Loop +from qupulse.hardware.awgs.old_base import AWG + + +class ChannelTupleAdapter(AWG): + def __copy__(self) -> None: + pass + + def __init__(self, method_identifier, method_num_channels, method_num_marker, method_upload, method_remove, + method_clear, method_arm, method_programs, method_sample_rate): + self.m_identifier = method_identifier + self.m_num_channels = method_num_channels + self.m_num_marker = method_num_marker + self.m_upload = method_upload + self.m_remove = method_remove + self.m_clear = method_clear + self.m_arm = method_arm + self.m_programs = method_programs + self.m_sample_rate = method_sample_rate + + def identifier(self) -> str: + return self.m_identifier + + def num_channels(self) -> int: + return self.m_num_channels() + + def num_markers(self) -> int: + return self.m_num_marker + + 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.m_upload(name, program, channels, markers, voltage_transformation, force) + + def remove(self, name: str) -> None: + return self.m_remove(name) + + def clear(self) -> None: + return self.m_clear + + def arm(self, name: Optional[str]) -> None: + return self.m_arm(name) + + def programs(self) -> Set[str]: + return self.m_programs + + def sample_rate(self) -> float: + return self.m_sample_rate diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 974201693..acbecb13b 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -10,6 +10,8 @@ from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible from qupulse._program.waveforms import MultiChannelWaveform +from qupulse.hardware.awgs import channel_tuple_wrapper +from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ ProgramManagement, ActivatableChannels from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times @@ -1152,6 +1154,22 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self[TaborProgramManagement].clear() + self._channel_tuple_adapter = ChannelTupleAdapter( + self.name, + self.num_channels, + self.num_markers, + self[TaborProgramManagement].upload, + self[TaborProgramManagement].remove, + self[TaborProgramManagement].clear, + self[TaborProgramManagement].arm, + self.programs, + self.sample_rate + ) + + @property + def channel_tuple_adapter(self) -> channel_tuple_wrapper: + return self._channel_tuple_adapter + def select(self) -> None: """The channel tuple is selected, which means that the first channel of the channel tuple is selected""" self.channels[0].select() @@ -1174,6 +1192,7 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: @property def sample_rate(self) -> float: + """Returns the sample rate that the channels of a channel tuple have""" return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) @property From 9729d07e84fda7d2d5281121c4b06c15137ec7bf Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 10 Mar 2020 10:54:52 +0100 Subject: [PATCH 050/107] channel tuple adapter - a bug was fixed --- qupulse/hardware/awgs/channel_tuple_wrapper.py | 2 +- qupulse/hardware/awgs/tabor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index f40d7b5ed..e97e49425 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -24,7 +24,7 @@ def identifier(self) -> str: return self.m_identifier def num_channels(self) -> int: - return self.m_num_channels() + return self.m_num_channels def num_markers(self) -> int: return self.m_num_marker diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index acbecb13b..de7011d90 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1098,7 +1098,7 @@ def clear(self) -> None: @with_select def arm(self, name: Optional[str]) -> None: """ - The program 'name' armed + The program 'name' gets armed Args: name (str): the program the device should change to From fd6d4266a5331f3c95258588864ab55b7e8a28ee Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 10 Mar 2020 15:02:42 +0100 Subject: [PATCH 051/107] channel tuple adapter - consturctor was changed --- .../hardware/awgs/channel_tuple_wrapper.py | 38 +++++++++---------- qupulse/hardware/awgs/tabor.py | 15 +------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index e97e49425..fbf69f4e0 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,33 +1,26 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop +from qupulse.hardware.awgs import TaborChannelTuple from qupulse.hardware.awgs.old_base import AWG +from qupulse.hardware.awgs.features import ProgramManagement class ChannelTupleAdapter(AWG): def __copy__(self) -> None: pass - def __init__(self, method_identifier, method_num_channels, method_num_marker, method_upload, method_remove, - method_clear, method_arm, method_programs, method_sample_rate): - self.m_identifier = method_identifier - self.m_num_channels = method_num_channels - self.m_num_marker = method_num_marker - self.m_upload = method_upload - self.m_remove = method_remove - self.m_clear = method_clear - self.m_arm = method_arm - self.m_programs = method_programs - self.m_sample_rate = method_sample_rate + def __init__(self, channel_tuple: TaborChannelTuple): + self._channel_tuple = channel_tuple def identifier(self) -> str: - return self.m_identifier + return self._channel_tuple.name def num_channels(self) -> int: - return self.m_num_channels + return self._channel_tuple.num_channels def num_markers(self) -> int: - return self.m_num_marker + return self._channel_tuple.num_markers def upload(self, name: str, program: Loop, @@ -35,19 +28,24 @@ def upload(self, name: str, markers: Tuple[Optional["ChannelID"], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: - return self.m_upload(name, program, channels, markers, voltage_transformation, force) + from qupulse.hardware.awgs.tabor import TaborProgramManagement + return self._channel_tuple[TaborProgramManagement].upload(name, program, channels, markers, + voltage_transformation, force) def remove(self, name: str) -> None: - return self.m_remove(name) + from qupulse.hardware.awgs.tabor import TaborProgramManagement + return self._channel_tuple[TaborProgramManagement].remove(name) def clear(self) -> None: - return self.m_clear + from qupulse.hardware.awgs.tabor import TaborProgramManagement + return self._channel_tuple[TaborProgramManagement].clear() def arm(self, name: Optional[str]) -> None: - return self.m_arm(name) + from qupulse.hardware.awgs.tabor import TaborProgramManagement + return self._channel_tuple[TaborProgramManagement].arm(name) def programs(self) -> Set[str]: - return self.m_programs + return self._channel_tuple.programs def sample_rate(self) -> float: - return self.m_sample_rate + return self._channel_tuple.sample_rate diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index de7011d90..77b8ef864 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -10,7 +10,6 @@ from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible from qupulse._program.waveforms import MultiChannelWaveform -from qupulse.hardware.awgs import channel_tuple_wrapper from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ ProgramManagement, ActivatableChannels @@ -1154,20 +1153,10 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self[TaborProgramManagement].clear() - self._channel_tuple_adapter = ChannelTupleAdapter( - self.name, - self.num_channels, - self.num_markers, - self[TaborProgramManagement].upload, - self[TaborProgramManagement].remove, - self[TaborProgramManagement].clear, - self[TaborProgramManagement].arm, - self.programs, - self.sample_rate - ) + self._channel_tuple_adapter = ChannelTupleAdapter(self) @property - def channel_tuple_adapter(self) -> channel_tuple_wrapper: + def channel_tuple_adapter(self) -> ChannelTupleAdapter: return self._channel_tuple_adapter def select(self) -> None: From 921abe26b65e94651a3b243e80b40338f4bc6000 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 10 Mar 2020 15:14:06 +0100 Subject: [PATCH 052/107] channel tuple adapter - consturctor was changed --- qupulse/hardware/awgs/channel_tuple_wrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index fbf69f4e0..52404e8fe 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -3,7 +3,6 @@ from qupulse._program._loop import Loop from qupulse.hardware.awgs import TaborChannelTuple from qupulse.hardware.awgs.old_base import AWG -from qupulse.hardware.awgs.features import ProgramManagement class ChannelTupleAdapter(AWG): From 042e4237d4b219e69cda93dbd136454f8572c8db Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 23 Mar 2020 13:55:08 +0100 Subject: [PATCH 053/107] start of implementing the feedback --- .../hardware/awgs/channel_tuple_wrapper.py | 12 ++- qupulse/hardware/awgs/tabor.py | 92 +++++++++++-------- .../old_tabor_simulator_based_tests.py | 8 +- tests/hardware/tabor_simulator_based_tests.py | 20 ++-- 4 files changed, 79 insertions(+), 53 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index 52404e8fe..a3514ce4c 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,15 +1,20 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs import TaborChannelTuple +from qupulse.hardware.awgs import TaborChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple from qupulse.hardware.awgs.old_base import AWG class ChannelTupleAdapter(AWG): + """ + + """ + # TODO (LuL): Doc strings + def __copy__(self) -> None: pass - def __init__(self, channel_tuple: TaborChannelTuple): + def __init__(self, channel_tuple: TaborChannelTuple): # TODO (LuL): Not Tabor, but base class: ChannelTuple self._channel_tuple = channel_tuple def identifier(self) -> str: @@ -44,7 +49,8 @@ def arm(self, name: Optional[str]) -> None: return self._channel_tuple[TaborProgramManagement].arm(name) def programs(self) -> Set[str]: - return self._channel_tuple.programs + from qupulse.hardware.awgs.tabor import TaborProgramManagement + return self._channel_tuple[TaborProgramManagement].programs def sample_rate(self) -> float: return self._channel_tuple.sample_rate diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 77b8ef864..840ff6bf0 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -283,12 +283,12 @@ def __init__(self, @property def markers(self): - return self._markers + return self._markers # TODO (LuL): Uncomment the instantiation of self._channels # TODO: typing @property def channels(self): - return self._channels + return self._channels # TODO (LuL): Uncomment the instantiation of self._channels # TODO: typing def sampled_segments(self, @@ -518,7 +518,7 @@ def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> C @functools.wraps(function_object) def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: - channel_tuple.select() + channel_tuple._select() # TODO (LuL): make the select-function private, because it only should be called from here return function_object(channel_tuple, *args, **kwargs) return selector @@ -659,6 +659,9 @@ def from_builtin(cls, data: dict) -> 'PlottableProgram': ######################################################################################################################## # Features # TODO: maybe implement Synchronization Feature for Tabor Devices +# TODO (LuL): Not just maybe. Use the TaborChannelSynchronization for channel synchronization, but you can +# raise an error if the the group-size is not 2. Groups of 4 can be implemented later, but you should +# prepare the class for this. """ class TaborChannelSynchronization(ChannelSynchronization): @@ -687,7 +690,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external mirror_addresses: list of devices on which the same things as on the main device are done. For example you can a simulator and a real Device at once """ - + # TODO (LuL): I don't think None would work for instr_addr. If so, remove the default value to make it mandatory super().__init__(device_name) self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) @@ -704,6 +707,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] # ChannelTuple + # TODO (LuL): Use the ChannelSynchronization-feature to set up the ChannelTuple's self._channel_tuples = [ TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2]), TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4]) @@ -712,7 +716,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external if external_trigger: raise NotImplementedError() # pragma: no cover - self.initialize() # TODO: change for synchronisation-feature + self._initialize() # TODO: change for synchronisation-feature def cleanup(self) -> None: # TODO: split cleanup in to two different methods @@ -757,28 +761,29 @@ def all_devices(self) -> Sequence[teawg.TEWXAwg]: return (self._instr,) + self._mirrors def send_cmd(self, cmd_str, paranoia_level=None): + # TODO (LuL): This function should be private for instr in self.all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) - def send_query(self, query_str, query_mirrors=False) -> Any: + def _send_query(self, query_str, query_mirrors=False) -> Any: if query_mirrors: return tuple(instr.send_query(query_str) for instr in self.all_devices) else: return self._instr.send_query(query_str) - def send_binary_data(self, pref, bin_dat, paranoia_level=None): + def _send_binary_data(self, pref, bin_dat, paranoia_level=None): for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) - def download_segment_lengths(self, seg_len_list, pref=":SEGM:DATA", paranoia_level=None): + def _download_segment_lengths(self, seg_len_list, pref=":SEGM:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) - def download_sequencer_table(self, seq_table, pref=":SEQ:DATA", paranoia_level=None): + def _download_sequencer_table(self, seq_table, pref=":SEQ:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) - def download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=None): + def _download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=None): for instr in self.all_devices: instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) @@ -849,10 +854,10 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): - self.channels[ch - 1].select() - self.marker_channels[(ch - 1) % 2].select() + self.channels[ch - 1]._select() + self.marker_channels[(ch - 1) % 2]._select() for name, query, dtype in name_query_type_list: - data[name].append(dtype(self.send_query(query))) + data[name].append(dtype(self._send_query(query))) return data @property @@ -864,6 +869,7 @@ def enable(self) -> None: This method immediately generates the selected output waveform, if the device is in continuous and armed run mode. """ + # TODO (LuL): Can make a feature for this? self.send_cmd(":ENAB") def abort(self) -> None: @@ -871,9 +877,10 @@ def abort(self) -> None: With abort you can terminate the current generation of the output waveform. When the output waveform is terminated the output starts generating an idle waveform. """ + # TODO (LuL): Can make a feature for this? self.send_cmd(":ABOR") - def initialize(self) -> None: + def _initialize(self) -> None: # 1. Select channel # 2. Turn off gated mode # 3. continous mode @@ -895,6 +902,7 @@ def reset(self) -> None: Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and all channel tuples are cleared. """ + # TODO (LuL): Can make a feature for this? self.send_cmd(':RES') self._coupled = None @@ -903,6 +911,7 @@ def reset(self) -> None: channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: + # TODO (LuL): Can make a feature for this? self.send_cmd(":TRIG") def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: @@ -938,7 +947,7 @@ def __init__(self, channel: "TaborChannel"): @property def offset(self) -> float: return float( - self._parent.device.send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent.idn))) + self._parent.device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent.idn))) @offset.setter def offset(self, offset: float) -> None: @@ -946,11 +955,11 @@ def offset(self, offset: float) -> None: @property def amplitude(self) -> float: - coupling = self._parent.device.send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent.idn)) + coupling = self._parent.device._send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent.idn)) if coupling == "DC": - return float(self._parent.device.send_query(":VOLT?")) + return float(self._parent.device._send_query(":VOLT?")) elif coupling == "HV": - return float(self._parent.device.send_query(":VOLT:HV?")) + return float(self._parent.device._send_query(":VOLT:HV?")) else: raise TaborException("Unknown coupling: {}".format(coupling)) @@ -969,6 +978,9 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: class TaborChannelActivatable(ActivatableChannels): + # TODO (LuL): Wouldn't it be better, to have a property named "active" or "enabled" instead of "status" + # and not a setter for that property, but functions like "activate"/"deactivate" or "enable"/"disable"? + # I think this would be more innovative def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() self.parent = marker_channel @@ -1015,7 +1027,7 @@ def _set_channel_tuple(self, channel_tuple) -> None: """ self._channel_tuple = channel_tuple - def select(self) -> None: + def _select(self) -> None: # TODO (LuL): This may be private self.device.send_cmd(":INST:SEL {channel}".format(channel=self.idn)) @@ -1073,7 +1085,7 @@ def remove(self, name: str) -> None: def clear(self) -> None: """Delete all segments and clear memory""" - self._parent.device.channels[0].select() + self._parent.device.channels[0]._select() self._parent.device.send_cmd(":TRAC:DEL:ALL") self._parent.device.send_cmd(":SOUR:SEQ:DEL:ALL") self._parent.device.send_cmd(":ASEQ:DEL") @@ -1081,7 +1093,7 @@ def clear(self) -> None: self._parent.device.send_cmd(":TRAC:DEF 1, 192") self._parent.device.send_cmd(":TRAC:SEL 1") self._parent.device.send_cmd(":TRAC:MODE COMB") - self._parent.device.send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent._idle_segment.get_as_binary()) + self._parent.device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent._idle_segment.get_as_binary()) self._parent._segment_lengths = 192 * np.ones(1, dtype=np.uint32) self._parent._segment_capacity = 192 * np.ones(1, dtype=np.uint32) @@ -1107,6 +1119,11 @@ def arm(self, name: Optional[str]) -> None: else: self._parent.change_armed_program(name) + @property + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program.name for program in self._parent._known_programs.keys()) + class TaborChannelTuple(AWGChannelTuple): def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], @@ -1153,15 +1170,17 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self[TaborProgramManagement].clear() - self._channel_tuple_adapter = ChannelTupleAdapter(self) + self._channel_tuple_adapter: ChannelTupleAdapter @property def channel_tuple_adapter(self) -> ChannelTupleAdapter: + if self._channel_tuple_adapter is None: + self._channel_tuple_adapter = ChannelTupleAdapter(self) return self._channel_tuple_adapter - def select(self) -> None: + def _select(self) -> None: """The channel tuple is selected, which means that the first channel of the channel tuple is selected""" - self.channels[0].select() + self.channels[0]._select() @property def device(self) -> TaborDevice: @@ -1182,7 +1201,7 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: @property def sample_rate(self) -> float: """Returns the sample rate that the channels of a channel tuple have""" - return self.device.send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) + return self.device._send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) @property def total_capacity(self) -> int: @@ -1372,7 +1391,7 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() - self.device.send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) + self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) self._segment_references[segment_index] = 1 self._segment_hashes[segment_index] = hash(segment) @@ -1386,6 +1405,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: segment_index = len(self._segment_capacity) first_segment_number = segment_index + 1 + self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len), paranoia_level=self.internal_paranoia_level) self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number), @@ -1407,7 +1427,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: paranoia_level=self.internal_paranoia_level) else: # flush the capacity - self.device.download_segment_lengths(segment_capacity) + self.device._download_segment_lengths(segment_capacity) # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): @@ -1520,7 +1540,7 @@ def set_program_sequence_table(self, name, new_sequence_table): @with_select @with_configuration_guard - def change_armed_program(self, name: Optional[str]) -> None: + def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add this to ProgramManagement """The armed program of the channel tuple is change to the program with the name 'name'""" if name is None: sequencer_tables = [self._idle_sequence_table] @@ -1564,17 +1584,17 @@ def change_armed_program(self, name: Optional[str]) -> None: # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): self.device.send_cmd("SEQ:SEL {}".format(i + 1)) - self.device.download_sequencer_table(sequencer_table) + self.device._download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables self.device.send_cmd("SEQ:SEL 1") - self.device.download_adv_seq_table(advanced_sequencer_table) + self.device._download_adv_seq_table(advanced_sequencer_table) self._advanced_sequence_table = advanced_sequencer_table self._current_program = name @with_select - def run_current_program(self) -> None: + def run_current_program(self) -> None: # TODO (LuL): Add this to ProgramManagement """ This method starts running the active program @@ -1591,15 +1611,14 @@ def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(program for program in self._known_programs.keys()) - @property - def num_channels(self) -> int: + def num_channels(self) -> int: # TODO (LuL): This is not needed, the caller can call len(...) hisself """ Returns the number of channels that belong to the channel tuple """ return len(self.channels) @property - def num_markers(self) -> int: + def num_markers(self) -> int: # TODO (LuL): This is not needed, the caller can call len(...) hisself """ Returns the number of marker channels that belong to the channel tuple """ @@ -1615,6 +1634,7 @@ def _enter_config_mode(self) -> None: # 2. Select DC as function shape # 3. Select build-in waveform mode + if self.device.is_coupled(): out_cmd = ':OUTP:ALL OFF' else: @@ -1641,7 +1661,7 @@ def _exit_config_mode(self) -> None: # TODO: change implementation for channel synchronisation feature - if self.device.send_query(":INST:COUP:STAT?") == "ON": + if self.device._send_query(":INST:COUP:STAT?") == "ON": # Coupled -> switch all channels at once other_channel_tuple: TaborChannelTuple if self.channels == self.device.channel_tuples[0].channels: @@ -1722,7 +1742,7 @@ def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: """ self._channel_tuple = channel_tuple - def select(self) -> None: + def _select(self) -> None: """ This marker channel is selected and is now the active channel marker of the device """ diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py index b2fab40bb..a3e0efcc7 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -133,11 +133,11 @@ def test_select_marker(self): self.instrument.select_marker(6) self.instrument.select_marker(2) - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + selected = self.instrument._send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') self.instrument.select_marker(1) - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + selected = self.instrument._send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): @@ -145,10 +145,10 @@ def test_select_channel(self): self.instrument.select_channel(6) self.instrument.select_channel(1) - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') + self.assertEqual(self.instrument._send_query(':INST:SEL?'), '1') self.instrument.select_channel(4) - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') + self.assertEqual(self.instrument._send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index a109656e7..b20c5768f 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -142,25 +142,25 @@ def test_amplitude(self): def test_select_marker(self): with self.assertRaises(IndexError): - self.instrument.marker_channels[6].select() + self.instrument.marker_channels[6]._select() - self.instrument.marker_channels[1].select() - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.instrument.marker_channels[1]._select() + selected = self.instrument._send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') - self.instrument.marker_channels[0].select() - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.instrument.marker_channels[0]._select() + selected = self.instrument._send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): with self.assertRaises(IndexError): - self.instrument.channels[6].select() + self.instrument.channels[6]._select() - self.instrument.channels[0].select() - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') + self.instrument.channels[0]._select() + self.assertEqual(self.instrument._send_query(':INST:SEL?'), '1') - self.instrument.channels[3].select() - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') + self.instrument.channels[3]._select() + self.assertEqual(self.instrument._send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): From d5a1d8dba67812f292b6dd75ad1272ff170beb03 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 24 Mar 2020 15:31:55 +0100 Subject: [PATCH 054/107] Rebase conflicts finished - with buggs left --- qupulse/hardware/awgs/channel_tuple_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index a3514ce4c..8692eeeba 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,7 +1,7 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs import TaborChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple +from qupulse.hardware.awgs.base import AWGChannelTuple from qupulse.hardware.awgs.old_base import AWG @@ -14,7 +14,7 @@ class ChannelTupleAdapter(AWG): def __copy__(self) -> None: pass - def __init__(self, channel_tuple: TaborChannelTuple): # TODO (LuL): Not Tabor, but base class: ChannelTuple + def __init__(self, channel_tuple: AWGChannelTuple): # TODO (LuL): Not Tabor, but base class: ChannelTuple self._channel_tuple = channel_tuple def identifier(self) -> str: From 07da7bb415e578051d32ed2be050949481169404 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 25 Mar 2020 09:37:17 +0100 Subject: [PATCH 055/107] Compared with lukas merged branch --- .../hardware/awgs/channel_tuple_wrapper.py | 2 +- qupulse/hardware/awgs/old_base.py | 1 - qupulse/hardware/awgs/old_tabor.py | 840 +++++------------- qupulse/hardware/awgs/tabor.py | 24 - 4 files changed, 224 insertions(+), 643 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index 8692eeeba..e3d5e1069 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,7 +1,7 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs.base import AWGChannelTuple +from qupulse.hardware.awgs import AWGChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple from qupulse.hardware.awgs.old_base import AWG diff --git a/qupulse/hardware/awgs/old_base.py b/qupulse/hardware/awgs/old_base.py index 104629ee3..dd8750907 100644 --- a/qupulse/hardware/awgs/old_base.py +++ b/qupulse/hardware/awgs/old_base.py @@ -257,4 +257,3 @@ def __init__(self, channel): def __str__(self) -> str: return 'Marker or channel not found: {}'.format(self.channel) - diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index 8697ee0dd..6b664d867 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -1,11 +1,9 @@ import fractions -import sys import functools import weakref -import itertools -import operator -from typing import List, Tuple, Set, NamedTuple, Callable, Optional, Any, Sequence, cast, Generator, Union, Dict -from enum import Enum +import logging +import numbers +from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple from collections import OrderedDict # Provided by Tabor electronics for python 2.7 @@ -15,361 +13,20 @@ import numpy as np from qupulse.utils.types import ChannelID -from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times +from qupulse.hardware.util import voltage_to_uint16, find_positions from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling +from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ + make_combined_wave -assert (sys.byteorder == 'little') -__all__ = ['TaborDevice', 'TaborChannelTuple'] +__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] -class TaborSegment: - """Represents one segment of two channels on the device. Convenience class.""" - - __slots__ = ('ch_a', 'ch_b', 'marker_a', 'marker_b') - - def __init__(self, - ch_a: Optional[np.ndarray], - ch_b: Optional[np.ndarray], - marker_a: Optional[np.ndarray], - marker_b: Optional[np.ndarray]): - if ch_a is None and ch_b is None: - raise TaborException('Empty TaborSegments are not allowed') - if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): - raise TaborException('Channel entries to have to have the same length') - - self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) - self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) - - self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) - self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) - - if marker_a is not None and len(marker_a) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') - if marker_b is not None and len(marker_b) * 2 != self.num_points: - raise TaborException('Marker A has to have half of the channels length') - - @classmethod - def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': - data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) - data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() - return cls.from_binary_data(data_a, data_b) - - @classmethod - def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': - ch_b = data_b - - channel_mask = np.uint16(2 ** 14 - 1) - ch_a = np.bitwise_and(data_a, channel_mask) - - marker_a_mask = np.uint16(2 ** 14) - marker_b_mask = np.uint16(2 ** 15) - marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) - - marker_a = np.bitwise_and(marker_data, marker_a_mask) - marker_b = np.bitwise_and(marker_data, marker_b_mask) - - return cls(ch_a=ch_a, - ch_b=ch_b, - marker_a=marker_a, - marker_b=marker_b) - - def __hash__(self) -> int: - return hash(tuple(0 if data is None else bytes(data) - for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) - - def __eq__(self, other: 'TaborSegment'): - def compare_markers(marker_1, marker_2): - if marker_1 is None: - if marker_2 is None: - return True - else: - return not np.any(marker_2) - - elif marker_2 is None: - return not np.any(marker_1) - - else: - return np.array_equal(marker_1, marker_2) - - return (np.array_equal(self.ch_a, other.ch_a) and - np.array_equal(self.ch_b, other.ch_b) and - compare_markers(self.marker_a, other.marker_a) and - compare_markers(self.marker_b, other.marker_b)) - - @property - def data_a(self) -> np.ndarray: - """channel_data and marker data""" - if self.marker_a is None and self.marker_b is None: - return self.ch_a - - if self.ch_a is None: - raise NotImplementedError('What data should be used in a?') - - # copy channel information - data = np.array(self.ch_a) - - if self.marker_a is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) - - if self.marker_b is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) - - return data - - @property - def data_b(self) -> np.ndarray: - """channel_data and marker data""" - return self.ch_b - - @property - def num_points(self) -> int: - return len(self.ch_b) if self.ch_a is None else len(self.ch_a) - - def get_as_binary(self) -> np.ndarray: - assert not (self.ch_a is None or self.ch_b is None) - return make_combined_wave([self]) - - -class TaborSequencing(Enum): - SINGLE = 1 - ADVANCED = 2 - - -class TaborProgram: - def __init__(self, - program: Loop, - device_properties, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]]): - if len(channels) != device_properties['chan_per_part']: - raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) - if len(markers) != device_properties['chan_per_part']: - raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) - channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker - for marker in - markers if - marker is not None) - self._program = program - - self.__waveform_mode = None - self._channels = tuple(channels) - self._markers = tuple(markers) - self.__used_channels = channel_set - self.__device_properties = device_properties - - self._waveforms = [] # type: List[MultiChannelWaveform] - self._sequencer_tables = [] - self._advanced_sequencer_table = [] - - if self.program.repetition_count > 1: - self.program.encapsulate() - - if self.program.depth() > 1: - self.setup_advanced_sequence_mode() - self.__waveform_mode = TaborSequencing.ADVANCED - else: - if self.program.depth() == 0: - self.program.encapsulate() - self.setup_single_sequence_mode() - self.__waveform_mode = TaborSequencing.SINGLE - - @property - def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._markers - - @property - def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._channels - - def sampled_segments(self, - sample_rate: fractions.Fraction, - voltage_amplitude: Tuple[float, float], - voltage_offset: Tuple[float, float], - voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], - Sequence[int]]: - sample_rate = fractions.Fraction(sample_rate, 10 ** 9) - - time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) - - if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): - raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') - - def voltage_to_data(waveform, time, channel): - if self._channels[channel]: - return voltage_to_uint16( - voltage_transformation[channel]( - waveform.get_sampled(channel=self._channels[channel], - sample_times=time)), - voltage_amplitude[channel], - voltage_offset[channel], - resolution=14) - else: - return np.full_like(time, 8192, dtype=np.uint16) - - def get_marker_data(waveform: MultiChannelWaveform, time, marker): - if self._markers[marker]: - markerID = self._markers[marker] - return waveform.get_sampled(channel=markerID, sample_times=time) != 0 - else: - return np.full_like(time, False, dtype=bool) - - segments = np.empty_like(self._waveforms, dtype=TaborSegment) - for i, waveform in enumerate(self._waveforms): - t = time_array[:segment_lengths[i]] - marker_time = t[::2] - segment_a = voltage_to_data(waveform, t, 0) - segment_b = voltage_to_data(waveform, t, 1) - assert (len(segment_a) == len(t)) - assert (len(segment_b) == len(t)) - marker_a = get_marker_data(waveform, marker_time, 0) - marker_b = get_marker_data(waveform, marker_time, 1) - segments[i] = TaborSegment(ch_a=segment_a, - ch_b=segment_b, - marker_a=marker_a, - marker_b=marker_b) - return segments, segment_lengths - - def setup_single_sequence_mode(self) -> None: - assert self.program.depth() == 1 - - sequencer_table = [] - waveforms = OrderedDict() - - for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in self.program): - if waveform in waveforms: - waveform_index = waveforms[waveform] - else: - waveform_index = len(waveforms) - waveforms[waveform] = waveform_index - sequencer_table.append((repetition_count, waveform_index, 0)) - - self._waveforms = tuple(waveforms.keys()) - self._sequencer_tables = [sequencer_table] - self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] - - def setup_advanced_sequence_mode(self) -> None: - assert self.program.depth() > 1 - assert self.program.repetition_count == 1 - - self.program.flatten_and_balance(2) - - min_seq_len = self.__device_properties['min_seq_len'] - max_seq_len = self.__device_properties['max_seq_len'] - - def check_merge_with_next(program, n): - if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and - len(program[n]) + len(program[n + 1]) < max_seq_len): - program[n][len(program[n]):] = program[n + 1][:] - program[n + 1:n + 2] = [] - return True - return False - - def check_partial_unroll(program, n): - st = program[n] - if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: - if sum(entry.repetition_count for entry in st) < min_seq_len: - st.unroll_children() - while len(st) < min_seq_len: - st.split_one_child() - return True - return False - - i = 0 - while i < len(self.program): - self.program[i].assert_tree_integrity() - if len(self.program[i]) > max_seq_len: - raise TaborException('The algorithm is not smart enough to make sequence tables shorter') - elif len(self.program[i]) < min_seq_len: - assert self.program[i].repetition_count > 0 - if self.program[i].repetition_count == 1: - # check if merging with neighbour is possible - if i > 0 and check_merge_with_next(self.program, i - 1): - pass - elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): - pass - - # check if (partial) unrolling is possible - elif check_partial_unroll(self.program, i): - i += 1 - - # check if sequence table can be extended by unrolling a neighbor - elif (i > 0 - and self.program[i - 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): - self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] - self.program[i - 1].repetition_count -= 1 - - elif (i + 1 < len(self.program) - and self.program[i + 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): - self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] - self.program[i + 1].repetition_count -= 1 - - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - elif check_partial_unroll(self.program, i): - i += 1 - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - else: - i += 1 - - for sequence_table in self.program: - assert len(sequence_table) >= self.__device_properties['min_seq_len'] - assert len(sequence_table) <= self.__device_properties['max_seq_len'] - - advanced_sequencer_table = [] - sequencer_tables = [] - waveforms = OrderedDict() - for sequencer_table_loop in self.program: - current_sequencer_table = [] - for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in sequencer_table_loop): - if waveform in waveforms: - wf_index = waveforms[waveform] - else: - wf_index = len(waveforms) - waveforms[waveform] = wf_index - current_sequencer_table.append((repetition_count, wf_index, 0)) - - if current_sequencer_table in sequencer_tables: - sequence_no = sequencer_tables.index(current_sequencer_table) + 1 - else: - sequence_no = len(sequencer_tables) + 1 - sequencer_tables.append(current_sequencer_table) - - advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) - - self._advanced_sequencer_table = advanced_sequencer_table - self._sequencer_tables = sequencer_tables - self._waveforms = tuple(waveforms.keys()) - - @property - def program(self) -> Loop: - return self._program - - def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: - return self._sequencer_tables - - def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: - """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" - return self._advanced_sequencer_table - - @property - def waveform_mode(self) -> str: - return self.__waveform_mode - - -class TaborDevice: +class TaborAWGRepresentation: def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ - :param instr_addr: Instrument address that is forwarded to teawag + :param instr_addr: Instrument address that is forwarded to teawg :param paranoia_level: Paranoia level that is forwarded to teawg :param external_trigger: Not supported yet :param reset: @@ -377,6 +34,7 @@ def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, re """ self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) + self._coupled = None self._clock_marker = [0, 0, 0, 0] @@ -388,15 +46,21 @@ def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, re self.initialize() - self._channel_pair_AB = TaborChannelTuple(self, (1, 2), str(instr_addr) + '_AB') - self._channel_pair_CD = TaborChannelTuple(self, (3, 4), str(instr_addr) + '_CD') + self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') + self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') + + def is_coupled(self) -> bool: + if self._coupled is None: + return self.send_query(':INST:COUP:STAT?') == 'ON' + else: + return self._coupled @property - def channel_pair_AB(self) -> 'TaborChannelTuple': + def channel_pair_AB(self) -> 'TaborChannelPair': return self._channel_pair_AB @property - def channel_pair_CD(self) -> 'TaborChannelTuple': + def channel_pair_CD(self) -> 'TaborChannelPair': return self._channel_pair_CD @property @@ -422,7 +86,7 @@ def dev_properties(self) -> dict: @property def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr,) + self._mirrors + return (self._instr, ) + self._mirrors def send_cmd(self, cmd_str, paranoia_level=None): for instr in self.all_devices: @@ -517,7 +181,7 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): self.select_channel(ch) - self.select_marker((ch - 1) % 2 + 1) + self.select_marker((ch-1) % 2 + 1) for name, query, dtype in name_query_type_list: data[name].append(dtype(self.send_query(query))) return data @@ -574,9 +238,9 @@ def initialize(self) -> None: # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) setup_command = ( - ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " - ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") self.send_cmd(':INST:SEL 1') self.send_cmd(setup_command) self.send_cmd(':INST:SEL 3') @@ -584,6 +248,7 @@ def initialize(self) -> None: def reset(self) -> None: self.send_cmd(':RES') + self._coupled = None self.initialize() self.channel_pair_AB.clear() self.channel_pair_CD.clear() @@ -606,13 +271,11 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: ('program', TaborProgram)]) -def with_configuration_guard(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[[ - 'TaborChannelTuple'], - Any]: +def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], + Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" - @functools.wraps(function_object) - def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: + def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: if channel_pair._configuration_guard_count == 0: channel_pair._enter_config_mode() channel_pair._configuration_guard_count += 1 @@ -627,146 +290,20 @@ def guarding_method(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: return guarding_method -def with_select(function_object: Callable[['TaborChannelTuple', Any], Any]) -> Callable[['TaborChannelTuple'], Any]: - """Asserts the channel pair is selcted when the wrapped function is called""" - +def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: + """Asserts the channel pair is selected when the wrapped function is called""" @functools.wraps(function_object) - def selector(channel_pair: 'TaborChannelTuple', *args, **kwargs) -> Any: + def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: channel_pair.select() return function_object(channel_pair, *args, **kwargs) return selector -class PlottableProgram: - TableEntry = NamedTuple('TableEntry', [('repetition_count', int), - ('element_number', int), - ('jump_flag', int)]) - - def __init__(self, - segments: List[TaborSegment], - sequence_tables: List[List[Tuple[int, int, int]]], - advanced_sequence_table: List[Tuple[int, int, int]]): - self._segments = segments - self._sequence_tables = [[self.TableEntry(*sequence_table_entry) - for sequence_table_entry in sequence_table] - for sequence_table in sequence_tables] - self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) - for adv_seq_entry in advanced_sequence_table] - - @classmethod - def from_read_data(cls, waveforms: List[np.ndarray], - sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], - advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': - return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], - [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], - cls._reformat_rep_seg_jump(advanced_sequence_table)) - - @classmethod - def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: - return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) - for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) - - def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: - if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): - adv_seq_tab = self._advanced_sequence_table[1:] - else: - adv_seq_tab = self._advanced_sequence_table +class TaborChannelPair(AWG): + CONFIG_MODE_PARANOIA_LEVEL = None - # remove idle pulse at end - if with_last_idles: - return adv_seq_tab - else: - while adv_seq_tab[-1] == (1, 1, 0): - adv_seq_tab = adv_seq_tab[:-1] - return adv_seq_tab - - def _iter_segment_table_entry(self, - with_first_idle=False, - with_last_idles=False) -> Generator[TableEntry, None, None]: - for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): - for _ in range(sequence_repeat): - yield from self._sequence_tables[sequence_no - 1] - - def iter_waveforms_and_repetitions(self, - channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): - yield ch_getter(self._segments[segment_no - 1]), segment_repeat - - def iter_samples(self, channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[np.uint16, None, None]: - for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): - waveform = list(waveform) - for _ in range(repetition): - yield from waveform - - def get_as_single_waveform(self, channel: int, - max_total_length: int = 10 ** 9, - with_marker: bool = False) -> Optional[np.ndarray]: - waveforms = self.get_waveforms(channel, with_marker=with_marker) - repetitions = self.get_repetitions() - waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) - - total_length = (repetitions * waveform_lengths).sum() - if total_length > max_total_length: - return None - - result = np.empty(total_length, dtype=np.uint16) - c_idx = 0 - for wf, rep in zip(waveforms, repetitions): - mem = wf.size * rep - target = result[c_idx:c_idx + mem] - - target = target.reshape((rep, wf.size)) - target[:, :] = wf[np.newaxis, :] - c_idx += mem - return result - - def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: - if with_marker: - ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] - else: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1]) - for _, segment_no, _ in self._iter_segment_table_entry()] - - def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1])] - - def get_repetitions(self) -> np.ndarray: - return np.fromiter((segment_repeat - for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) - - def __eq__(self, other): - for ch in (0, 1): - for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), - other.iter_samples(ch, True, False)): - if x != y: - return False - return True - - def to_builtin(self) -> dict: - waveforms = [[wf.data_a.tolist() for wf in self._segments], - [wf.data_b.tolist() for wf in self._segments]] - return {'waveforms': waveforms, - 'seq_tables': self._sequence_tables, - 'adv_seq_table': self._advanced_sequence_table} - - @classmethod - def from_builtin(cls, data: dict) -> 'PlottableProgram': - waveforms = data['waveforms'] - waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) - for data_a, data_b in zip(*waveforms)] - return cls(waveforms, data['seq_tables'], data['adv_seq_table']) - - -class TaborChannelTuple(AWG): - def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identifier: str): + def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): super().__init__(identifier) self._device = weakref.ref(tabor_device) @@ -777,12 +314,13 @@ def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identif raise ValueError('Invalid channel pair: {}'.format(channels)) self._channels = channels - self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), None, None) + self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + None, None) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -796,17 +334,34 @@ def __init__(self, tabor_device: TaborDevice, channels: Tuple[int, int], identif self._sequencer_tables = None self._advanced_sequence_table = None + self._internal_paranoia_level = 0 + self.clear() + @property + def internal_paranoia_level(self) -> Optional[int]: + return self._internal_paranoia_level + + @internal_paranoia_level.setter + def internal_paranoia_level(self, paranoia_level: Optional[int]): + """ Sets the paranoia level with which commands from within methods are called """ + assert paranoia_level in (None, 0, 1, 2) + self._internal_paranoia_level = paranoia_level + def select(self) -> None: - self.device.send_cmd(':INST:SEL {}'.format(self._channels[0])) + self.device.send_cmd(':INST:SEL {}'.format(self._channels[0]), + paranoia_level=self.internal_paranoia_level) @property def total_capacity(self) -> int: return int(self.device.dev_properties['max_arb_mem']) // 2 @property - def device(self) -> TaborDevice: + def logger(self): + return logging.getLogger("qupulse.tabor") + + @property + def device(self) -> TaborAWGRepresentation: return self._device() def free_program(self, name: str) -> TaborProgramMemory: @@ -848,9 +403,9 @@ def read_waveforms(self) -> List[np.ndarray]: waveforms = [] uploaded_waveform_indices = np.flatnonzero(self._segment_references) + 1 for segment in uploaded_waveform_indices: - device.send_cmd(':TRAC:SEL {}'.format(segment)) + device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) - device.send_cmd(':TRAC:SEL {}'.format(old_segment)) + device.send_cmd(':TRAC:SEL {}'.format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms @with_select @@ -861,9 +416,9 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] sequences = [] uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: - device.send_cmd(':SEQ:SEL {}'.format(sequence)) + device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) - device.send_cmd(':SEQ:SEL {}'.format(old_sequence)) + device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences @with_select @@ -899,55 +454,42 @@ def upload(self, name: str, make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) + sample_rate=fractions.Fraction(sample_rate, 10**9)) - # helper to restore previous state if upload is impossible - to_restore = None - to_restore_was_armed = False if name in self._known_programs: if force: - if self._current_program == name: - to_restore_was_armed = True - - # save old program to restore in on error - to_restore = name, self.free_program(name) + self.free_program(name) else: raise ValueError('{} is already known on {}'.format(name, self.identifier)) - try: - # parse to tabor program - tabor_program = TaborProgram(program, - channels=tuple(channels), - markers=markers, - device_properties=self.device.dev_properties) - - # They call the peak to peak range amplitude - ranges = (self.device.amplitude(self._channels[0]), - self.device.amplitude(self._channels[1])) - - voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) - - if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: - voltage_offsets = (0, 0) - elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets = (self.device.offset(self._channels[0]), - self.device.offset(self._channels[1])) - else: - raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) - - segments, segment_lengths = tabor_program.sampled_segments(sample_rate=sample_rate, - voltage_amplitude=voltage_amplitudes, - voltage_offset=voltage_offsets, - voltage_transformation=voltage_transformation) - - waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, - segment_lengths) - except: - if to_restore: - self._restore_program(*to_restore) - if to_restore_was_armed: - self.change_armed_program(name) - raise + # They call the peak to peak range amplitude + ranges = (self.device.amplitude(self._channels[0]), + self.device.amplitude(self._channels[1])) + + voltage_amplitudes = (ranges[0]/2, ranges[1]/2) + + if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets = (0, 0) + elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets = (self.device.offset(self._channels[0]), + self.device.offset(self._channels[1])) + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) + + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=markers, + device_properties=self.device.dev_properties, + sample_rate=sample_rate / 10**9, + amplitudes=voltage_amplitudes, + offsets=voltage_offsets, + voltage_transformations=voltage_transformation) + + segments, segment_lengths = tabor_program.get_sampled_segments() + + waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, + segment_lengths) self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 @@ -957,7 +499,7 @@ def upload(self, name: str, waveform_to_segment[wf_index] = segment_index if np.any(to_amend): - segments_to_amend = segments[to_amend] + segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, @@ -968,17 +510,17 @@ def upload(self, name: str, def clear(self) -> None: """Delete all segments and clear memory""" self.device.select_channel(self._channels[0]) - self.device.send_cmd(':TRAC:DEL:ALL') - self.device.send_cmd(':SOUR:SEQ:DEL:ALL') - self.device.send_cmd(':ASEQ:DEL') + self.device.send_cmd(':TRAC:DEL:ALL', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':SOUR:SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':ASEQ:DEL', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:DEF 1, 192') - self.device.send_cmd(':TRAC:SEL 1') - self.device.send_cmd(':TRAC:MODE COMB') + self.device.send_cmd(':TRAC:DEF 1, 192', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:SEL 1', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) - self._segment_lengths = 192 * np.ones(1, dtype=np.uint32) - self._segment_capacity = 192 * np.ones(1, dtype=np.uint32) + self._segment_lengths = 192*np.ones(1, dtype=np.uint32) + self._segment_capacity = 192*np.ones(1, dtype=np.uint32) self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) self._segment_references = np.ones(1, dtype=np.uint32) @@ -988,8 +530,7 @@ def clear(self) -> None: self._known_programs = dict() self.change_armed_program(None) - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[ - np.ndarray, np.ndarray, np.ndarray]: + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ 1. Find known segments 2. Find empty spaces with fitting length @@ -1009,8 +550,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths known_pos_in_memory = waveform_to_segment[known] - assert len(known_pos_in_memory) == 0 or np.all( - self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) new_reference_counter = self._segment_references.copy() new_reference_counter[known_pos_in_memory] += 1 @@ -1037,8 +577,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths if free_segment_count == 0: break - pos_of_same_length = np.logical_and(free_segments, - segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) idx_same_length = np.argmax(pos_of_same_length) if pos_of_same_length[idx_same_length]: free_segments[idx_same_length] = False @@ -1084,12 +623,13 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: segment_no = segment_index + 1 - self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points)) + self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), + paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points - self.device.send_cmd(':TRAC:SEL {}'.format(segment_no)) + self.device.send_cmd(':TRAC:SEL {}'.format(segment_no), paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB') + self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) @@ -1106,9 +646,12 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: segment_index = len(self._segment_capacity) first_segment_number = segment_index + 1 - self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len)) - self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number)) - self.device.send_cmd(':TRAC:MODE COMB') + self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len), + paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number), + paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:MODE COMB', + paranoia_level=self.internal_paranoia_level) self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) @@ -1119,14 +662,16 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: if len(segments) < old_to_update: for i, segment in enumerate(segments): current_segment_number = first_segment_number + i - self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points)) + self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points), + paranoia_level=self.internal_paranoia_level) else: # flush the capacity self.device.download_segment_lengths(segment_capacity) # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(':TRAC:DEF {},{}'.format(i + 1, segment_lengths[i])) + self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i]), + paranoia_level=self.internal_paranoia_level) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -1141,7 +686,7 @@ def cleanup(self) -> None: """Discard all segments after the last which is still referenced""" reserved_indices = np.flatnonzero(self._segment_references > 0) old_end = len(self._segment_lengths) - new_end = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 self._segment_lengths = self._segment_lengths[:new_end] self._segment_capacity = self._segment_capacity[:new_end] self._segment_hashes = self._segment_hashes[:new_end] @@ -1151,8 +696,9 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i + 1) - for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) + self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) + for i in range(chunk_start, min(chunk_start+chunk_size, old_end))), + paranoia_level=self.internal_paranoia_level) except Exception as e: raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e @@ -1167,23 +713,80 @@ def remove(self, name: str) -> None: self.free_program(name) self.cleanup() + @with_configuration_guard + def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: + """ Joins the given commands into one and executes it with configuration guard. + + Args: + commands: Commands that should be executed. + """ + cmd_str = ";".join(commands) + self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) + + def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: + """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters + in program memory and device's (adv.) sequence tables if program is current program. + + If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to + enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) + and set device._is_coupled. + + Args: + program_name: Name of program which should be changed. + parameters: Names of volatile parameters and respective values to which they should be set. + """ + + waveform_to_segment_index, program = self._known_programs[program_name] + + modifications = program.update_volatile_parameters(parameters) + + self.logger.debug("parameter modifications: %r" % modifications) + + if not modifications: + self.logger.info("There are no volatile parameters to update. Either there are no volatile parameters with " + "these names,\nthe respective repetition counts already have the given values or the " + "volatile parameters were dropped during upload.") + return + + if program_name == self._current_program: + commands = [] + + for position, entry in modifications.items(): + if not entry.repetition_count > 0: + raise ValueError('Repetition must be > 0') + + if isinstance(position, int): + commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, + entry.repetition_count, entry.jump_flag)) + else: + table_num, step_num = position + commands.append(":SEQ:SEL {}".format(table_num + 2)) + commands.append(":SEQ:DEF {},{},{},{}".format(step_num, + waveform_to_segment_index[entry.element_id] + 1, + entry.repetition_count, entry.jump_flag)) + self._execute_multiple_commands_with_config_guard(commands) + + # Wait until AWG is finished + _ = self.device.main_instrument._visa_inst.query('*OPC?') + def set_marker_state(self, marker: int, active: bool) -> None: - """Sets the marker state of this channel pair. According to the manual one cannot turn them off/on separately.""" + """Sets the marker state of this channel pair. + According to the manual one cannot turn them off/on separately.""" command_string = ':INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}' command_string = command_string.format( channel=self._channels[0], marker=(1, 2)[marker], active='ON' if active else 'OFF') - self.device.send_cmd(command_string) + self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) def set_channel_state(self, channel, active) -> None: command_string = ':INST:SEL {}; :OUTP {}'.format(self._channels[channel], 'ON' if active else 'OFF') - self.device.send_cmd(command_string) + self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) @with_select def arm(self, name: str) -> None: if self._current_program == name: - self.device.send_cmd('SEQ:SEL 1') + self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) else: self.change_armed_program(name) @@ -1205,7 +808,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # translate waveform number to actual segment sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for (rep_count, wf_index, jump_flag) in sequencer_table] + for ((rep_count, wf_index, jump_flag), _) in sequencer_table] for sequencer_table in program.get_sequencer_tables()] # insert idle sequence @@ -1230,17 +833,17 @@ def change_armed_program(self, name: Optional[str]) -> None: advanced_sequencer_table.append((1, 1, 0)) # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd('SEQ:DEL:ALL') + self.device.send_cmd('SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) self._sequencer_tables = [] - self.device.send_cmd('ASEQ:DEL') + self.device.send_cmd('ASEQ:DEL', paranoia_level=self.internal_paranoia_level) self._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i + 1)) + self.device.send_cmd('SEQ:SEL {}'.format(i+1), paranoia_level=self.internal_paranoia_level) self.device.download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables - self.device.send_cmd('SEQ:SEL 1') + self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) self.device.download_adv_seq_table(advanced_sequencer_table) self._advanced_sequence_table = advanced_sequencer_table @@ -1250,14 +853,14 @@ def change_armed_program(self, name: Optional[str]) -> None: @with_select def run_current_program(self) -> None: if self._current_program: - self.device.send_cmd(':TRIG') + self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) else: raise RuntimeError('No program active') @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" - return set(program.name for program in self._known_programs.keys()) + return set(program for program in self._known_programs.keys()) @property def sample_rate(self) -> float: @@ -1272,66 +875,69 @@ def num_markers(self) -> int: return 2 def _enter_config_mode(self) -> None: - """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the sequencing is disabled - as the manual states this speeds up sequence validation when uploading multiple sequences.""" + """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the + sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. + When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip.""" if self._is_in_config_mode is False: - # 1. Selct channel pair + # 1. Select channel pair # 2. Select DC as function shape # 3. Select build-in waveform mode - if self.device.send_query(':INST:COUP:STAT?') == 'ON': - self.device.send_cmd(':OUTP:ALL OFF') + if self.device.is_coupled(): + out_cmd = ':OUTP:ALL OFF' else: - self.device.send_cmd(':INST:SEL {}; :OUTP OFF; :INST:SEL {}; :OUTP OFF'.format(*self._channels)) + out_cmd = ':INST:SEL {};:OUTP OFF;:INST:SEL {};:OUTP OFF'.format(*self._channels) + + marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - self.set_marker_state(0, False) - self.set_marker_state(1, False) - self.device.send_cmd(':SOUR:FUNC:MODE FIX') + wf_mode_cmd = ':SOUR:FUNC:MODE FIX' + cmd = ';'.join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = True - @with_select def _exit_config_mode(self) -> None: """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" - if self.device.send_query(':INST:COUP:STAT?') == 'ON': + sel_ch = ':INST:SEL {}'.format(self._channels[0]) + aseq_cmd = ':SOUR:FUNC:MODE ASEQ;SEQ:SEL 1' + + cmds = [sel_ch, aseq_cmd] + + if self.device.is_coupled(): # Coupled -> switch all channels at once if self._channels == (1, 2): other_channel_pair = self.device.channel_pair_CD else: + assert self._channels == (3, 4) other_channel_pair = self.device.channel_pair_AB if not other_channel_pair._is_in_config_mode: - self.device.send_cmd(':SOUR:FUNC:MODE ASEQ') - self.device.send_cmd(':SEQ:SEL 1') - self.device.send_cmd(':OUTP:ALL ON') + cmds.append(':OUTP:ALL ON') else: - self.device.send_cmd(':SOUR:FUNC:MODE ASEQ') - self.device.send_cmd(':SEQ:SEL 1') + # ch 0 already selected + cmds.append(':OUTP ON; :INST:SEL {}; :OUTP ON'.format(self._channels[1])) - self.device.send_cmd(':INST:SEL {}; :OUTP ON; :INST:SEL {}; :OUTP ON'.format(*self._channels)) - - self.set_marker_state(0, True) - self.set_marker_state(1, True) + cmds.append(':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') + cmds.append(':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') + cmd = ';'.join(cmds) + self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = False -class TaborException(Exception): - pass - - class TaborUndefinedState(TaborException): """If this exception is raised the attached tabor device is in an undefined state. It is highly recommended to call reset it.""" - def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): + def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): super().__init__(*args) self.device = device def reset_device(self): - if isinstance(self.device, TaborDevice): + if isinstance(self.device, TaborAWGRepresentation): self.device.reset() - elif isinstance(self.device, TaborChannelTuple): + elif isinstance(self.device, TaborChannelPair): self.device.clear() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 840ff6bf0..84e01d077 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -22,27 +22,6 @@ # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech # Beware of the string encoding change! import teawg -import numpy as np - -from qupulse.hardware.awgs.features.amplitude_offset_feature import ChannelAmplitudeOffsetFeature -from qupulse.hardware.awgs.features.device_mirror_feature import DeviceMirrorFeature -from qupulse.utils.types import ChannelID -from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times -from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling - - -from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple - - -# TODO: ??? -assert (sys.byteorder == 'little') - - -# TODO: ??? -# __all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] - -######################################################################################################################## # What does this mean? assert (sys.byteorder == "little") @@ -1192,7 +1171,6 @@ def channels(self) -> Collection["TaborChannel"]: """Returns all channels of the channel tuple""" return self._channels - @property def marker_channels(self) -> Collection["TaborMarkerChannel"]: """Returns all marker channels of the channel tuple""" @@ -1634,7 +1612,6 @@ def _enter_config_mode(self) -> None: # 2. Select DC as function shape # 3. Select build-in waveform mode - if self.device.is_coupled(): out_cmd = ':OUTP:ALL OFF' else: @@ -1648,7 +1625,6 @@ def _enter_config_mode(self) -> None: self.device.send_cmd(":SOUR:FUNC:MODE FIX") - wf_mode_cmd = ':SOUR:FUNC:MODE FIX' cmd = ';'.join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) From 4e72b7978eecf861d299c6b9d4556d1b37956751 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 25 Mar 2020 12:53:25 +0100 Subject: [PATCH 056/107] old simulator based tests runs, but throws asserations fail --- qupulse/_program/tabor.py | 2 +- qupulse/hardware/awgs/old_tabor.py | 3 + .../old_tabor_simulator_based_tests.py | 85 ++++++++++++++----- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 96359b34c..5579661ca 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -9,7 +9,7 @@ import numpy as np from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.awgs.base import ProgramEntry +from qupulse.hardware.awgs.old_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 diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index 6b664d867..ec399b903 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -93,9 +93,12 @@ def send_cmd(self, cmd_str, paranoia_level=None): instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: + print("Querry - " + query_str) if query_mirrors: + print("RESULT - " + tuple(instr.send_query(query_str) for instr in self.all_devices)) return tuple(instr.send_query(query_str) for instr in self.all_devices) else: + print("RESULT - " + self._instr.send_query(query_str)) return self._instr.send_query(query_str) def send_binary_data(self, pref, bin_dat, paranoia_level=None): diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py index a3e0efcc7..8143442c5 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -7,8 +7,9 @@ import pytabor import numpy as np -from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, PlottableProgram - +from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair +from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry +from typing import List, Tuple, Optional, Any class TaborSimulatorManager: def __init__(self, @@ -55,9 +56,9 @@ def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_ti time.sleep(0.1) def connect(self): - self.instrument = TaborDevice('127.0.0.1', - reset=True, - paranoia_level=2) + self.instrument = TaborAWGRepresentation('127.0.0.1', + reset=True, + paranoia_level=2) if self.instrument.main_instrument.visa_inst is None: raise RuntimeError('Could not connect to simulator') @@ -99,6 +100,16 @@ def tearDown(self): self.instrument.reset() self.simulator_manager.disconnect() + @staticmethod + def to_new_sequencer_tables(sequencer_tables: List[List[Tuple[int, int, int]]] + ) -> List[List[Tuple[TableDescription, Optional[Any]]]]: + return [[(TableDescription(*entry), None) for entry in sequencer_table] + for sequencer_table in sequencer_tables] + + @staticmethod + def to_new_advanced_sequencer_table(advanced_sequencer_table: List[Tuple[int, int, int]]) -> List[TableDescription]: + return [TableDescription(*entry) for entry in advanced_sequencer_table] + class TaborAWGRepresentationTests(TaborSimulatorBasedTest): def __init__(self, *args, **kwargs): @@ -133,11 +144,11 @@ def test_select_marker(self): self.instrument.select_marker(6) self.instrument.select_marker(2) - selected = self.instrument._send_query(':SOUR:MARK:SEL?') + selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') self.instrument.select_marker(1) - selected = self.instrument._send_query(':SOUR:MARK:SEL?') + selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): @@ -145,10 +156,10 @@ def test_select_channel(self): self.instrument.select_channel(6) self.instrument.select_channel(1) - self.assertEqual(self.instrument._send_query(':INST:SEL?'), '1') + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') self.instrument.select_channel(4) - self.assertEqual(self.instrument._send_query(':INST:SEL?'), '4') + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): @@ -160,18 +171,21 @@ def setUp(self): zero = np.ones(192, dtype=np.uint16) * 2**13 sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) - self.segments = [TaborSegment(ramp_up, ramp_up, None, None), - TaborSegment(ramp_down, zero, None, None), - TaborSegment(sine, sine, None, None)] + self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), + TaborSegment.from_sampled(ramp_down, zero, None, None), + TaborSegment.from_sampled(sine, sine, None, None)] - self.zero_segment = TaborSegment(zero, zero, None, None) + self.zero_segment = TaborSegment.from_sampled(zero, zero, None, None) # program 1 - self.sequence_tables = [[(10, 0, 0), (10, 1, 0), (10, 0, 0), (10, 1, 0)], - [(1, 0, 0), (1, 1, 0), (1, 0, 0), (1, 1, 0)]] + self.sequence_tables_raw = [[(10, 0, 0), (10, 1, 0), (10, 0, 0), (10, 1, 0)], + [(1, 0, 0), (1, 1, 0), (1, 0, 0), (1, 1, 0)]] self.advanced_sequence_table = [(1, 1, 0), (1, 2, 0)] - self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') + self.sequence_tables = self.to_new_sequencer_tables(self.sequence_tables_raw) + self.advanced_sequence_table = self.to_new_advanced_sequencer_table(self.advanced_sequence_table) + + self.channel_pair = TaborChannelPair(self.instrument, (1, 2), 'tabor_unit_test') def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: @@ -183,6 +197,12 @@ def get_sequencer_tables(): def get_advanced_sequencer_table(): return advanced_sequencer_table + @staticmethod + def update_volatile_parameters(parameters): + modifications = {1: TableEntry(repetition_count=5, element_number=2, jump_flag=0), + (0, 1): TableDescription(repetition_count=50, element_id=1, jump_flag=0)} + return modifications + markers = (None, None) channels = (1, 2) @@ -215,13 +235,13 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequece_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) + actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) for rep, index, jump in table] - for table in self.sequence_tables] + for table in self.sequence_tables_raw] expected = list(tuple(np.asarray(d) for d in zip(*table)) - for table in actual_sequece_tables) + for table in actual_sequence_tables) np.testing.assert_equal(sequence_tables, expected) @@ -236,3 +256,30 @@ def test_read_advanced_sequencer_table(self): advanced_table = self.channel_pair.read_advanced_sequencer_table() np.testing.assert_equal(advanced_table, expected) + + def test_set_volatile_parameter(self): + self.channel_pair._amend_segments(self.segments) + self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) + + para = {'a': 5} + actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index + 2, jump) + for rep, index, jump in table] + for table in self.sequence_tables_raw] + + actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + + self.channel_pair.set_volatile_parameters('dummy_program', parameters=para) + + actual_sequence_tables[1][1] = (50, 3, 0) + actual_advanced_table[2] = (5, 3, 0) + + sequence_table = self.channel_pair.read_sequence_tables() + expected = list(tuple(np.asarray(d) + for d in zip(*table)) + for table in actual_sequence_tables) + np.testing.assert_equal(sequence_table, expected) + + advanced_table = self.channel_pair.read_advanced_sequencer_table() + expected = list(np.asarray(d) + for d in zip(*actual_advanced_table)) + np.testing.assert_equal(advanced_table, expected) From 69030b1b0cf7856065249e0ef2382b510f45eecc Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 25 Mar 2020 16:04:13 +0100 Subject: [PATCH 057/107] a few bugs from the rebase are fixed --- qupulse/_program/tabor.py | 2 +- qupulse/hardware/awgs/base.py | 119 +++++++++++++++++- .../hardware/awgs/channel_tuple_wrapper.py | 2 +- qupulse/hardware/awgs/old_tabor.py | 2 +- qupulse/hardware/awgs/tabor.py | 13 +- tests/_program/tabor_tests.py | 2 +- .../old_tabor_simulator_based_tests.py | 2 +- tests/hardware/tabor_dummy_based_tests.py | 2 +- tests/hardware/tabor_simulator_based_tests.py | 2 + 9 files changed, 130 insertions(+), 16 deletions(-) diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 5579661ca..96359b34c 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -9,7 +9,7 @@ import numpy as np from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.awgs.old_base import ProgramEntry +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 diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 29e168aea..6bb691e4e 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,10 +1,15 @@ from abc import ABC, abstractmethod -from typing import Optional +from typing import Optional, Tuple, Sequence, Callable, List +from collections import OrderedDict +import numpy + +from qupulse._program._loop import Loop +from qupulse._program.waveforms import Waveform from qupulse.hardware.awgs import channel_tuple_wrapper from qupulse.hardware.awgs.base_features import Feature, FeatureAble -from qupulse.utils.types import Collection - +from qupulse.hardware.util import get_sample_times +from qupulse.utils.types import Collection, TimeType, ChannelID __all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature", "AWGChannelTupleFeature"] @@ -180,3 +185,111 @@ class AWGMarkerChannel(_BaseAWGChannel, ABC): 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(Exception): + + 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) + + +class ProgramEntry: + """This is a helper class for implementing awgs drivers. A driver can subclass it to help organizing sampled + waveforms""" + def __init__(self, loop: Loop, + channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], + amplitudes: Tuple[float, ...], + offsets: Tuple[float, ...], + voltage_transformations: Tuple[Optional[Callable], ...], + sample_rate: TimeType, + waveforms: Sequence[Waveform] = None): + """ + + Args: + loop: + channels: + markers: + amplitudes: + offsets: + voltage_transformations: + sample_rate: + waveforms: These waveforms are sampled and stored in _waveforms. If None the waveforms are extracted from + loop + """ + assert len(channels) == len(amplitudes) == len(offsets) == len(voltage_transformations) + + self._channels = tuple(channels) + self._markers = tuple(markers) + self._amplitudes = tuple(amplitudes) + self._offsets = tuple(offsets) + self._voltage_transformations = tuple(voltage_transformations) + + self._sample_rate = sample_rate + + self._loop = loop + + if waveforms is None: + waveforms = OrderedDict((node.waveform, None) + for node in loop.get_depth_first_iterator() if node.is_leaf()).keys() + if waveforms: + self._waveforms = OrderedDict(zip(waveforms, self._sample_waveforms(waveforms))) + else: + self._waveforms = OrderedDict() + + def _sample_empty_channel(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: + """Override this in derived class to change how """ + return None + + def _sample_empty_marker(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: + return None + + def _sample_waveforms(self, waveforms: Sequence[Waveform]) -> List[Tuple[Tuple[numpy.ndarray, ...], + Tuple[numpy.ndarray, ...]]]: + sampled_waveforms = [] + + time_array, segment_lengths = get_sample_times(waveforms, self._sample_rate) + for waveform, segment_length in zip(waveforms, segment_lengths): + wf_time = time_array[:segment_length] + + sampled_channels = [] + for channel, trafo, amplitude, offset in zip(self._channels, self._voltage_transformations, + self._amplitudes, self._offsets): + if channel is None: + sampled_channels.append(self._sample_empty_channel()) + else: + sampled = waveform.get_sampled(channel, wf_time) + if trafo is not None: + sampled = trafo(sampled) + sampled = sampled - offset + sampled /= amplitude + sampled_channels.append(waveform.get_sampled(channel, wf_time)) + + sampled_markers = [] + for marker in self._markers: + if marker is None: + sampled_markers.append(None) + else: + sampled_markers.append(waveform.get_sampled(marker, wf_time) != 0) + + sampled_waveforms.append((tuple(sampled_channels), tuple(sampled_markers))) + return sampled_waveforms + + +class OutOfWaveformMemoryException(Exception): + + def __str__(self) -> str: + return "Out of memory error adding waveform to waveform memory." + + +class ChannelNotFoundException(Exception): + def __init__(self, channel): + self.channel = channel + + def __str__(self) -> str: + return 'Marker or channel not found: {}'.format(self.channel) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index e3d5e1069..95a63b8c7 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,7 +1,7 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs import AWGChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple +from qupulse.hardware.awgs.base import AWGChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple from qupulse.hardware.awgs.old_base import AWG diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index ec399b903..e56192a47 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -16,7 +16,7 @@ from qupulse._program._loop import Loop, make_compatible from qupulse.hardware.util import voltage_to_uint16, find_positions from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling -from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ +from qupulse._program.old_tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ make_combined_wave diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 84e01d077..1290285cc 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,10 +1,12 @@ import fractions import functools import itertools +import numbers import operator import sys from enum import Enum -from typing import Optional, Set, Tuple, Callable, Dict, Union, Any, Iterable, List, NamedTuple, cast, Generator +from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple, \ + Generator, Iterable from collections import OrderedDict import numpy as np from qupulse import ChannelID @@ -13,10 +15,12 @@ from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ ProgramManagement, ActivatableChannels -from qupulse.hardware.util import voltage_to_uint16, make_combined_wave, find_positions, get_sample_times +from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times from qupulse.utils.types import Collection from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel from typing import Sequence +from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ + make_combined_wave # Provided by Tabor electronics for python 2.7 # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech @@ -1248,8 +1252,6 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.device.get_readable_device(simulator=True).read_adv_seq_table() - - # upload im Feature def read_complete_program(self) -> PlottableProgram: @@ -1392,7 +1394,6 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: paranoia_level=self.internal_paranoia_level) self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) - old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) segment_lengths = np.concatenate((self._segment_lengths, new_lengths)) @@ -1461,9 +1462,7 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu program_name: Name of program which should be changed. parameters: Names of volatile parameters and respective values to which they should be set. """ - waveform_to_segment_index, program = self._known_programs[program_name] - modifications = program.update_volatile_parameters(parameters) self.logger.debug("parameter modifications: %r" % modifications) diff --git a/tests/_program/tabor_tests.py b/tests/_program/tabor_tests.py index f874f718c..5663c225c 100644 --- a/tests/_program/tabor_tests.py +++ b/tests/_program/tabor_tests.py @@ -11,7 +11,7 @@ from teawg import model_properties_dict -from qupulse._program.tabor import TaborException, TaborProgram, \ +from qupulse._program.old_tabor import TaborException, TaborProgram, \ TaborSegment, TaborSequencing, PlottableProgram, TableDescription, make_combined_wave, TableEntry from qupulse._program._loop import Loop from qupulse._program.volatile import VolatileRepetitionCount diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py index 8143442c5..5e6aa53fa 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -8,7 +8,7 @@ import numpy as np from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair -from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry +from qupulse._program.old_tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry from typing import List, Tuple, Optional, Any class TaborSimulatorManager: diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py index 687e4f902..bb55a8f06 100644 --- a/tests/hardware/tabor_dummy_based_tests.py +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -181,7 +181,7 @@ def setUpClass(cls): from tests.pulses.sequencing_dummies import DummyWaveform - from qupulse._program.tabor import make_combined_wave + from qupulse._program.old_tabor import make_combined_wave cls.DummyWaveform = DummyWaveform cls.TaborChannelPair = TaborChannelPair diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index b20c5768f..78a43cc24 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -3,10 +3,12 @@ import time import platform import os +from typing import List, Tuple, Optional, Any import pytabor import numpy as np +from qupulse._program.tabor import TableDescription, TableEntry from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ TaborOffsetAmplitude From 560d4d3a36eb7bdcc711dc15997471b2cae2f770 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 25 Mar 2020 16:04:48 +0100 Subject: [PATCH 058/107] a few bugs from the rebase are fixed --- qupulse/_program/old_tabor.py | 727 ++++++++++++++++++++++++++++++++++ 1 file changed, 727 insertions(+) create mode 100644 qupulse/_program/old_tabor.py diff --git a/qupulse/_program/old_tabor.py b/qupulse/_program/old_tabor.py new file mode 100644 index 000000000..5579661ca --- /dev/null +++ b/qupulse/_program/old_tabor.py @@ -0,0 +1,727 @@ +import sys +from typing import NamedTuple, Optional, List, Generator, Tuple, Sequence, Mapping, Union, Dict, FrozenSet, cast +from enum import Enum +import operator +from collections import OrderedDict +import itertools +import numbers + +import numpy as np + +from qupulse.utils.types import ChannelID, TimeType +from qupulse.hardware.awgs.old_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 + +assert(sys.byteorder == 'little') + + +TableEntry = NamedTuple('TableEntry', [('repetition_count', int), + ('element_number', int), + ('jump_flag', int)]) +TableEntry.__doc__ = ("Entry in sequencing or advanced sequencer table as uploaded to the AWG with" + "download_adv_seq_table or download_sequencer_table") + +TableDescription = NamedTuple('TableDescription', [('repetition_count', int), + ('element_id', int), + ('jump_flag', int)]) +TableDescription.__doc__ = ("Entry in sequencing or advanced sequencer table but with an element 'reference' instead of" + "the hardware bound 'number'") + +PositionalEntry = NamedTuple('PositionalEntry', [('position', int), + ('element_number', int), + ('repetition_count', int), + ('jump_flag', int)]) +PositionalEntry.__doc__ = ("Entry in sequencing or advanced sequencer table as uploaded with :SEQ:DEF" + "or :ASEQ:DEF") + + +class TaborException(Exception): + pass + + +class TaborSegment: + """Represents one segment of two channels on the device. Convenience class. The data is stored in native format""" + + QUANTUM = 16 + ZERO_VAL = np.uint16(8192) + CHANNEL_MASK = np.uint16(2**14 - 1) + MARKER_A_MASK = np.uint16(2**14) + MARKER_B_MASK = np.uint16(2**15) + + __slots__ = ('_data', '_hash') + + @staticmethod + def _data_a_view(data: np.ndarray, writable: bool) -> np.ndarray: + view = data[:, 1, :] + assert not writable or view.base is data + return view + + @staticmethod + def _data_b_view(data: np.ndarray, writable: bool) -> np.ndarray: + view = data[:, 0, :] + assert not writable or view.base is data + return view + + @staticmethod + def _marker_data_view(data: np.ndarray, writable: bool) -> np.ndarray: + view = data.reshape((-1, 2, 2, 8))[:, 1, 1, :] + assert not writable or view.base is data + return view + + def __init__(self, *, + data: np.ndarray): + assert data.ndim == 3 + n_quanta, n_channels, quantum_size = data.shape + assert n_channels == 2 + assert quantum_size == self.QUANTUM + assert data.dtype is np.dtype('uint16') + self._data = data + self._data.flags.writeable = False + + # shape is not included because it only depends on the size i.e. (n_quantum, 2, 16) + self._hash = hash(self._data.tobytes()) + + @property + def native(self) -> np.ndarray: + """You must not change native (data or shape) + + Returns: + An array with shape (n_quanta, 2, 16) + """ + return self._data + + @property + def data_a(self) -> np.ndarray: + return self._data_a_view(self._data, writable=False).reshape(-1) + + @property + def data_b(self) -> np.ndarray: + return self._data_b_view(self._data, writable=False).reshape(-1) + + @classmethod + def from_sampled(cls, + ch_a: Optional[np.ndarray], + ch_b: Optional[np.ndarray], + marker_a: Optional[np.ndarray], + marker_b: Optional[np.ndarray]) -> 'TaborSegment': + num_points = set() + if ch_a is not None: + assert ch_a.ndim == 1 + assert ch_a.dtype is np.dtype('uint16') + num_points.add(ch_a.size) + if ch_b is not None: + assert ch_b.ndim == 1 + assert ch_b.dtype is np.dtype('uint16') + num_points.add(ch_b.size) + if marker_a is not None: + assert marker_a.ndim == 1 + marker_a = marker_a.astype(dtype=bool) + num_points.add(marker_a.size * 2) + if marker_b is not None: + assert marker_b.ndim == 1 + marker_b = marker_b.astype(dtype=bool) + num_points.add(marker_b.size * 2) + + if len(num_points) == 0: + raise TaborException('Empty TaborSegments are not allowed') + elif len(num_points) > 1: + raise TaborException('Channel entries have to have the same length') + num_points, = num_points + + assert num_points % cls.QUANTUM == 0 + + data = np.full((num_points // cls.QUANTUM, 2, cls.QUANTUM), cls.ZERO_VAL, dtype=np.uint16) + data_a = cls._data_a_view(data, writable=True) + data_b = cls._data_b_view(data, writable=True) + marker_view = cls._marker_data_view(data, writable=True) + + if ch_a is not None: + data_a[:] = ch_a.reshape((-1, cls.QUANTUM)) + + if ch_b is not None: + data_b[:] = ch_b.reshape((-1, cls.QUANTUM)) + + if marker_a is not None: + marker_view[:] |= np.left_shift(marker_a.astype(np.uint16), 14).reshape((-1, 8)) + + if marker_b is not None: + marker_view[:] |= np.left_shift(marker_b.astype(np.uint16), 15).reshape((-1, 8)) + + return cls(data=data) + + @classmethod + def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': + return cls(data=segment_data.reshape((-1, 2, 16))) + + @property + def ch_a(self): + return np.bitwise_and(self.data_a, self.CHANNEL_MASK) + + @property + def ch_b(self): + return self.data_b + + @property + def marker_a(self) -> np.ndarray: + marker_data = self._marker_data_view(self._data, writable=False) + return np.bitwise_and(marker_data, self.MARKER_A_MASK).astype(bool).reshape(-1) + + @property + def marker_b(self) -> np.ndarray: + marker_data = self._marker_data_view(self._data, writable=False) + return np.bitwise_and(marker_data, self.MARKER_B_MASK).astype(bool).reshape(-1) + + @classmethod + def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': + assert data_a.size == data_b.size + assert data_a.ndim == 1 == data_b.ndim + assert data_a.size % 16 == 0 + + data = np.empty((data_a.size // 16, 2, 16), dtype=np.uint16) + cls._data_a_view(data, writable=True)[:] = data_a.reshape((-1, 16)) + cls._data_b_view(data, writable=True)[:] = data_b.reshape((-1, 16)) + + return cls(data=data) + + def __hash__(self) -> int: + return self._hash + + def __eq__(self, other: 'TaborSegment'): + return np.array_equal(self._data, other._data) + + @property + def num_points(self) -> int: + return self._data.shape[0] * 16 + + def get_as_binary(self) -> np.ndarray: + return self.native.ravel() + + +def make_combined_wave(segments: List[TaborSegment], destination_array=None) -> np.ndarray: + """Combine multiple segments to one binary blob for bulk upload. Better implementation of + `pytabor.make_combined_wave`. + + Args: + segments: + destination_array: + + Returns: + 1 d array for upload to instrument + """ + quantum = TaborSegment.QUANTUM + + if len(segments) == 0: + return np.zeros(0, dtype=np.uint16) + + n_quanta = sum(segment.native.shape[0] for segment in segments) + len(segments) - 1 + + if destination_array is not None: + if destination_array.size != 2 * n_quanta * quantum: + raise ValueError('Destination array has an invalid size') + destination_array = destination_array.reshape((n_quanta, 2, quantum)) + else: + destination_array = np.empty((n_quanta, 2, quantum), dtype=np.uint16) + + current_quantum = 0 + for segment in segments: + if current_quantum > 0: + # fill one quantum with first data point from upcoming segment + destination_array[current_quantum, :, :] = segment.native[0, :, 0][:, None] + current_quantum += 1 + + segment_quanta = segment.native.shape[0] + destination_array[current_quantum:current_quantum + segment_quanta, ...] = segment.native + + current_quantum += segment_quanta + + return destination_array.ravel() + + +class PlottableProgram: + def __init__(self, + segments: List[TaborSegment], + sequence_tables: List[List[Tuple[int, int, int]]], + advanced_sequence_table: List[Tuple[int, int, int]]): + self._segments = segments + self._sequence_tables = [[TableEntry(*sequence_table_entry) + for sequence_table_entry in sequence_table] + for sequence_table in sequence_tables] + self._advanced_sequence_table = [TableEntry(*adv_seq_entry) + for adv_seq_entry in advanced_sequence_table] + + @classmethod + def from_read_data(cls, waveforms: List[np.ndarray], + sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], + advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': + return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], + [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], + cls._reformat_rep_seg_jump(advanced_sequence_table)) + + @classmethod + def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: + return list(TableEntry(int(rep), int(seg_no), int(jump)) + for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) + + def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: + if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): + adv_seq_tab = self._advanced_sequence_table[1:] + else: + adv_seq_tab = self._advanced_sequence_table + + # remove idle pulse at end + if with_last_idles: + return adv_seq_tab + else: + while adv_seq_tab[-1] == (1, 1, 0): + adv_seq_tab = adv_seq_tab[:-1] + return adv_seq_tab + + def _iter_segment_table_entry(self, + with_first_idle=False, + with_last_idles=False) -> Generator[TableEntry, None, None]: + for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): + for _ in range(sequence_repeat): + yield from self._sequence_tables[sequence_no - 1] + + def iter_waveforms_and_repetitions(self, + channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): + yield ch_getter(self._segments[segment_no - 1]), segment_repeat + + def iter_samples(self, channel: int, + with_first_idle=False, + with_last_idles=False) -> Generator[np.uint16, None, None]: + for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): + waveform = list(waveform) + for _ in range(repetition): + yield from waveform + + def get_as_single_waveform(self, channel: int, + max_total_length: int=10**9, + with_marker: bool=False) -> Optional[np.ndarray]: + waveforms = self.get_waveforms(channel, with_marker=with_marker) + repetitions = self.get_repetitions() + waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) + + total_length = (repetitions*waveform_lengths).sum() + if total_length > max_total_length: + return None + + result = np.empty(total_length, dtype=np.uint16) + c_idx = 0 + for wf, rep in zip(waveforms, repetitions): + mem = wf.size*rep + target = result[c_idx:c_idx+mem] + + target = target.reshape((rep, wf.size)) + target[:, :] = wf[np.newaxis, :] + c_idx += mem + return result + + def get_waveforms(self, channel: int, with_marker: bool=False) -> List[np.ndarray]: + if with_marker: + ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] + else: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1]) + for _, segment_no, _ in self._iter_segment_table_entry()] + + def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: + ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] + return [ch_getter(self._segments[segment_no - 1])] + + def get_repetitions(self) -> np.ndarray: + return np.fromiter((segment_repeat + for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) + + def __eq__(self, other): + for ch in (0, 1): + for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), + other.iter_samples(ch, True, False)): + if x != y: + return False + return True + + def to_builtin(self) -> dict: + waveforms = [[wf.data_a.tolist() for wf in self._segments], + [wf.data_b.tolist() for wf in self._segments]] + return {'waveforms': waveforms, + 'seq_tables': self._sequence_tables, + 'adv_seq_table': self._advanced_sequence_table} + + @classmethod + def from_builtin(cls, data: dict) -> 'PlottableProgram': + waveforms = data['waveforms'] + waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) + for data_a, data_b in zip(*waveforms)] + return cls(waveforms, data['seq_tables'], data['adv_seq_table']) + + +class TaborSequencing(Enum): + SINGLE = 1 + ADVANCED = 2 + + +class TaborProgram(ProgramEntry): + """ + + Implementations notes concerning indices / position + - index: zero based index in internal data structure f.i. the waveform list + - position: ? + - no/number: one based index on device + + + """ + + def __init__(self, + program: Loop, + device_properties: Mapping, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]], + amplitudes: Tuple[float, float], + offsets: Tuple[float, float], + voltage_transformations: Tuple[Optional[callable], Optional[callable]], + sample_rate: TimeType, + mode: TaborSequencing = None + ): + if len(channels) != device_properties['chan_per_part']: + raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) + if len(markers) != device_properties['chan_per_part']: + raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) + used_channels = frozenset(set(channels).union(markers) - {None}) + + if program.repetition_count > 1 or program.depth() == 0: + program.encapsulate() + + if mode is None: + if program.depth() > 1: + mode = TaborSequencing.ADVANCED + else: + mode = TaborSequencing.SINGLE + + super().__init__(loop=program, + channels=channels, + markers=markers, + amplitudes=amplitudes, + offsets=offsets, + voltage_transformations=voltage_transformations, + sample_rate=sample_rate, + waveforms=[] # no sampling happens here + ) + + self._used_channels = used_channels + self._parsed_program = None # type: Optional[ParsedProgram] + self._mode = None + self._device_properties = device_properties + + assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode" + if mode == TaborSequencing.SINGLE: + self.setup_single_sequence_mode() + else: + self.setup_advanced_sequence_mode() + + self._sampled_segments = self._calc_sampled_segments() + + @property + def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._markers + + @property + def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: + return self._channels + + @property + def program(self) -> Loop: + return self._loop + + def _channel_data(self, waveform: Waveform, time: np.ndarray, channel_idx: int): + if self._channels[channel_idx] is None: + return np.full_like(time, 8192, dtype=np.uint16) + + else: + return voltage_to_uint16( + self._voltage_transformations[channel_idx]( + waveform.get_sampled(channel=self._channels[channel_idx], + sample_times=time)), + self._amplitudes[channel_idx], + self._offsets[channel_idx], + resolution=14) + + def _marker_data(self, waveform: Waveform, time: np.ndarray, marker_idx: int): + if self._markers[marker_idx] is None: + return np.full_like(time, False, dtype=bool) + else: + marker = self._markers[marker_idx] + return waveform.get_sampled(channel=marker, sample_times=time) != 0 + + def _calc_sampled_segments(self) -> Tuple[Sequence[TaborSegment], Sequence[int]]: + """ + Returns: + (segments, segment_lengths) + """ + time_array, segment_lengths = get_sample_times(self._parsed_program.waveforms, self._sample_rate) + + if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): + raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') + + segments = [] + for i, waveform in enumerate(self._parsed_program.waveforms): + t = time_array[:segment_lengths[i]] + marker_time = t[::2] + segment_a = self._channel_data(waveform, t, 0) + segment_b = self._channel_data(waveform, t, 1) + assert (len(segment_a) == len(t)) + assert (len(segment_b) == len(t)) + marker_a = self._marker_data(waveform, marker_time, 0) + marker_b = self._marker_data(waveform, marker_time, 1) + segment = TaborSegment.from_sampled(ch_a=segment_a, + ch_b=segment_b, + marker_a=marker_a, + marker_b=marker_b) + segments.append(segment) + return segments, segment_lengths + + def setup_single_sequence_mode(self) -> None: + assert self.program.depth() == 1 + assert self.program.is_balanced() + self._parsed_program = parse_single_seq_program(program=self.program, used_channels=self._used_channels) + self._mode = TaborSequencing.SINGLE + + def setup_advanced_sequence_mode(self) -> None: + assert self.program.depth() > 1 + assert self.program.repetition_count == 1 + + self.program.flatten_and_balance(2) + + min_seq_len = self._device_properties['min_seq_len'] + max_seq_len = self._device_properties['max_seq_len'] + + prepare_program_for_advanced_sequence_mode(self.program, min_seq_len=min_seq_len, max_seq_len=max_seq_len) + + for sequence_table in self.program: + assert len(sequence_table) >= min_seq_len + assert len(sequence_table) <= max_seq_len + + self._parsed_program = parse_aseq_program(self.program, used_channels=self._used_channels) + self._mode = TaborSequencing.ADVANCED + + def get_sampled_segments(self) -> Tuple[Sequence[TaborSegment], Sequence[int]]: + return self._sampled_segments + + def update_volatile_parameters(self, parameters: Mapping[str, numbers.Number]) -> Mapping[Union[int, Tuple[int, int]], + Union[TableEntry, TableDescription]]: + """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters + in program memory. + + Args: + parameters: Name of volatile parameters and respective values to which they should be set. + + Returns: + Mapping position of change -> (new repetition value, element_num/id, jump flag) + """ + modifications = {} + + for position, volatile_repetition in self._parsed_program.volatile_parameter_positions.items(): + if isinstance(position, int): + old_rep_count, element_num, jump_flag = self._parsed_program.advanced_sequencer_table[position] + new_value = volatile_repetition.update_volatile_dependencies(parameters) + + if new_value != old_rep_count: + new_entry = TableEntry(repetition_count=new_value, element_number=element_num, jump_flag=jump_flag) + self._parsed_program.advanced_sequencer_table[position] = new_entry + modifications[position] = new_entry + else: + adv_idx, seq_pos = position + adv_pos = self._parsed_program.advanced_sequencer_table[adv_idx].element_number - 1 + sequencer_table = self._parsed_program.sequencer_tables[adv_pos] + ((old_rep_count, element_id, jump_flag), param) = sequencer_table[seq_pos] + + new_value = volatile_repetition.update_volatile_dependencies(parameters) + if new_value != old_rep_count: + new_description = TableDescription(repetition_count=new_value, + element_id=element_id, jump_flag=jump_flag) + sequencer_table[seq_pos] = (new_description, param) + modifications[position] = TableDescription(repetition_count=new_value, + element_id=element_id, jump_flag=jump_flag) + + return modifications + + def get_sequencer_tables(self): # -> List[List[TableDescription, Optional[MappedParameter]]]: + return self._parsed_program.sequencer_tables + + def get_advanced_sequencer_table(self) -> List[TableEntry]: + """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" + return self._parsed_program.advanced_sequencer_table + + @property + def waveform_mode(self) -> str: + return self._mode + + +def _check_merge_with_next(program, n, max_seq_len): + if (program[n].repetition_count == 1 and program[n+1].repetition_count == 1 and + len(program[n]) + len(program[n+1]) < max_seq_len): + program[n][len(program[n]):] = program[n + 1][:] + program[n + 1:n + 2] = [] + return True + return False + + +def _check_partial_unroll(program, n, min_seq_len): + st = program[n] + if st.volatile_repetition: + return False + + if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: + if sum(entry.repetition_count for entry in st) < min_seq_len: + st.unroll_children() + while len(st) < min_seq_len: + st.split_one_child() + return True + return False + + +def prepare_program_for_advanced_sequence_mode(program: Loop, min_seq_len: int, max_seq_len: int): + """This function tries to bring the program in a form, where the sequence tables' lengths are valid. + + Args: + program: + min_seq_len: + max_seq_len: + + Raises: + TaborException: if a sequence table that is too long cannot be shortened or a sequence table that is to short + cannot be enlarged. + """ + i = 0 + while i < len(program): + program[i].assert_tree_integrity() + if len(program[i]) > max_seq_len: + raise TaborException('The algorithm is not smart enough to make sequence tables shorter') + elif len(program[i]) < min_seq_len: + assert program[i].repetition_count > 0 + if program[i].repetition_count == 1: + # check if merging with neighbour is possible + if i > 0 and _check_merge_with_next(program, i - 1, max_seq_len=max_seq_len): + pass + elif i + 1 < len(program) and _check_merge_with_next(program, i, max_seq_len=max_seq_len): + pass + + # check if (partial) unrolling is possible + elif _check_partial_unroll(program, i, min_seq_len=min_seq_len): + i += 1 + + # check if sequence table can be extended by unrolling a neighbor + elif (i > 0 + and program[i - 1].repetition_count > 1 + and len(program[i]) + len(program[i - 1]) < max_seq_len): + program[i][:0] = program[i - 1].copy_tree_structure()[:] + program[i - 1].repetition_count -= 1 + + elif (i + 1 < len(program) + and program[i + 1].repetition_count > 1 + and len(program[i]) + len(program[i + 1]) < max_seq_len): + program[i][len(program[i]):] = program[i + 1].copy_tree_structure()[:] + program[i + 1].repetition_count -= 1 + if program[i + 1].volatile_repetition: + program[i + 1].volatile_repetition = program[i + 1].volatile_repetition - 1 + + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + elif _check_partial_unroll(program, i, min_seq_len=min_seq_len): + i += 1 + else: + raise TaborException('The algorithm is not smart enough to make this sequence table longer') + else: + i += 1 + + +ParsedProgram = NamedTuple('ParsedProgram', [('advanced_sequencer_table', Sequence[TableEntry]), + ('sequencer_tables', Sequence[Sequence[ + Tuple[TableDescription, Optional[VolatileProperty]]]]), + ('waveforms', Tuple[Waveform, ...]), + ('volatile_parameter_positions', Dict[Union[int, Tuple[int, int]], + VolatileRepetitionCount])]) + + +def parse_aseq_program(program: Loop, used_channels: FrozenSet[ChannelID]) -> ParsedProgram: + volatile_parameter_positions = {} + + advanced_sequencer_table = [] + # we use an ordered dict here to avoid O(n**2) behaviour while looking for duplicates + sequencer_tables = OrderedDict() + waveforms = OrderedDict() + for adv_position, sequencer_table_loop in enumerate(program): + current_sequencer_table = [] + for position, (waveform, repetition_definition, volatile_repetition) in enumerate( + (waveform_loop.waveform.get_subset_for_channels(used_channels), + waveform_loop.repetition_definition, waveform_loop.volatile_repetition) + for waveform_loop in cast(Sequence[Loop], sequencer_table_loop)): + + wf_index = waveforms.setdefault(waveform, len(waveforms)) + current_sequencer_table.append((TableDescription(repetition_count=int(repetition_definition), + element_id=wf_index, jump_flag=0), + volatile_repetition)) + + if volatile_repetition: + assert not isinstance(repetition_definition, int) + volatile_parameter_positions[(adv_position, position)] = repetition_definition + + # make hashable + current_sequencer_table = tuple(current_sequencer_table) + + sequence_index = sequencer_tables.setdefault(current_sequencer_table, len(sequencer_tables)) + sequence_no = sequence_index + 1 + + advanced_sequencer_table.append(TableEntry(repetition_count=sequencer_table_loop.repetition_count, + element_number=sequence_no, jump_flag=0)) + if sequencer_table_loop.volatile_repetition: + volatile_parameter_positions[adv_position] = sequencer_table_loop.repetition_definition + + # transform sequencer_tables in lists to make it indexable and mutable + sequencer_tables = list(map(list, sequencer_tables)) + + return ParsedProgram( + advanced_sequencer_table=advanced_sequencer_table, + sequencer_tables=sequencer_tables, + waveforms=tuple(waveforms.keys()), + volatile_parameter_positions=volatile_parameter_positions + ) + + +def parse_single_seq_program(program: Loop, used_channels: FrozenSet[ChannelID]) -> ParsedProgram: + assert program.depth() == 1 + + sequencer_table = [] + waveforms = OrderedDict() + volatile_parameter_positions = {} + + for position, (waveform, repetition_definition, volatile_repetition) in enumerate( + (waveform_loop.waveform.get_subset_for_channels(used_channels), + waveform_loop.repetition_definition, waveform_loop.volatile_repetition) + for waveform_loop in program): + if waveform in waveforms: + waveform_index = waveforms[waveform] + else: + waveform_index = len(waveforms) + waveforms[waveform] = waveform_index + + sequencer_table.append((TableDescription(repetition_count=int(repetition_definition), + element_id=waveform_index, + jump_flag=0), volatile_repetition)) + if volatile_repetition is not None: + volatile_parameter_positions[(0, position)] = repetition_definition + + return ParsedProgram( + advanced_sequencer_table=[TableEntry(repetition_count=program.repetition_count, element_number=1, jump_flag=0)], + sequencer_tables=[sequencer_table], + waveforms=tuple(waveforms.keys()), + volatile_parameter_positions=volatile_parameter_positions + ) From e8d0fe844deda9e1e9aef72daa95e06ea26cbf5d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 26 Mar 2020 10:48:03 +0100 Subject: [PATCH 059/107] UNIT-tests run without exceptions again after the rebase --- qupulse/hardware/awgs/old_tabor.py | 3 - qupulse/hardware/awgs/tabor.py | 641 +++-------------------------- 2 files changed, 49 insertions(+), 595 deletions(-) diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index e56192a47..f99db5b2c 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -93,12 +93,9 @@ def send_cmd(self, cmd_str, paranoia_level=None): instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: - print("Querry - " + query_str) if query_mirrors: - print("RESULT - " + tuple(instr.send_query(query_str) for instr in self.all_devices)) return tuple(instr.send_query(query_str) for instr in self.all_devices) else: - print("RESULT - " + self._instr.send_query(query_str)) return self._instr.send_query(query_str) def send_binary_data(self, pref, bin_dat, paranoia_level=None): diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 1290285cc..7e257a77d 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,6 +1,7 @@ import fractions import functools import itertools +import logging import numbers import operator import sys @@ -33,446 +34,8 @@ __all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] -class TaborSegment: - # __slots__ = ("channel_list", "marker_list") - # - # def __init__(self, channel_list: List[Optional[np.ndarray]], marker_list: List[Optional[np.ndarray]]): - # """ - # :param channel_list: - # :param marker_list: - # ??? Beides Listen mit jeweils Spannungswerten zu den einzelden Channlen/Markern - # """ - # - # # TODO: check if the channel and the marker count is valid - # - # # Check if at least on channel is not None - # ch_not_empty = False - # for channel in channel_list: - # if channel is not None: - # ch_not_empty = True - # break - # if ch_not_empty: - # raise TaborException("Empty TaborSegments are not allowed") - # - # # Check if all channels that aren"t None are the same length - # comparison_length = self.num_points - # for channel in channel_list: - # if channel is not None: - # comparison_length = len(channel) - # break - # for channel in channel_list: - # if channel is not None: - # if not (len(channel) == comparison_length): # can be ignored. this case isn"t possible - # raise TaborException("Channel entries to have to have the same length") - # - # self.channel_list = List[np.asarray(Optional[np.ndarray], dtype=np.uint16)] - # self.marker_list = List[np.asarray(Optional[np.ndarray], dtype=bool)] - # # TODO: is it possible like that. It"s only possible when python works with references - # for channel in channel_list: - # self.channel_list.add(None if channel is None else np.asarray(channel, dtype=np.uint16)) - # for marker in marker_list: - # self.marker_list.add(None if marker is None else np.asarray(marker, dtype=bool)) - # - # # Check all markers have half the length of the channels - # for idx, marker in enumerate(marker_list): - # if marker is not None and len(marker) * 2 != self.num_points: - # raise TaborException("Marker {} has to have half of the channels length".format(idx + 1)) - # # +1 to match the idn of the marker channel objects - # - # @classmethod - # def from_binary_segment(cls, segment_data: np.ndarray) -> "TaborSegment": - # pass # TODO: to implement - # - # @classmethod - # def from_binary_data(cls, date_list: List[np.ndarray]) -> "TaborSegment": - # channel_list: List[Optional[np.ndarray]] - # marker_list: List[Optional[np.ndarray]] - # - # # TODO: to implement - # - # return cls(channel_list, marker_list) - # - # def __hash__(self) -> int: - # # TODO: is this possible? - # return hash(tuple((self.channel_list, self.marker_list))) - # - # def __eq__(self, other: "TaborSegment"): - # pass # TODO: to implement - # - # def data(self, data_nr: int): - # pass # TODO: to implement - # - # @property - # def num_points(self) -> int: - # """ - # Method that returns the length of the first Channel that is not None - # :return: length of the first Channel that is not None - # """ - # for channel in self.channel_list: - # if channel is not None: - # return len(channel) - # - # def get_as_binary(self) -> np.ndarray: - # pass # TODO: to implement - - """Represents one segment of two channels on the device. Convenience class.""" - - __slots__ = ("ch_a", "ch_b", "marker_a", "marker_b") - - def __init__(self, - ch_a: Optional[np.ndarray], - ch_b: Optional[np.ndarray], - marker_a: Optional[np.ndarray], - marker_b: Optional[np.ndarray]): - if ch_a is None and ch_b is None: - raise TaborException("Empty TaborSegments are not allowed") - if ch_a is not None and ch_b is not None and len(ch_a) != len(ch_b): - raise TaborException("Channel entries to have to have the same length") - - self.ch_a = None if ch_a is None else np.asarray(ch_a, dtype=np.uint16) - self.ch_b = None if ch_b is None else np.asarray(ch_b, dtype=np.uint16) - - self.marker_a = None if marker_a is None else np.asarray(marker_a, dtype=bool) - self.marker_b = None if marker_b is None else np.asarray(marker_b, dtype=bool) - - if marker_a is not None and len(marker_a) * 2 != self.num_points: - raise TaborException("Marker A has to have half of the channels length") - if marker_b is not None and len(marker_b) * 2 != self.num_points: - raise TaborException("Marker A has to have half of the channels length") - - @classmethod - def from_binary_segment(cls, segment_data: np.ndarray) -> "TaborSegment": - data_a = segment_data.reshape((-1, 16))[1::2, :].reshape((-1,)) - data_b = segment_data.reshape((-1, 16))[0::2, :].ravel() - return cls.from_binary_data(data_a, data_b) - - @classmethod - def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> "TaborSegment": - ch_b = data_b - - channel_mask = np.uint16(2 ** 14 - 1) - ch_a = np.bitwise_and(data_a, channel_mask) - - marker_a_mask = np.uint16(2 ** 14) - marker_b_mask = np.uint16(2 ** 15) - marker_data = data_a.reshape(-1, 8)[1::2, :].reshape((-1,)) - - marker_a = np.bitwise_and(marker_data, marker_a_mask) - marker_b = np.bitwise_and(marker_data, marker_b_mask) - - return cls(ch_a=ch_a, - ch_b=ch_b, - marker_a=marker_a, - marker_b=marker_b) - - def __hash__(self) -> int: - return hash(tuple(0 if data is None else bytes(data) - for data in (self.ch_a, self.ch_b, self.marker_a, self.marker_b))) - - def __eq__(self, other: "TaborSegment"): - def compare_markers(marker_1, marker_2): - if marker_1 is None: - if marker_2 is None: - return True - else: - return not np.any(marker_2) - - elif marker_2 is None: - return not np.any(marker_1) - - else: - return np.array_equal(marker_1, marker_2) - - return (np.array_equal(self.ch_a, other.ch_a) and - np.array_equal(self.ch_b, other.ch_b) and - compare_markers(self.marker_a, other.marker_a) and - compare_markers(self.marker_b, other.marker_b)) - - @property - def data_a(self) -> np.ndarray: - """channel_data and marker data""" - if self.marker_a is None and self.marker_b is None: - return self.ch_a - - if self.ch_a is None: - raise NotImplementedError("What data should be used in a?") - - # copy channel information - data = np.array(self.ch_a) - - if self.marker_a is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 14) * self.marker_a.astype(np.uint16) - - if self.marker_b is not None: - data.reshape(-1, 8)[1::2, :].flat |= (1 << 15) * self.marker_b.astype(np.uint16) - - return data - - @property - def data_b(self) -> np.ndarray: - """channel_data and marker data""" - return self.ch_b - - @property - def num_points(self) -> int: - return len(self.ch_b) if self.ch_a is None else len(self.ch_a) - - def get_as_binary(self) -> np.ndarray: - assert not (self.ch_a is None or self.ch_b is None) - return make_combined_wave([self]) - - -class TaborSequencing(Enum): - SINGLE = 1 - ADVANCED = 2 - - -class TaborProgram: - def __init__(self, - program: Loop, - device_properties, - channels, - markers): - # TODO: entspricht channels und markers den Vorgaben der device_properties + kommentare entfernen bzw. Code ersetzen - - # channel_set = frozenset(channel for channel in channels if channel is not None) | frozenset(marker - # for marker in - # markers if - # marker is not None) - - self._program = program - - self.__waveform_mode = None - # self._channels = tuple(channels) - # self._markers = tuple(markers) - # self.__used_channels = channel_set - self.__device_properties = device_properties - - self._waveforms = List["MultiChannelWaveform"] - self._sequencer_tables = [] - self._advanced_sequencer_table = [] - - if self.program.repetition_count > 1: - self.program.encapsulate() - - if self.program.depth() > 1: - self.setup_advanced_sequence_mode() - self.__waveform_mode = TaborSequencing.ADVANCED - else: - if self.program.depth() == 0: - self.program.encapsulate() - self.setup_single_sequence_mode() - self.__waveform_mode = TaborSequencing.SINGLE - - @property - def markers(self): - return self._markers # TODO (LuL): Uncomment the instantiation of self._channels - # TODO: typing - - @property - def channels(self): - return self._channels # TODO (LuL): Uncomment the instantiation of self._channels - # TODO: typing - - def sampled_segments(self, - sample_rate: fractions.Fraction, - voltage_amplitude: Tuple[float, float], - voltage_offset: Tuple[float, float], - voltage_transformation: Tuple[Callable, Callable]) -> Tuple[Sequence[TaborSegment], - Sequence[int]]: - sample_rate = fractions.Fraction(sample_rate, 10 ** 9) - - time_array, segment_lengths = get_sample_times(self._waveforms, sample_rate) - - if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): - raise TaborException("At least one waveform has a length that is smaller 192 or not a multiple of 16") - - def voltage_to_data(waveform, time, channel): - if self._channels[channel]: - return voltage_to_uint16( - voltage_transformation[channel]( - waveform.get_sampled(channel=self._channels[channel], - sample_times=time)), - voltage_amplitude[channel], - voltage_offset[channel], - resolution=14) - else: - return np.full_like(time, 8192, dtype=np.uint16) - - def get_marker_data(waveform: MultiChannelWaveform, time, marker): - if self._markers[marker]: - markerID = self._markers[marker] - return waveform.get_sampled(channel=markerID, sample_times=time) != 0 - else: - return np.full_like(time, False, dtype=bool) - - segments = np.empty_like(self._waveforms, dtype=TaborSegment) - for i, waveform in enumerate(self._waveforms): - t = time_array[:segment_lengths[i]] - marker_time = t[::2] - segment_a = voltage_to_data(waveform, t, 0) - segment_b = voltage_to_data(waveform, t, 1) - assert (len(segment_a) == len(t)) - assert (len(segment_b) == len(t)) - marker_a = get_marker_data(waveform, marker_time, 0) - marker_b = get_marker_data(waveform, marker_time, 1) - segments[i] = TaborSegment(ch_a=segment_a, - ch_b=segment_b, - marker_a=marker_a, - marker_b=marker_b) - return segments, segment_lengths - - def setup_single_sequence_mode(self) -> None: - # TODO: comment missing - """ - - """ - assert self.program.depth() == 1 - - sequencer_table = [] - waveforms = OrderedDict() - - for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in self.program): - if waveform in waveforms: - waveform_index = waveforms[waveform] - else: - waveform_index = len(waveforms) - waveforms[waveform] = waveform_index - sequencer_table.append((repetition_count, waveform_index, 0)) - - self._waveforms = tuple(waveforms.keys()) - self._sequencer_tables = [sequencer_table] - self._advanced_sequencer_table = [(self.program.repetition_count, 1, 0)] - - def setup_advanced_sequence_mode(self) -> None: - # TODO: comment missing - """ - - """ - assert self.program.depth() > 1 - assert self.program.repetition_count == 1 - - self.program.flatten_and_balance(2) - - min_seq_len = self.__device_properties["min_seq_len"] - max_seq_len = self.__device_properties["max_seq_len"] - - def check_merge_with_next(program, n): - if (program[n].repetition_count == 1 and program[n + 1].repetition_count == 1 and - len(program[n]) + len(program[n + 1]) < max_seq_len): - program[n][len(program[n]):] = program[n + 1][:] - program[n + 1:n + 2] = [] - return True - return False - - def check_partial_unroll(program, n): - st = program[n] - if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: - if sum(entry.repetition_count for entry in st) < min_seq_len: - st.unroll_children() - while len(st) < min_seq_len: - st.split_one_child() - return True - return False - - i = 0 - while i < len(self.program): - self.program[i].assert_tree_integrity() - if len(self.program[i]) > max_seq_len: - raise TaborException("The algorithm is not smart enough to make sequence tables shorter") - elif len(self.program[i]) < min_seq_len: - assert self.program[i].repetition_count > 0 - if self.program[i].repetition_count == 1: - # check if merging with neighbour is possible - if i > 0 and check_merge_with_next(self.program, i - 1): - pass - elif i + 1 < len(self.program) and check_merge_with_next(self.program, i): - pass - - # check if (partial) unrolling is possible - elif check_partial_unroll(self.program, i): - i += 1 - - # check if sequence table can be extended by unrolling a neighbor - elif (i > 0 - and self.program[i - 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i - 1]) < max_seq_len): - self.program[i][:0] = self.program[i - 1].copy_tree_structure()[:] - self.program[i - 1].repetition_count -= 1 - - elif (i + 1 < len(self.program) - and self.program[i + 1].repetition_count > 1 - and len(self.program[i]) + len(self.program[i + 1]) < max_seq_len): - self.program[i][len(self.program[i]):] = self.program[i + 1].copy_tree_structure()[:] - self.program[i + 1].repetition_count -= 1 - - else: - raise TaborException("The algorithm is not smart enough to make this sequence table longer") - elif check_partial_unroll(self.program, i): - i += 1 - else: - raise TaborException("The algorithm is not smart enough to make this sequence table longer") - else: - i += 1 - - for sequence_table in self.program: - assert len(sequence_table) >= self.__device_properties["min_seq_len"] - assert len(sequence_table) <= self.__device_properties["max_seq_len"] - - advanced_sequencer_table = [] - sequencer_tables = [] - waveforms = OrderedDict() - for sequencer_table_loop in self.program: - current_sequencer_table = [] - for waveform, repetition_count in ((waveform_loop.waveform.get_subset_for_channels(self.__used_channels), - waveform_loop.repetition_count) - for waveform_loop in sequencer_table_loop): - if waveform in waveforms: - wf_index = waveforms[waveform] - else: - wf_index = len(waveforms) - waveforms[waveform] = wf_index - current_sequencer_table.append((repetition_count, wf_index, 0)) - - if current_sequencer_table in sequencer_tables: - sequence_no = sequencer_tables.index(current_sequencer_table) + 1 - else: - sequence_no = len(sequencer_tables) + 1 - sequencer_tables.append(current_sequencer_table) - - advanced_sequencer_table.append((sequencer_table_loop.repetition_count, sequence_no, 0)) - - self._advanced_sequencer_table = advanced_sequencer_table - self._sequencer_tables = sequencer_tables - self._waveforms = tuple(waveforms.keys()) - - @property - def program(self) -> Loop: - return self._program - - def get_sequencer_tables(self) -> List[Tuple[int, int, int]]: - # TODO: comment missing - """ - - """ - return self._sequencer_tables - - def get_advanced_sequencer_table(self) -> List[Tuple[int, int, int]]: - # TODO: comment missing - """ - - """ - return self._advanced_sequencer_table - - @property - def waveform_mode(self) -> str: - # TODO: comment missing - """ - - """ - return self.__waveform_mode +TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), + ('program', TaborProgram)]) # TODO: How does this work? @@ -501,142 +64,12 @@ def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> C @functools.wraps(function_object) def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: - channel_tuple._select() # TODO (LuL): make the select-function private, because it only should be called from here + channel_tuple._select() return function_object(channel_tuple, *args, **kwargs) return selector -class PlottableProgram: - TableEntry = NamedTuple("TableEntry", [("repetition_count", int), - ("element_number", int), - ("jump_flag", int)]) - - def __init__(self, - segments: List[TaborSegment], - sequence_tables: List[List[Tuple[int, int, int]]], - advanced_sequence_table: List[Tuple[int, int, int]]): - self._segments = segments - self._sequence_tables = [[self.TableEntry(*sequence_table_entry) - for sequence_table_entry in sequence_table] - for sequence_table in sequence_tables] - self._advanced_sequence_table = [self.TableEntry(*adv_seq_entry) - for adv_seq_entry in advanced_sequence_table] - - @classmethod - def from_read_data(cls, waveforms: List[np.ndarray], - sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], - advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': - return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], - [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], - cls._reformat_rep_seg_jump(advanced_sequence_table)) - - @classmethod - def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: - return list(cls.TableEntry(int(rep), int(seg_no), int(jump)) - for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) - - def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: - if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): - adv_seq_tab = self._advanced_sequence_table[1:] - else: - adv_seq_tab = self._advanced_sequence_table - - # remove idle pulse at end - if with_last_idles: - return adv_seq_tab - else: - while adv_seq_tab[-1] == (1, 1, 0): - adv_seq_tab = adv_seq_tab[:-1] - return adv_seq_tab - - def _iter_segment_table_entry(self, - with_first_idle=False, - with_last_idles=False) -> Generator[TableEntry, None, None]: - for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): - for _ in range(sequence_repeat): - yield from self._sequence_tables[sequence_no - 1] - - def iter_waveforms_and_repetitions(self, - channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): - yield ch_getter(self._segments[segment_no - 1]), segment_repeat - - def iter_samples(self, channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[np.uint16, None, None]: - for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): - waveform = list(waveform) - for _ in range(repetition): - yield from waveform - - def get_as_single_waveform(self, channel: int, - max_total_length: int = 10 ** 9, - with_marker: bool = False) -> Optional[np.ndarray]: - waveforms = self.get_waveforms(channel, with_marker=with_marker) - repetitions = self.get_repetitions() - waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) - - total_length = (repetitions * waveform_lengths).sum() - if total_length > max_total_length: - return None - - result = np.empty(total_length, dtype=np.uint16) - c_idx = 0 - for wf, rep in zip(waveforms, repetitions): - mem = wf.size * rep - target = result[c_idx:c_idx + mem] - - target = target.reshape((rep, wf.size)) - target[:, :] = wf[np.newaxis, :] - c_idx += mem - return result - - def get_waveforms(self, channel: int, with_marker: bool = False) -> List[np.ndarray]: - if with_marker: - ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] - else: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1]) - for _, segment_no, _ in self._iter_segment_table_entry()] - - def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1])] - - def get_repetitions(self) -> np.ndarray: - return np.fromiter((segment_repeat - for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) - - def __eq__(self, other): - for ch in (0, 1): - for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), - other.iter_samples(ch, True, False)): - if x != y: - return False - return True - - def to_builtin(self) -> dict: - waveforms = [[wf.data_a.tolist() for wf in self._segments], - [wf.data_b.tolist() for wf in self._segments]] - return {'waveforms': waveforms, - 'seq_tables': self._sequence_tables, - 'adv_seq_table': self._advanced_sequence_table} - - @classmethod - def from_builtin(cls, data: dict) -> 'PlottableProgram': - waveforms = data['waveforms'] - waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) - for data_a, data_b in zip(*waveforms)] - return cls(waveforms, data['seq_tables'], data['adv_seq_table']) - - -TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), - ("program", TaborProgram)]) - ######################################################################################################################## # Device ######################################################################################################################## @@ -701,6 +134,12 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._initialize() # TODO: change for synchronisation-feature + def is_coupled(self) -> bool: + if self._coupled is None: + return self._send_query(':INST:COUP:STAT?') == 'ON' + else: + return self._coupled + def cleanup(self) -> None: # TODO: split cleanup in to two different methods for channel_tuple in self.channel_tuples: @@ -889,7 +328,7 @@ def reset(self) -> None: self.send_cmd(':RES') self._coupled = None - self.initialize() + self._initialize() for channel_tuple in self.channel_tuples: channel_tuple[TaborProgramManagement].clear() @@ -972,6 +411,14 @@ def __init__(self, marker_channel: "TaborMarkerChannel"): def status(self) -> bool: pass # TODO: to implement + def enable(self): + command_string = ":INST:SEL {ch_id}; :OUTP ON".format(ch_id=self.parent.idn) + self.parent.device.send_cmd(command_string) + + def disable(self): + command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self.parent.idn) + self.parent.device.send_cmd(command_string) + @status.setter def status(self, channel_status: bool) -> None: command_string = ":INST:SEL {ch_id}; :OUTP {output}".format(ch_id=self.parent.idn, @@ -1109,6 +556,8 @@ def programs(self) -> Set[str]: class TaborChannelTuple(AWGChannelTuple): + CONFIG_MODE_PARANOIA_LEVEL = None + def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], marker_channels: Iterable["TaborMarkerChannel"]): super().__init__(idn) @@ -1129,13 +578,13 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann # adding Features self.add_feature(TaborProgramManagement(self)) - self._idle_segment = TaborSegment(voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), None, None) - + self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + None, None) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] @@ -1155,6 +604,14 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self._channel_tuple_adapter: ChannelTupleAdapter + @property + def internal_paranoia_level(self) -> Optional[int]: + return self._internal_paranoia_level + + @property + def logger(self): + return logging.getLogger("qupulse.tabor") + @property def channel_tuple_adapter(self) -> ChannelTupleAdapter: if self._channel_tuple_adapter is None: @@ -1392,7 +849,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: paranoia_level=self.internal_paranoia_level) self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + self.device._send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) @@ -1528,7 +985,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add # translate waveform number to actual segment sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for (rep_count, wf_index, jump_flag) in sequencer_table] + for ((rep_count, wf_index, jump_flag), _) in sequencer_table] for sequencer_table in program.get_sequencer_tables()] # insert idle sequence @@ -1553,17 +1010,17 @@ def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add advanced_sequencer_table.append((1, 1, 0)) # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd("SEQ:DEL:ALL") + self.device.send_cmd("SEQ:DEL:ALL", paranoia_level=self.internal_paranoia_level) self._sequencer_tables = [] - self.device.send_cmd("ASEQ:DEL") + self.device.send_cmd("ASEQ:DEL", paranoia_level=self.internal_paranoia_level) self._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd("SEQ:SEL {}".format(i + 1)) + self.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self.internal_paranoia_level) self.device._download_sequencer_table(sequencer_table) self._sequencer_tables = sequencer_tables - self.device.send_cmd("SEQ:SEL 1") + self.device.send_cmd("SEQ:SEL 1", paranoia_level=self.internal_paranoia_level) self.device._download_adv_seq_table(advanced_sequencer_table) self._advanced_sequence_table = advanced_sequencer_table @@ -1614,15 +1071,15 @@ def _enter_config_mode(self) -> None: if self.device.is_coupled(): out_cmd = ':OUTP:ALL OFF' else: - cmd = "" + out_cmd = "" for channel in self.channels: - cmd = cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) - self.device.send_cmd(cmd[:-1]) - - for marker_ch in self.marker_channels: - marker_ch[TaborMarkerChannelActivatable].status = False + out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=channel.idn) - self.device.send_cmd(":SOUR:FUNC:MODE FIX") + marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + #TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature + #for marker_ch in self.marker_channels: + # marker_ch[TaborMarkerChannelActivatable].status = False wf_mode_cmd = ':SOUR:FUNC:MODE FIX' From 7831e1f5c6c136bb078269d3c0f72b8d9515a57a Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 1 Apr 2020 11:51:49 +0200 Subject: [PATCH 060/107] Synchronisation feature and DeviceControl feature were added --- qupulse/_program/old_tabor.py | 727 ------------------ qupulse/hardware/awgs/__init__.py | 2 +- .../hardware/awgs/channel_tuple_wrapper.py | 9 +- qupulse/hardware/awgs/features.py | 40 + qupulse/hardware/awgs/old_tabor.py | 2 +- qupulse/hardware/awgs/tabor.py | 194 +++-- tests/_program/tabor_tests.py | 2 +- .../old_tabor_simulator_based_tests.py | 2 +- tests/hardware/tabor_dummy_based_tests.py | 10 +- tests/hardware/tabor_simulator_based_tests.py | 6 +- 10 files changed, 147 insertions(+), 847 deletions(-) delete mode 100644 qupulse/_program/old_tabor.py diff --git a/qupulse/_program/old_tabor.py b/qupulse/_program/old_tabor.py deleted file mode 100644 index 5579661ca..000000000 --- a/qupulse/_program/old_tabor.py +++ /dev/null @@ -1,727 +0,0 @@ -import sys -from typing import NamedTuple, Optional, List, Generator, Tuple, Sequence, Mapping, Union, Dict, FrozenSet, cast -from enum import Enum -import operator -from collections import OrderedDict -import itertools -import numbers - -import numpy as np - -from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.awgs.old_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 - -assert(sys.byteorder == 'little') - - -TableEntry = NamedTuple('TableEntry', [('repetition_count', int), - ('element_number', int), - ('jump_flag', int)]) -TableEntry.__doc__ = ("Entry in sequencing or advanced sequencer table as uploaded to the AWG with" - "download_adv_seq_table or download_sequencer_table") - -TableDescription = NamedTuple('TableDescription', [('repetition_count', int), - ('element_id', int), - ('jump_flag', int)]) -TableDescription.__doc__ = ("Entry in sequencing or advanced sequencer table but with an element 'reference' instead of" - "the hardware bound 'number'") - -PositionalEntry = NamedTuple('PositionalEntry', [('position', int), - ('element_number', int), - ('repetition_count', int), - ('jump_flag', int)]) -PositionalEntry.__doc__ = ("Entry in sequencing or advanced sequencer table as uploaded with :SEQ:DEF" - "or :ASEQ:DEF") - - -class TaborException(Exception): - pass - - -class TaborSegment: - """Represents one segment of two channels on the device. Convenience class. The data is stored in native format""" - - QUANTUM = 16 - ZERO_VAL = np.uint16(8192) - CHANNEL_MASK = np.uint16(2**14 - 1) - MARKER_A_MASK = np.uint16(2**14) - MARKER_B_MASK = np.uint16(2**15) - - __slots__ = ('_data', '_hash') - - @staticmethod - def _data_a_view(data: np.ndarray, writable: bool) -> np.ndarray: - view = data[:, 1, :] - assert not writable or view.base is data - return view - - @staticmethod - def _data_b_view(data: np.ndarray, writable: bool) -> np.ndarray: - view = data[:, 0, :] - assert not writable or view.base is data - return view - - @staticmethod - def _marker_data_view(data: np.ndarray, writable: bool) -> np.ndarray: - view = data.reshape((-1, 2, 2, 8))[:, 1, 1, :] - assert not writable or view.base is data - return view - - def __init__(self, *, - data: np.ndarray): - assert data.ndim == 3 - n_quanta, n_channels, quantum_size = data.shape - assert n_channels == 2 - assert quantum_size == self.QUANTUM - assert data.dtype is np.dtype('uint16') - self._data = data - self._data.flags.writeable = False - - # shape is not included because it only depends on the size i.e. (n_quantum, 2, 16) - self._hash = hash(self._data.tobytes()) - - @property - def native(self) -> np.ndarray: - """You must not change native (data or shape) - - Returns: - An array with shape (n_quanta, 2, 16) - """ - return self._data - - @property - def data_a(self) -> np.ndarray: - return self._data_a_view(self._data, writable=False).reshape(-1) - - @property - def data_b(self) -> np.ndarray: - return self._data_b_view(self._data, writable=False).reshape(-1) - - @classmethod - def from_sampled(cls, - ch_a: Optional[np.ndarray], - ch_b: Optional[np.ndarray], - marker_a: Optional[np.ndarray], - marker_b: Optional[np.ndarray]) -> 'TaborSegment': - num_points = set() - if ch_a is not None: - assert ch_a.ndim == 1 - assert ch_a.dtype is np.dtype('uint16') - num_points.add(ch_a.size) - if ch_b is not None: - assert ch_b.ndim == 1 - assert ch_b.dtype is np.dtype('uint16') - num_points.add(ch_b.size) - if marker_a is not None: - assert marker_a.ndim == 1 - marker_a = marker_a.astype(dtype=bool) - num_points.add(marker_a.size * 2) - if marker_b is not None: - assert marker_b.ndim == 1 - marker_b = marker_b.astype(dtype=bool) - num_points.add(marker_b.size * 2) - - if len(num_points) == 0: - raise TaborException('Empty TaborSegments are not allowed') - elif len(num_points) > 1: - raise TaborException('Channel entries have to have the same length') - num_points, = num_points - - assert num_points % cls.QUANTUM == 0 - - data = np.full((num_points // cls.QUANTUM, 2, cls.QUANTUM), cls.ZERO_VAL, dtype=np.uint16) - data_a = cls._data_a_view(data, writable=True) - data_b = cls._data_b_view(data, writable=True) - marker_view = cls._marker_data_view(data, writable=True) - - if ch_a is not None: - data_a[:] = ch_a.reshape((-1, cls.QUANTUM)) - - if ch_b is not None: - data_b[:] = ch_b.reshape((-1, cls.QUANTUM)) - - if marker_a is not None: - marker_view[:] |= np.left_shift(marker_a.astype(np.uint16), 14).reshape((-1, 8)) - - if marker_b is not None: - marker_view[:] |= np.left_shift(marker_b.astype(np.uint16), 15).reshape((-1, 8)) - - return cls(data=data) - - @classmethod - def from_binary_segment(cls, segment_data: np.ndarray) -> 'TaborSegment': - return cls(data=segment_data.reshape((-1, 2, 16))) - - @property - def ch_a(self): - return np.bitwise_and(self.data_a, self.CHANNEL_MASK) - - @property - def ch_b(self): - return self.data_b - - @property - def marker_a(self) -> np.ndarray: - marker_data = self._marker_data_view(self._data, writable=False) - return np.bitwise_and(marker_data, self.MARKER_A_MASK).astype(bool).reshape(-1) - - @property - def marker_b(self) -> np.ndarray: - marker_data = self._marker_data_view(self._data, writable=False) - return np.bitwise_and(marker_data, self.MARKER_B_MASK).astype(bool).reshape(-1) - - @classmethod - def from_binary_data(cls, data_a: np.ndarray, data_b: np.ndarray) -> 'TaborSegment': - assert data_a.size == data_b.size - assert data_a.ndim == 1 == data_b.ndim - assert data_a.size % 16 == 0 - - data = np.empty((data_a.size // 16, 2, 16), dtype=np.uint16) - cls._data_a_view(data, writable=True)[:] = data_a.reshape((-1, 16)) - cls._data_b_view(data, writable=True)[:] = data_b.reshape((-1, 16)) - - return cls(data=data) - - def __hash__(self) -> int: - return self._hash - - def __eq__(self, other: 'TaborSegment'): - return np.array_equal(self._data, other._data) - - @property - def num_points(self) -> int: - return self._data.shape[0] * 16 - - def get_as_binary(self) -> np.ndarray: - return self.native.ravel() - - -def make_combined_wave(segments: List[TaborSegment], destination_array=None) -> np.ndarray: - """Combine multiple segments to one binary blob for bulk upload. Better implementation of - `pytabor.make_combined_wave`. - - Args: - segments: - destination_array: - - Returns: - 1 d array for upload to instrument - """ - quantum = TaborSegment.QUANTUM - - if len(segments) == 0: - return np.zeros(0, dtype=np.uint16) - - n_quanta = sum(segment.native.shape[0] for segment in segments) + len(segments) - 1 - - if destination_array is not None: - if destination_array.size != 2 * n_quanta * quantum: - raise ValueError('Destination array has an invalid size') - destination_array = destination_array.reshape((n_quanta, 2, quantum)) - else: - destination_array = np.empty((n_quanta, 2, quantum), dtype=np.uint16) - - current_quantum = 0 - for segment in segments: - if current_quantum > 0: - # fill one quantum with first data point from upcoming segment - destination_array[current_quantum, :, :] = segment.native[0, :, 0][:, None] - current_quantum += 1 - - segment_quanta = segment.native.shape[0] - destination_array[current_quantum:current_quantum + segment_quanta, ...] = segment.native - - current_quantum += segment_quanta - - return destination_array.ravel() - - -class PlottableProgram: - def __init__(self, - segments: List[TaborSegment], - sequence_tables: List[List[Tuple[int, int, int]]], - advanced_sequence_table: List[Tuple[int, int, int]]): - self._segments = segments - self._sequence_tables = [[TableEntry(*sequence_table_entry) - for sequence_table_entry in sequence_table] - for sequence_table in sequence_tables] - self._advanced_sequence_table = [TableEntry(*adv_seq_entry) - for adv_seq_entry in advanced_sequence_table] - - @classmethod - def from_read_data(cls, waveforms: List[np.ndarray], - sequence_tables: List[Tuple[np.ndarray, np.ndarray, np.ndarray]], - advanced_sequence_table: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> 'PlottableProgram': - return cls([TaborSegment.from_binary_segment(wf) for wf in waveforms], - [cls._reformat_rep_seg_jump(seq_table) for seq_table in sequence_tables], - cls._reformat_rep_seg_jump(advanced_sequence_table)) - - @classmethod - def _reformat_rep_seg_jump(cls, rep_seg_jump_tuple: Tuple[np.ndarray, np.ndarray, np.ndarray]) -> List[TableEntry]: - return list(TableEntry(int(rep), int(seg_no), int(jump)) - for rep, seg_no, jump in zip(*rep_seg_jump_tuple)) - - def _get_advanced_sequence_table(self, with_first_idle=False, with_last_idles=False) -> List[TableEntry]: - if not with_first_idle and self._advanced_sequence_table[0] == (1, 1, 1): - adv_seq_tab = self._advanced_sequence_table[1:] - else: - adv_seq_tab = self._advanced_sequence_table - - # remove idle pulse at end - if with_last_idles: - return adv_seq_tab - else: - while adv_seq_tab[-1] == (1, 1, 0): - adv_seq_tab = adv_seq_tab[:-1] - return adv_seq_tab - - def _iter_segment_table_entry(self, - with_first_idle=False, - with_last_idles=False) -> Generator[TableEntry, None, None]: - for sequence_repeat, sequence_no, _ in self._get_advanced_sequence_table(with_first_idle, with_last_idles): - for _ in range(sequence_repeat): - yield from self._sequence_tables[sequence_no - 1] - - def iter_waveforms_and_repetitions(self, - channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[Tuple[np.ndarray, int], None, None]: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - for segment_repeat, segment_no, _ in self._iter_segment_table_entry(with_first_idle, with_last_idles): - yield ch_getter(self._segments[segment_no - 1]), segment_repeat - - def iter_samples(self, channel: int, - with_first_idle=False, - with_last_idles=False) -> Generator[np.uint16, None, None]: - for waveform, repetition in self.iter_waveforms_and_repetitions(channel, with_first_idle, with_last_idles): - waveform = list(waveform) - for _ in range(repetition): - yield from waveform - - def get_as_single_waveform(self, channel: int, - max_total_length: int=10**9, - with_marker: bool=False) -> Optional[np.ndarray]: - waveforms = self.get_waveforms(channel, with_marker=with_marker) - repetitions = self.get_repetitions() - waveform_lengths = np.fromiter((wf.size for wf in waveforms), count=len(waveforms), dtype=np.uint64) - - total_length = (repetitions*waveform_lengths).sum() - if total_length > max_total_length: - return None - - result = np.empty(total_length, dtype=np.uint16) - c_idx = 0 - for wf, rep in zip(waveforms, repetitions): - mem = wf.size*rep - target = result[c_idx:c_idx+mem] - - target = target.reshape((rep, wf.size)) - target[:, :] = wf[np.newaxis, :] - c_idx += mem - return result - - def get_waveforms(self, channel: int, with_marker: bool=False) -> List[np.ndarray]: - if with_marker: - ch_getter = (operator.attrgetter('data_a'), operator.attrgetter('data_b'))[channel] - else: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1]) - for _, segment_no, _ in self._iter_segment_table_entry()] - - def get_segment_waveform(self, channel: int, segment_no: int) -> np.ndarray: - ch_getter = (operator.attrgetter('ch_a'), operator.attrgetter('ch_b'))[channel] - return [ch_getter(self._segments[segment_no - 1])] - - def get_repetitions(self) -> np.ndarray: - return np.fromiter((segment_repeat - for segment_repeat, *_ in self._iter_segment_table_entry()), dtype=np.uint32) - - def __eq__(self, other): - for ch in (0, 1): - for x, y in itertools.zip_longest(self.iter_samples(ch, True, False), - other.iter_samples(ch, True, False)): - if x != y: - return False - return True - - def to_builtin(self) -> dict: - waveforms = [[wf.data_a.tolist() for wf in self._segments], - [wf.data_b.tolist() for wf in self._segments]] - return {'waveforms': waveforms, - 'seq_tables': self._sequence_tables, - 'adv_seq_table': self._advanced_sequence_table} - - @classmethod - def from_builtin(cls, data: dict) -> 'PlottableProgram': - waveforms = data['waveforms'] - waveforms = [TaborSegment.from_binary_data(np.array(data_a, dtype=np.uint16), np.array(data_b, dtype=np.uint16)) - for data_a, data_b in zip(*waveforms)] - return cls(waveforms, data['seq_tables'], data['adv_seq_table']) - - -class TaborSequencing(Enum): - SINGLE = 1 - ADVANCED = 2 - - -class TaborProgram(ProgramEntry): - """ - - Implementations notes concerning indices / position - - index: zero based index in internal data structure f.i. the waveform list - - position: ? - - no/number: one based index on device - - - """ - - def __init__(self, - program: Loop, - device_properties: Mapping, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]], - amplitudes: Tuple[float, float], - offsets: Tuple[float, float], - voltage_transformations: Tuple[Optional[callable], Optional[callable]], - sample_rate: TimeType, - mode: TaborSequencing = None - ): - if len(channels) != device_properties['chan_per_part']: - raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) - if len(markers) != device_properties['chan_per_part']: - raise TaborException('TaborProgram only supports {} markers'.format(device_properties['chan_per_part'])) - used_channels = frozenset(set(channels).union(markers) - {None}) - - if program.repetition_count > 1 or program.depth() == 0: - program.encapsulate() - - if mode is None: - if program.depth() > 1: - mode = TaborSequencing.ADVANCED - else: - mode = TaborSequencing.SINGLE - - super().__init__(loop=program, - channels=channels, - markers=markers, - amplitudes=amplitudes, - offsets=offsets, - voltage_transformations=voltage_transformations, - sample_rate=sample_rate, - waveforms=[] # no sampling happens here - ) - - self._used_channels = used_channels - self._parsed_program = None # type: Optional[ParsedProgram] - self._mode = None - self._device_properties = device_properties - - assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode" - if mode == TaborSequencing.SINGLE: - self.setup_single_sequence_mode() - else: - self.setup_advanced_sequence_mode() - - self._sampled_segments = self._calc_sampled_segments() - - @property - def markers(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._markers - - @property - def channels(self) -> Tuple[Optional[ChannelID], Optional[ChannelID]]: - return self._channels - - @property - def program(self) -> Loop: - return self._loop - - def _channel_data(self, waveform: Waveform, time: np.ndarray, channel_idx: int): - if self._channels[channel_idx] is None: - return np.full_like(time, 8192, dtype=np.uint16) - - else: - return voltage_to_uint16( - self._voltage_transformations[channel_idx]( - waveform.get_sampled(channel=self._channels[channel_idx], - sample_times=time)), - self._amplitudes[channel_idx], - self._offsets[channel_idx], - resolution=14) - - def _marker_data(self, waveform: Waveform, time: np.ndarray, marker_idx: int): - if self._markers[marker_idx] is None: - return np.full_like(time, False, dtype=bool) - else: - marker = self._markers[marker_idx] - return waveform.get_sampled(channel=marker, sample_times=time) != 0 - - def _calc_sampled_segments(self) -> Tuple[Sequence[TaborSegment], Sequence[int]]: - """ - Returns: - (segments, segment_lengths) - """ - time_array, segment_lengths = get_sample_times(self._parsed_program.waveforms, self._sample_rate) - - if np.any(segment_lengths % 16 > 0) or np.any(segment_lengths < 192): - raise TaborException('At least one waveform has a length that is smaller 192 or not a multiple of 16') - - segments = [] - for i, waveform in enumerate(self._parsed_program.waveforms): - t = time_array[:segment_lengths[i]] - marker_time = t[::2] - segment_a = self._channel_data(waveform, t, 0) - segment_b = self._channel_data(waveform, t, 1) - assert (len(segment_a) == len(t)) - assert (len(segment_b) == len(t)) - marker_a = self._marker_data(waveform, marker_time, 0) - marker_b = self._marker_data(waveform, marker_time, 1) - segment = TaborSegment.from_sampled(ch_a=segment_a, - ch_b=segment_b, - marker_a=marker_a, - marker_b=marker_b) - segments.append(segment) - return segments, segment_lengths - - def setup_single_sequence_mode(self) -> None: - assert self.program.depth() == 1 - assert self.program.is_balanced() - self._parsed_program = parse_single_seq_program(program=self.program, used_channels=self._used_channels) - self._mode = TaborSequencing.SINGLE - - def setup_advanced_sequence_mode(self) -> None: - assert self.program.depth() > 1 - assert self.program.repetition_count == 1 - - self.program.flatten_and_balance(2) - - min_seq_len = self._device_properties['min_seq_len'] - max_seq_len = self._device_properties['max_seq_len'] - - prepare_program_for_advanced_sequence_mode(self.program, min_seq_len=min_seq_len, max_seq_len=max_seq_len) - - for sequence_table in self.program: - assert len(sequence_table) >= min_seq_len - assert len(sequence_table) <= max_seq_len - - self._parsed_program = parse_aseq_program(self.program, used_channels=self._used_channels) - self._mode = TaborSequencing.ADVANCED - - def get_sampled_segments(self) -> Tuple[Sequence[TaborSegment], Sequence[int]]: - return self._sampled_segments - - def update_volatile_parameters(self, parameters: Mapping[str, numbers.Number]) -> Mapping[Union[int, Tuple[int, int]], - Union[TableEntry, TableDescription]]: - """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters - in program memory. - - Args: - parameters: Name of volatile parameters and respective values to which they should be set. - - Returns: - Mapping position of change -> (new repetition value, element_num/id, jump flag) - """ - modifications = {} - - for position, volatile_repetition in self._parsed_program.volatile_parameter_positions.items(): - if isinstance(position, int): - old_rep_count, element_num, jump_flag = self._parsed_program.advanced_sequencer_table[position] - new_value = volatile_repetition.update_volatile_dependencies(parameters) - - if new_value != old_rep_count: - new_entry = TableEntry(repetition_count=new_value, element_number=element_num, jump_flag=jump_flag) - self._parsed_program.advanced_sequencer_table[position] = new_entry - modifications[position] = new_entry - else: - adv_idx, seq_pos = position - adv_pos = self._parsed_program.advanced_sequencer_table[adv_idx].element_number - 1 - sequencer_table = self._parsed_program.sequencer_tables[adv_pos] - ((old_rep_count, element_id, jump_flag), param) = sequencer_table[seq_pos] - - new_value = volatile_repetition.update_volatile_dependencies(parameters) - if new_value != old_rep_count: - new_description = TableDescription(repetition_count=new_value, - element_id=element_id, jump_flag=jump_flag) - sequencer_table[seq_pos] = (new_description, param) - modifications[position] = TableDescription(repetition_count=new_value, - element_id=element_id, jump_flag=jump_flag) - - return modifications - - def get_sequencer_tables(self): # -> List[List[TableDescription, Optional[MappedParameter]]]: - return self._parsed_program.sequencer_tables - - def get_advanced_sequencer_table(self) -> List[TableEntry]: - """Advanced sequencer table that can be used via the download_adv_seq_table pytabor command""" - return self._parsed_program.advanced_sequencer_table - - @property - def waveform_mode(self) -> str: - return self._mode - - -def _check_merge_with_next(program, n, max_seq_len): - if (program[n].repetition_count == 1 and program[n+1].repetition_count == 1 and - len(program[n]) + len(program[n+1]) < max_seq_len): - program[n][len(program[n]):] = program[n + 1][:] - program[n + 1:n + 2] = [] - return True - return False - - -def _check_partial_unroll(program, n, min_seq_len): - st = program[n] - if st.volatile_repetition: - return False - - if sum(entry.repetition_count for entry in st) * st.repetition_count >= min_seq_len: - if sum(entry.repetition_count for entry in st) < min_seq_len: - st.unroll_children() - while len(st) < min_seq_len: - st.split_one_child() - return True - return False - - -def prepare_program_for_advanced_sequence_mode(program: Loop, min_seq_len: int, max_seq_len: int): - """This function tries to bring the program in a form, where the sequence tables' lengths are valid. - - Args: - program: - min_seq_len: - max_seq_len: - - Raises: - TaborException: if a sequence table that is too long cannot be shortened or a sequence table that is to short - cannot be enlarged. - """ - i = 0 - while i < len(program): - program[i].assert_tree_integrity() - if len(program[i]) > max_seq_len: - raise TaborException('The algorithm is not smart enough to make sequence tables shorter') - elif len(program[i]) < min_seq_len: - assert program[i].repetition_count > 0 - if program[i].repetition_count == 1: - # check if merging with neighbour is possible - if i > 0 and _check_merge_with_next(program, i - 1, max_seq_len=max_seq_len): - pass - elif i + 1 < len(program) and _check_merge_with_next(program, i, max_seq_len=max_seq_len): - pass - - # check if (partial) unrolling is possible - elif _check_partial_unroll(program, i, min_seq_len=min_seq_len): - i += 1 - - # check if sequence table can be extended by unrolling a neighbor - elif (i > 0 - and program[i - 1].repetition_count > 1 - and len(program[i]) + len(program[i - 1]) < max_seq_len): - program[i][:0] = program[i - 1].copy_tree_structure()[:] - program[i - 1].repetition_count -= 1 - - elif (i + 1 < len(program) - and program[i + 1].repetition_count > 1 - and len(program[i]) + len(program[i + 1]) < max_seq_len): - program[i][len(program[i]):] = program[i + 1].copy_tree_structure()[:] - program[i + 1].repetition_count -= 1 - if program[i + 1].volatile_repetition: - program[i + 1].volatile_repetition = program[i + 1].volatile_repetition - 1 - - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - elif _check_partial_unroll(program, i, min_seq_len=min_seq_len): - i += 1 - else: - raise TaborException('The algorithm is not smart enough to make this sequence table longer') - else: - i += 1 - - -ParsedProgram = NamedTuple('ParsedProgram', [('advanced_sequencer_table', Sequence[TableEntry]), - ('sequencer_tables', Sequence[Sequence[ - Tuple[TableDescription, Optional[VolatileProperty]]]]), - ('waveforms', Tuple[Waveform, ...]), - ('volatile_parameter_positions', Dict[Union[int, Tuple[int, int]], - VolatileRepetitionCount])]) - - -def parse_aseq_program(program: Loop, used_channels: FrozenSet[ChannelID]) -> ParsedProgram: - volatile_parameter_positions = {} - - advanced_sequencer_table = [] - # we use an ordered dict here to avoid O(n**2) behaviour while looking for duplicates - sequencer_tables = OrderedDict() - waveforms = OrderedDict() - for adv_position, sequencer_table_loop in enumerate(program): - current_sequencer_table = [] - for position, (waveform, repetition_definition, volatile_repetition) in enumerate( - (waveform_loop.waveform.get_subset_for_channels(used_channels), - waveform_loop.repetition_definition, waveform_loop.volatile_repetition) - for waveform_loop in cast(Sequence[Loop], sequencer_table_loop)): - - wf_index = waveforms.setdefault(waveform, len(waveforms)) - current_sequencer_table.append((TableDescription(repetition_count=int(repetition_definition), - element_id=wf_index, jump_flag=0), - volatile_repetition)) - - if volatile_repetition: - assert not isinstance(repetition_definition, int) - volatile_parameter_positions[(adv_position, position)] = repetition_definition - - # make hashable - current_sequencer_table = tuple(current_sequencer_table) - - sequence_index = sequencer_tables.setdefault(current_sequencer_table, len(sequencer_tables)) - sequence_no = sequence_index + 1 - - advanced_sequencer_table.append(TableEntry(repetition_count=sequencer_table_loop.repetition_count, - element_number=sequence_no, jump_flag=0)) - if sequencer_table_loop.volatile_repetition: - volatile_parameter_positions[adv_position] = sequencer_table_loop.repetition_definition - - # transform sequencer_tables in lists to make it indexable and mutable - sequencer_tables = list(map(list, sequencer_tables)) - - return ParsedProgram( - advanced_sequencer_table=advanced_sequencer_table, - sequencer_tables=sequencer_tables, - waveforms=tuple(waveforms.keys()), - volatile_parameter_positions=volatile_parameter_positions - ) - - -def parse_single_seq_program(program: Loop, used_channels: FrozenSet[ChannelID]) -> ParsedProgram: - assert program.depth() == 1 - - sequencer_table = [] - waveforms = OrderedDict() - volatile_parameter_positions = {} - - for position, (waveform, repetition_definition, volatile_repetition) in enumerate( - (waveform_loop.waveform.get_subset_for_channels(used_channels), - waveform_loop.repetition_definition, waveform_loop.volatile_repetition) - for waveform_loop in program): - if waveform in waveforms: - waveform_index = waveforms[waveform] - else: - waveform_index = len(waveforms) - waveforms[waveform] = waveform_index - - sequencer_table.append((TableDescription(repetition_count=int(repetition_definition), - element_id=waveform_index, - jump_flag=0), volatile_repetition)) - if volatile_repetition is not None: - volatile_parameter_positions[(0, position)] = repetition_definition - - return ParsedProgram( - advanced_sequencer_table=[TableEntry(repetition_count=program.repetition_count, element_number=1, jump_flag=0)], - sequencer_tables=[sequencer_table], - waveforms=tuple(waveforms.keys()), - volatile_parameter_positions=volatile_parameter_positions - ) diff --git a/qupulse/hardware/awgs/__init__.py b/qupulse/hardware/awgs/__init__.py index 028f34cd4..8d496a2a9 100644 --- a/qupulse/hardware/awgs/__init__.py +++ b/qupulse/hardware/awgs/__init__.py @@ -4,7 +4,7 @@ __all__ = ["install_requirements"] try: - from qupulse.hardware.awgs.old_tabor import TaborDevice, TaborChannelTuple + from qupulse.hardware.awgs.tabor import TaborDevice, TaborChannelTuple __all__.extend(["TaborAWGRepresentation", "TaborChannelPair"]) except ImportError: pass diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index 95a63b8c7..8913510b0 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -1,20 +1,21 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs.base import AWGChannelTuple # TODO (LuL): Not Tabor, but base class: ChannelTuple +from qupulse.hardware.awgs.base import AWGChannelTuple from qupulse.hardware.awgs.old_base import AWG class ChannelTupleAdapter(AWG): + # TODO (toCheck): is this DocString okay like this? """ - + 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. """ - # TODO (LuL): Doc strings def __copy__(self) -> None: pass - def __init__(self, channel_tuple: AWGChannelTuple): # TODO (LuL): Not Tabor, but base class: ChannelTuple + def __init__(self, channel_tuple: AWGChannelTuple): self._channel_tuple = channel_tuple def identifier(self) -> str: diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 9fc57c02d..29c95e776 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -23,6 +23,36 @@ def synchronize_channels(self, group_size: int) -> None: raise NotImplementedError() +class DeviceControl(AWGDeviceFeature, ABC): + #TODO (toCheck): is this Feature ok like this? + @abstractmethod + def enable(self) -> None: + """This method generates the selected output waveform.""" + raise NotImplementedError() + + @abstractmethod + def abort(self) -> None: + """This method terminates the current generation of the output waveform.""" + raise NotImplementedError() + + @abstractmethod + def reset(self) -> None: + """ + Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and + all channel tuples are cleared. + """ + raise NotImplementedError() + + @abstractmethod + def trigger(self) -> None: + # TODO (toDo): Docstring missing + """ + + """ + raise NotImplementedError() + + + ######################################################################################################################## # channel tuple features ######################################################################################################################## @@ -90,6 +120,16 @@ def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" raise NotImplementedError() + @abstractmethod + def run_current_program(self) -> None: + raise NotImplementedError() + + #TODO: change_armed_program hierhin verschieben + """ + @abstractmethod + def change_armed_program(self) -> None: + raise NotImplementedError() + """ ######################################################################################################################## # channel features diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py index f99db5b2c..6b664d867 100644 --- a/qupulse/hardware/awgs/old_tabor.py +++ b/qupulse/hardware/awgs/old_tabor.py @@ -16,7 +16,7 @@ from qupulse._program._loop import Loop, make_compatible from qupulse.hardware.util import voltage_to_uint16, find_positions from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling -from qupulse._program.old_tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ +from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ make_combined_wave diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 7e257a77d..887d86680 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,11 +1,9 @@ import fractions import functools -import itertools import logging import numbers -import operator import sys -from enum import Enum +import weakref from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple, \ Generator, Iterable from collections import OrderedDict @@ -15,7 +13,7 @@ from qupulse._program.waveforms import MultiChannelWaveform from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ - ProgramManagement, ActivatableChannels + ProgramManagement, ActivatableChannels, DeviceControl from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times from qupulse.utils.types import Collection from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel @@ -74,21 +72,60 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: # Device ######################################################################################################################## # Features -# TODO: maybe implement Synchronization Feature for Tabor Devices -# TODO (LuL): Not just maybe. Use the TaborChannelSynchronization for channel synchronization, but you can -# raise an error if the the group-size is not 2. Groups of 4 can be implemented later, but you should -# prepare the class for this. -""" class TaborChannelSynchronization(ChannelSynchronization): def __init__(self, device: "TaborDevice"): super().__init__() self._parent = device def synchronize_channels(self, group_size: int) -> None: - pass # TODO: to implement -""" + if group_size == 2: + i = 0 + while i < group_size: + self._parent._channel_tuples.append( + TaborChannelTuple((i+1), + self._parent, + self._parent.channels[(i*group_size):((i*group_size) + group_size)], + self._parent.marker_channels[(i*group_size):((i*group_size) + group_size)]) + ) + i = i + 1 + else: + raise NotImplementedError() + + +class TaborDeviceControl(DeviceControl): + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = device + + def enable(self) -> None: + """ + This method immediately generates the selected output waveform, if the device is in continuous and armed + run mode. + """ + self._parent.send_cmd(":ENAB") + + def abort(self) -> None: + """ + With abort you can terminate the current generation of the output waveform. When the output waveform is + terminated the output starts generating an idle waveform. + """ + self._parent.send_cmd(":ABOR") + + def reset(self) -> None: + """ + Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and + all channel tuples are cleared. + """ + self._parent.send_cmd(':RES') + self._parent._coupled = None + + self._parent._initialize() + for channel_tuple in self._parent.channel_tuples: + channel_tuple[TaborProgramManagement].clear() + def trigger(self) -> None: + self.send_cmd(":TRIG") # Implementation class TaborDevice(AWGDevice): @@ -106,12 +143,11 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external mirror_addresses: list of devices on which the same things as on the main device are done. For example you can a simulator and a real Device at once """ - # TODO (LuL): I don't think None would work for instr_addr. If so, remove the default value to make it mandatory super().__init__(device_name) self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) self._coupled = None - self._clock_marker = [0, 0, 0, 0] # TODO: What are clock markers used for? + self._clock_marker = [0, 0, 0, 0] if reset: self.send_cmd(":RES") @@ -123,11 +159,11 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] # ChannelTuple - # TODO (LuL): Use the ChannelSynchronization-feature to set up the ChannelTuple's - self._channel_tuples = [ - TaborChannelTuple(1, self, self.channels[0:2], self.marker_channels[0:2]), - TaborChannelTuple(2, self, self.channels[2:4], self.marker_channels[2:4]) - ] + self._channel_tuples = [] + + self.add_feature(TaborDeviceControl(self)) + self.add_feature(TaborChannelSynchronization(self)) + self[TaborChannelSynchronization].synchronize_channels(2) if external_trigger: raise NotImplementedError() # pragma: no cover @@ -286,22 +322,6 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover - def enable(self) -> None: - """ - This method immediately generates the selected output waveform, if the device is in continuous and armed - run mode. - """ - # TODO (LuL): Can make a feature for this? - self.send_cmd(":ENAB") - - def abort(self) -> None: - """ - With abort you can terminate the current generation of the output waveform. When the output waveform is - terminated the output starts generating an idle waveform. - """ - # TODO (LuL): Can make a feature for this? - self.send_cmd(":ABOR") - def _initialize(self) -> None: # 1. Select channel # 2. Turn off gated mode @@ -319,23 +339,6 @@ def _initialize(self) -> None: self.send_cmd(":INST:SEL 3") self.send_cmd(setup_command) - def reset(self) -> None: - """ - Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and - all channel tuples are cleared. - """ - # TODO (LuL): Can make a feature for this? - self.send_cmd(':RES') - self._coupled = None - - self._initialize() - for channel_tuple in self.channel_tuples: - channel_tuple[TaborProgramManagement].clear() - - def trigger(self) -> None: - # TODO (LuL): Can make a feature for this? - self.send_cmd(":TRIG") - def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: """ A method to get the first readable device out of all devices. @@ -405,25 +408,25 @@ class TaborChannelActivatable(ActivatableChannels): # I think this would be more innovative def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() - self.parent = marker_channel + self._parent = weakref.ref(marker_channel) @property def status(self) -> bool: pass # TODO: to implement def enable(self): - command_string = ":INST:SEL {ch_id}; :OUTP ON".format(ch_id=self.parent.idn) - self.parent.device.send_cmd(command_string) + command_string = ":INST:SEL {ch_id}; :OUTP ON".format(ch_id=self._parent().idn) + self._parent().device.send_cmd(command_string) def disable(self): - command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self.parent.idn) - self.parent.device.send_cmd(command_string) + command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self._parent().idn) + self._parent().device.send_cmd(command_string) @status.setter def status(self, channel_status: bool) -> None: - command_string = ":INST:SEL {ch_id}; :OUTP {output}".format(ch_id=self.parent.idn, + command_string = ":INST:SEL {ch_id}; :OUTP {output}".format(ch_id=self._parent().idn, output="ON" if channel_status else "OFF") - self.parent.device.send_cmd(command_string) + self._parent().device.send_cmd(command_string) # Implementation @@ -431,7 +434,7 @@ class TaborChannel(AWGChannel): def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) - self._device = device + self._device = weakref.ref(device) # adding Features self.add_feature(TaborOffsetAmplitude(self)) @@ -441,12 +444,12 @@ def __init__(self, idn: int, device: TaborDevice): @property def device(self) -> TaborDevice: """Returns the device that the channel belongs to""" - return self._device + return self._device() @property def channel_tuple(self) -> Optional[AWGChannelTuple]: """Returns the channel tuple that this channel belongs to""" - return self._channel_tuple + return self._channel_tuple() def _set_channel_tuple(self, channel_tuple) -> None: """ @@ -455,9 +458,10 @@ def _set_channel_tuple(self, channel_tuple) -> None: Args: channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to """ - self._channel_tuple = channel_tuple + #TODO: problem with the _? + self._channel_tuple = weakref.ref(channel_tuple) - def _select(self) -> None: # TODO (LuL): This may be private + def _select(self) -> None: self.device.send_cmd(":INST:SEL {channel}".format(channel=self.idn)) @@ -474,7 +478,8 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): @property def programs(self) -> Set[str]: - pass # TODO: to implement + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program.name for program in self._parent._known_programs.keys()) @with_configuration_guard @with_select @@ -549,10 +554,19 @@ def arm(self, name: Optional[str]) -> None: else: self._parent.change_armed_program(name) - @property - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - return set(program.name for program in self._parent._known_programs.keys()) + # TODO Does this work fine with @with_select? + @with_select + def run_current_program(self) -> None: + """ + This method starts running the active program + + Throws: + RuntimeError: This exception is thrown if there is no active program for this device + """ + if self._parent._current_program: + self._parent.device.send_cmd(':TRIG', paranoia_level=self._parent.internal_paranoia_level) + else: + raise RuntimeError("No program active") class TaborChannelTuple(AWGChannelTuple): @@ -561,7 +575,7 @@ class TaborChannelTuple(AWGChannelTuple): def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], marker_channels: Iterable["TaborMarkerChannel"]): super().__init__(idn) - self._device = device # TODO: weakref.ref(device) can't be used like in the old driver + self._device = weakref.ref(device) self._configuration_guard_count = 0 self._is_in_config_mode = False @@ -625,7 +639,7 @@ def _select(self) -> None: @property def device(self) -> TaborDevice: """Returns the device that the channel tuple belongs to""" - return self._device + return self._device() @property def channels(self) -> Collection["TaborChannel"]: @@ -709,15 +723,11 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.device.get_readable_device(simulator=True).read_adv_seq_table() - # upload im Feature - def read_complete_program(self) -> PlottableProgram: return PlottableProgram.from_read_data(self.read_waveforms(), self.read_sequence_tables(), self.read_advanced_sequencer_table()) - # clear im Feature - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ Tuple[np.ndarray, np.ndarray, np.ndarray]: # TODO: comment was not finished @@ -1027,37 +1037,11 @@ def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add self._current_program = name - @with_select - def run_current_program(self) -> None: # TODO (LuL): Add this to ProgramManagement - """ - This method starts running the active program - - Throws: - RuntimeError: This exception is thrown if there is no active program for this device - """ - if self._current_program: - self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) - else: - raise RuntimeError("No program active") - @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(program for program in self._known_programs.keys()) - def num_channels(self) -> int: # TODO (LuL): This is not needed, the caller can call len(...) hisself - """ - Returns the number of channels that belong to the channel tuple - """ - return len(self.channels) - - @property - def num_markers(self) -> int: # TODO (LuL): This is not needed, the caller can call len(...) hisself - """ - Returns the number of marker channels that belong to the channel tuple - """ - return len(self.marker_channels) - def _enter_config_mode(self) -> None: """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. @@ -1148,7 +1132,7 @@ def status(self, channel_state: bool) -> None: class TaborMarkerChannel(AWGMarkerChannel): def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) - self._device = device + self._device = weakref.ref(device) # adding Features self.add_feature(TaborMarkerChannelActivatable(self)) @@ -1158,21 +1142,21 @@ def __init__(self, idn: int, device: TaborDevice): @property def device(self) -> TaborDevice: """Returns the device that this marker channel belongs to""" - return self._device + return self._device() @property def channel_tuple(self) -> Optional[TaborChannelTuple]: """Returns the channel tuple that this marker channel belongs to""" - return self._channel_tuple + return self._channel_tuple() - def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: + def _set_channel_tuple(self, channel_tuple) -> None: """ The channel tuple 'channel_tuple' is assigned to this marker channel Args: channel_tuple (TaborChannelTuple): the channel tuple that this marker channel belongs to """ - self._channel_tuple = channel_tuple + self._channel_tuple = weakref.ref(channel_tuple) def _select(self) -> None: """ diff --git a/tests/_program/tabor_tests.py b/tests/_program/tabor_tests.py index 5663c225c..f874f718c 100644 --- a/tests/_program/tabor_tests.py +++ b/tests/_program/tabor_tests.py @@ -11,7 +11,7 @@ from teawg import model_properties_dict -from qupulse._program.old_tabor import TaborException, TaborProgram, \ +from qupulse._program.tabor import TaborException, TaborProgram, \ TaborSegment, TaborSequencing, PlottableProgram, TableDescription, make_combined_wave, TableEntry from qupulse._program._loop import Loop from qupulse._program.volatile import VolatileRepetitionCount diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py index 5e6aa53fa..8143442c5 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -8,7 +8,7 @@ import numpy as np from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair -from qupulse._program.old_tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry +from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry from typing import List, Tuple, Optional, Any class TaborSimulatorManager: diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py index bb55a8f06..dbc4d9793 100644 --- a/tests/hardware/tabor_dummy_based_tests.py +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -8,8 +8,10 @@ import numpy as np +from qupulse._program.tabor import TableDescription, TableEntry from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling -from qupulse.hardware.awgs.old_tabor import TaborProgram, TaborDevice +from qupulse.hardware.awgs.tabor import TaborProgram, TaborDevice, TaborProgramMemory +from qupulse.utils.types import TimeType from tests.hardware.dummy_modules import import_package @@ -95,7 +97,7 @@ def tearDownClass(cls): cls.restore_packages() def setUp(self): - from qupulse.hardware.awgs.tabor import TaborAWGRepresentation + from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation self.instrument = TaborAWGRepresentation('main_instrument', reset=True, paranoia_level=2, @@ -174,14 +176,14 @@ def to_new_advanced_sequencer_table(advanced_sequencer_table: List[Tuple[int, in def setUpClass(cls): super().setUpClass() - from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborProgramMemory, TaborSegment, TaborSequencing + from qupulse.hardware.awgs.old_tabor import TaborChannelPair, TaborProgramMemory, TaborSegment, TaborSequencing from qupulse.pulses.table_pulse_template import TableWaveform from qupulse.pulses.interpolation import HoldInterpolationStrategy from qupulse._program._loop import Loop from tests.pulses.sequencing_dummies import DummyWaveform - from qupulse._program.old_tabor import make_combined_wave + from qupulse._program.tabor import make_combined_wave cls.DummyWaveform = DummyWaveform cls.TaborChannelPair = TaborChannelPair diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 78a43cc24..bcf59a4d8 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -9,8 +9,9 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry +from qupulse.hardware.awgs.features import DeviceControl from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ - TaborOffsetAmplitude + TaborOffsetAmplitude, TaborDeviceControl class TaborSimulatorManager: @@ -82,7 +83,6 @@ class TaborSimulatorBasedTest(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # self.instrument = None self.instrument: TaborDevice @classmethod @@ -101,7 +101,7 @@ def setUp(self): self.instrument = self.simulator_manager.connect() def tearDown(self): - self.instrument.reset() + self.instrument[TaborDeviceControl].reset() self.simulator_manager.disconnect() @staticmethod From 224147f5e9d9c55186e346f804241df987556c29 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 2 Apr 2020 13:15:05 +0200 Subject: [PATCH 061/107] More methods in ProgramManagement, but not all Unit Tests run --- .../hardware/awgs/channel_tuple_wrapper.py | 1 - qupulse/hardware/awgs/features.py | 5 +- qupulse/hardware/awgs/tabor.py | 118 ++++++++++++++---- tests/hardware/tabor_simulator_based_tests.py | 5 +- 4 files changed, 97 insertions(+), 32 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index 8913510b0..fff951010 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -11,7 +11,6 @@ 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 diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 29c95e776..1b2e79024 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -122,14 +122,13 @@ def programs(self) -> Set[str]: @abstractmethod def run_current_program(self) -> None: + """This method starts running the active program""" raise NotImplementedError() - #TODO: change_armed_program hierhin verschieben - """ @abstractmethod def change_armed_program(self) -> None: raise NotImplementedError() - """ + ######################################################################################################################## # channel features diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 887d86680..00d7ece8d 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -31,7 +31,6 @@ __all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] - TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), ('program', TaborProgram)]) @@ -43,6 +42,7 @@ def with_configuration_guard(function_object: Callable[["TaborChannelTuple", Any @functools.wraps(function_object) def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: + if channel_pair._configuration_guard_count == 0: channel_pair._enter_config_mode() channel_pair._configuration_guard_count += 1 @@ -56,7 +56,6 @@ def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: return guarding_method - def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @@ -83,10 +82,10 @@ def synchronize_channels(self, group_size: int) -> None: i = 0 while i < group_size: self._parent._channel_tuples.append( - TaborChannelTuple((i+1), + TaborChannelTuple((i + 1), self._parent, - self._parent.channels[(i*group_size):((i*group_size) + group_size)], - self._parent.marker_channels[(i*group_size):((i*group_size) + group_size)]) + self._parent.channels[(i * group_size):((i * group_size) + group_size)], + self._parent.marker_channels[(i * group_size):((i * group_size) + group_size)]) ) i = i + 1 else: @@ -125,7 +124,8 @@ def reset(self) -> None: channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: - self.send_cmd(":TRIG") + self._parent.send_cmd(":TRIG") + # Implementation class TaborDevice(AWGDevice): @@ -140,8 +140,8 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external paranoia_level (int): Paranoia level that is forwarded to teawg external_trigger (bool): Not supported yet reset (bool): - mirror_addresses: list of devices on which the same things as on the main device are done. For example you can a simulator and a real Device at once - + mirror_addresses: list of devices on which the same things as on the main device are done. + For example you can a simulator and a real Device at once """ super().__init__(device_name) self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) @@ -403,15 +403,12 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: class TaborChannelActivatable(ActivatableChannels): - # TODO (LuL): Wouldn't it be better, to have a property named "active" or "enabled" instead of "status" - # and not a setter for that property, but functions like "activate"/"deactivate" or "enable"/"disable"? - # I think this would be more innovative def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() self._parent = weakref.ref(marker_channel) @property - def status(self) -> bool: + def active(self) -> bool: pass # TODO: to implement def enable(self): @@ -422,12 +419,6 @@ def disable(self): command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self._parent().idn) self._parent().device.send_cmd(command_string) - @status.setter - def status(self, channel_status: bool) -> None: - command_string = ":INST:SEL {ch_id}; :OUTP {output}".format(ch_id=self._parent().idn, - output="ON" if channel_status else "OFF") - self._parent().device.send_cmd(command_string) - # Implementation class TaborChannel(AWGChannel): @@ -458,7 +449,7 @@ def _set_channel_tuple(self, channel_tuple) -> None: Args: channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to """ - #TODO: problem with the _? + # TODO (toCheck): problem with the _? self._channel_tuple = weakref.ref(channel_tuple) def _select(self) -> None: @@ -476,6 +467,8 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): self._armed_program = None self._parent = channel_tuple + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" @@ -491,6 +484,7 @@ def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], The policy is to prefer amending the unknown waveforms to overwriting old ones. """ + # TODO: Change this if statements because num methods are deleted if len(channels) != self.num_channels: raise ValueError("Channel ID not specified") if len(markers) != self.num_markers: @@ -539,7 +533,7 @@ def clear(self) -> None: self._parent._sequencer_tables = [] self._parent._known_programs = dict() - self._parent.change_armed_program(None) + self._parent[TaborProgramManagement].change_armed_program(None) @with_select def arm(self, name: Optional[str]) -> None: @@ -552,7 +546,7 @@ def arm(self, name: Optional[str]) -> None: if self._parent._current_program == name: self._parent.device.send_cmd("SEQ:SEL 1") else: - self._parent.change_armed_program(name) + self._parent[TaborProgramManagement].change_armed_program(name) # TODO Does this work fine with @with_select? @with_select @@ -568,6 +562,79 @@ def run_current_program(self) -> None: else: raise RuntimeError("No program active") + @with_select + @with_configuration_guard + def change_armed_program(self, name: Optional[str]) -> None: + """The armed program of the channel tuple is change to the program with the name 'name'""" + if name is None: + sequencer_tables = [self._idle_sequence_table] + advanced_sequencer_table = [(1, 1, 0)] + else: + waveform_to_segment_index, program = self._parent._known_programs[name] + waveform_to_segment_number = waveform_to_segment_index + 1 + + # translate waveform number to actual segment + sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) + for ((rep_count, wf_index, jump_flag), _) in sequencer_table] + for sequencer_table in program.get_sequencer_tables()] + + # insert idle sequence + sequencer_tables = [self._idle_sequence_table] + sequencer_tables + + # adjust advanced sequence table entries by idle sequence table offset + advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) + for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] + + if program.waveform_mode == TaborSequencing.SINGLE: + assert len(advanced_sequencer_table) == 1 + assert len(sequencer_tables) == 2 + + while len(sequencer_tables[1]) < self._parent.device.dev_properties["min_seq_len"]: + assert advanced_sequencer_table[0][0] == 1 + sequencer_tables[1].append((1, 1, 0)) + + # insert idle sequence in advanced sequence table + advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table + + while len(advanced_sequencer_table) < self._parent.device.dev_properties["min_aseq_len"]: + advanced_sequencer_table.append((1, 1, 0)) + + self._parent.device.send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent.internal_paranoia_level) + self._sequencer_tables = [] + self._parent.device.send_cmd("ASEQ:DEL", paranoia_level=self._parent.internal_paranoia_level) + self._advanced_sequence_table = [] + + # download all sequence tables + for i, sequencer_table in enumerate(sequencer_tables): + self._parent.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self._parent.internal_paranoia_level) + self._parent.device._download_sequencer_table(sequencer_table) + self._sequencer_tables = sequencer_tables + self._parent.device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent.internal_paranoia_level) + + self._parent.device._download_adv_seq_table(advanced_sequencer_table) + self._advanced_sequence_table = advanced_sequencer_table + + self._current_program = name + + + def _select(self): + self._parent.channels[0]._select() + + @property + def _configuration_guard_count(self): + return self._parent._configuration_guard_count + + @_configuration_guard_count.setter + def _configuration_guard_count(self, configuration_guard_count): + self._parent._configuration_guard_count = configuration_guard_count + + def _enter_config_mode(self): + self._parent._enter_config_mode() + + def _exit_config_mode(self): + self._parent._exit_config_mode() + + class TaborChannelTuple(AWGChannelTuple): CONFIG_MODE_PARANOIA_LEVEL = None @@ -666,7 +733,7 @@ def free_program(self, name: str) -> TaborProgramMemory: program = self._known_programs.pop(name) self._segment_references[program.waveform_to_segment] -= 1 if self._current_program == name: - self.change_armed_program(None) + self[TaborProgramManagement].change_armed_program(None) return program def _restore_program(self, name: str, program: TaborProgram) -> None: @@ -972,7 +1039,7 @@ def _arm(self, name: str) -> None: if self._current_program == name: self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) else: - self.change_armed_program(name) + self[TaborProgramManagement].change_armed_program(name) # arm im Feature @@ -985,7 +1052,6 @@ def set_program_sequence_table(self, name, new_sequence_table): @with_select @with_configuration_guard def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add this to ProgramManagement - """The armed program of the channel tuple is change to the program with the name 'name'""" if name is None: sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] @@ -1061,8 +1127,8 @@ def _enter_config_mode(self) -> None: marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - #TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature - #for marker_ch in self.marker_channels: + # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature + # for marker_ch in self.marker_channels: # marker_ch[TaborMarkerChannelActivatable].status = False wf_mode_cmd = ':SOUR:FUNC:MODE FIX' diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index bcf59a4d8..70dc724fb 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -11,7 +11,7 @@ from qupulse._program.tabor import TableDescription, TableEntry from qupulse.hardware.awgs.features import DeviceControl from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ - TaborOffsetAmplitude, TaborDeviceControl + TaborOffsetAmplitude, TaborDeviceControl, TaborProgramManagement class TaborSimulatorManager: @@ -216,7 +216,7 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair.change_armed_program('dummy_program') + self.channel_pair[TaborProgramManagement].change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) @@ -275,6 +275,7 @@ def test_set_volatile_parameter(self): for rep, index, jump in table] for table in self.sequence_tables_raw] + actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] self.channel_pair.set_volatile_parameters('dummy_program', parameters=para) From 233a90504b9a5aaa4750ab7d9bd1c4b21f8c66f8 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 2 Apr 2020 15:11:20 +0200 Subject: [PATCH 062/107] Unit tests run fine, but TaborProgramManagement needs some work --- qupulse/hardware/awgs/features.py | 2 ++ qupulse/hardware/awgs/tabor.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 1b2e79024..8ed73706f 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -11,6 +11,7 @@ ######################################################################################################################## class ChannelSynchronization(AWGDeviceFeature, ABC): + """This Feature is used to synchronise a certain ammount of channels""" @abstractmethod def synchronize_channels(self, group_size: int) -> None: """ @@ -24,6 +25,7 @@ def synchronize_channels(self, group_size: int) -> None: class DeviceControl(AWGDeviceFeature, ABC): + """This feature is used for basic communication with a AWG""" #TODO (toCheck): is this Feature ok like this? @abstractmethod def enable(self) -> None: diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 00d7ece8d..5517034cb 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -467,7 +467,7 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): self._armed_program = None self._parent = channel_tuple - self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + #self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] @property def programs(self) -> Set[str]: @@ -567,7 +567,7 @@ def run_current_program(self) -> None: def change_armed_program(self, name: Optional[str]) -> None: """The armed program of the channel tuple is change to the program with the name 'name'""" if name is None: - sequencer_tables = [self._idle_sequence_table] + sequencer_tables = [self._parent._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] else: waveform_to_segment_index, program = self._parent._known_programs[name] @@ -579,7 +579,7 @@ def change_armed_program(self, name: Optional[str]) -> None: for sequencer_table in program.get_sequencer_tables()] # insert idle sequence - sequencer_tables = [self._idle_sequence_table] + sequencer_tables + sequencer_tables = [self._parent._idle_sequence_table] + sequencer_tables # adjust advanced sequence table entries by idle sequence table offset advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) @@ -600,21 +600,21 @@ def change_armed_program(self, name: Optional[str]) -> None: advanced_sequencer_table.append((1, 1, 0)) self._parent.device.send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent.internal_paranoia_level) - self._sequencer_tables = [] + self._parent._sequencer_tables = [] self._parent.device.send_cmd("ASEQ:DEL", paranoia_level=self._parent.internal_paranoia_level) - self._advanced_sequence_table = [] + self._parent._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): self._parent.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self._parent.internal_paranoia_level) self._parent.device._download_sequencer_table(sequencer_table) - self._sequencer_tables = sequencer_tables + self._parent._sequencer_tables = sequencer_tables self._parent.device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent.internal_paranoia_level) self._parent.device._download_adv_seq_table(advanced_sequencer_table) - self._advanced_sequence_table = advanced_sequencer_table + self._parent._advanced_sequence_table = advanced_sequencer_table - self._current_program = name + self._parent._current_program = name def _select(self): @@ -1095,7 +1095,7 @@ def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add for i, sequencer_table in enumerate(sequencer_tables): self.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self.internal_paranoia_level) self.device._download_sequencer_table(sequencer_table) - self._sequencer_tables = sequencer_tables + self._parent._sequencer_tables = sequencer_tables self.device.send_cmd("SEQ:SEL 1", paranoia_level=self.internal_paranoia_level) self.device._download_adv_seq_table(advanced_sequencer_table) From ef1db882b85eab606e9c717c77c337fc3ca6e490 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 6 Apr 2020 20:51:42 +0200 Subject: [PATCH 063/107] Unit tests run fine, but TaborProgramManagement needs some work --- qupulse/hardware/awgs/tabor.py | 63 +------------------ tests/hardware/tabor_simulator_based_tests.py | 4 +- 2 files changed, 5 insertions(+), 62 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 5517034cb..54dd766f3 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -467,7 +467,7 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): self._armed_program = None self._parent = channel_tuple - #self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] @property def programs(self) -> Set[str]: @@ -548,7 +548,6 @@ def arm(self, name: Optional[str]) -> None: else: self._parent[TaborProgramManagement].change_armed_program(name) - # TODO Does this work fine with @with_select? @with_select def run_current_program(self) -> None: """ @@ -567,7 +566,7 @@ def run_current_program(self) -> None: def change_armed_program(self, name: Optional[str]) -> None: """The armed program of the channel tuple is change to the program with the name 'name'""" if name is None: - sequencer_tables = [self._parent._idle_sequence_table] + sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] else: waveform_to_segment_index, program = self._parent._known_programs[name] @@ -579,7 +578,7 @@ def change_armed_program(self, name: Optional[str]) -> None: for sequencer_table in program.get_sequencer_tables()] # insert idle sequence - sequencer_tables = [self._parent._idle_sequence_table] + sequencer_tables + sequencer_tables = [self._idle_sequence_table] + sequencer_tables # adjust advanced sequence table entries by idle sequence table offset advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) @@ -616,7 +615,6 @@ def change_armed_program(self, name: Optional[str]) -> None: self._parent._current_program = name - def _select(self): self._parent.channels[0]._select() @@ -635,7 +633,6 @@ def _exit_config_mode(self): self._parent._exit_config_mode() - class TaborChannelTuple(AWGChannelTuple): CONFIG_MODE_PARANOIA_LEVEL = None @@ -666,7 +663,6 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann output_amplitude=0.5, output_offset=0., resolution=14), None, None) - self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] self._current_program = None @@ -1049,59 +1045,6 @@ def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table) def set_program_sequence_table(self, name, new_sequence_table): self._known_programs[name][1]._sequencer_tables = new_sequence_table - @with_select - @with_configuration_guard - def change_armed_program(self, name: Optional[str]) -> None: # TODO (LuL): Add this to ProgramManagement - if name is None: - sequencer_tables = [self._idle_sequence_table] - advanced_sequencer_table = [(1, 1, 0)] - else: - waveform_to_segment_index, program = self._known_programs[name] - waveform_to_segment_number = waveform_to_segment_index + 1 - - # translate waveform number to actual segment - sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for ((rep_count, wf_index, jump_flag), _) in sequencer_table] - for sequencer_table in program.get_sequencer_tables()] - - # insert idle sequence - sequencer_tables = [self._idle_sequence_table] + sequencer_tables - - # adjust advanced sequence table entries by idle sequence table offset - advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) - for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] - - if program.waveform_mode == TaborSequencing.SINGLE: - assert len(advanced_sequencer_table) == 1 - assert len(sequencer_tables) == 2 - - while len(sequencer_tables[1]) < self.device.dev_properties["min_seq_len"]: - assert advanced_sequencer_table[0][0] == 1 - sequencer_tables[1].append((1, 1, 0)) - - # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table - - while len(advanced_sequencer_table) < self.device.dev_properties["min_aseq_len"]: - advanced_sequencer_table.append((1, 1, 0)) - - # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd("SEQ:DEL:ALL", paranoia_level=self.internal_paranoia_level) - self._sequencer_tables = [] - self.device.send_cmd("ASEQ:DEL", paranoia_level=self.internal_paranoia_level) - self._advanced_sequence_table = [] - - # download all sequence tables - for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self.internal_paranoia_level) - self.device._download_sequencer_table(sequencer_table) - self._parent._sequencer_tables = sequencer_tables - self.device.send_cmd("SEQ:SEL 1", paranoia_level=self.internal_paranoia_level) - - self.device._download_adv_seq_table(advanced_sequencer_table) - self._advanced_sequence_table = advanced_sequencer_table - - self._current_program = name @property def programs(self) -> Set[str]: diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 70dc724fb..7036258c1 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -243,7 +243,7 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) + actual_sequence_tables = [self.channel_pair[TaborProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] @@ -271,7 +271,7 @@ def test_set_volatile_parameter(self): self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) para = {'a': 5} - actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index + 2, jump) + actual_sequence_tables = [self.channel_pair[TaborProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] From 130e36c9fba7c892c31840e6172839c876a1308d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 8 Apr 2020 11:55:31 +0200 Subject: [PATCH 064/107] Weakrefs added for Channels, ChannelTuple and features --- .../hardware/awgs/channel_tuple_wrapper.py | 20 +-- qupulse/hardware/awgs/tabor.py | 142 +++++++++--------- tests/hardware/tabor_simulator_based_tests.py | 15 +- 3 files changed, 88 insertions(+), 89 deletions(-) diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs/channel_tuple_wrapper.py index fff951010..b5e0bbd12 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs/channel_tuple_wrapper.py @@ -32,25 +32,25 @@ def upload(self, name: str, markers: Tuple[Optional["ChannelID"], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: - from qupulse.hardware.awgs.tabor import TaborProgramManagement - return self._channel_tuple[TaborProgramManagement].upload(name, program, channels, markers, + from qupulse.hardware.awgs.tabor import ProgramManagement + return self._channel_tuple[ProgramManagement].upload(name, program, channels, markers, voltage_transformation, force) def remove(self, name: str) -> None: - from qupulse.hardware.awgs.tabor import TaborProgramManagement - return self._channel_tuple[TaborProgramManagement].remove(name) + from qupulse.hardware.awgs.tabor import ProgramManagement + return self._channel_tuple[ProgramManagement].remove(name) def clear(self) -> None: - from qupulse.hardware.awgs.tabor import TaborProgramManagement - return self._channel_tuple[TaborProgramManagement].clear() + from qupulse.hardware.awgs.tabor import ProgramManagement + return self._channel_tuple[ProgramManagement].clear() def arm(self, name: Optional[str]) -> None: - from qupulse.hardware.awgs.tabor import TaborProgramManagement - return self._channel_tuple[TaborProgramManagement].arm(name) + from qupulse.hardware.awgs.tabor import ProgramManagement + return self._channel_tuple[ProgramManagement].arm(name) def programs(self) -> Set[str]: - from qupulse.hardware.awgs.tabor import TaborProgramManagement - return self._channel_tuple[TaborProgramManagement].programs + from qupulse.hardware.awgs.tabor import ProgramManagement + return self._channel_tuple[ProgramManagement].programs def sample_rate(self) -> float: return self._channel_tuple.sample_rate diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 54dd766f3..1473b98c8 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -75,17 +75,17 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: class TaborChannelSynchronization(ChannelSynchronization): def __init__(self, device: "TaborDevice"): super().__init__() - self._parent = device + self._parent = weakref.ref(device) def synchronize_channels(self, group_size: int) -> None: if group_size == 2: i = 0 while i < group_size: - self._parent._channel_tuples.append( + self._parent()._channel_tuples.append( TaborChannelTuple((i + 1), - self._parent, - self._parent.channels[(i * group_size):((i * group_size) + group_size)], - self._parent.marker_channels[(i * group_size):((i * group_size) + group_size)]) + self._parent(), + self._parent().channels[(i * group_size):((i * group_size) + group_size)], + self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) ) i = i + 1 else: @@ -95,36 +95,36 @@ def synchronize_channels(self, group_size: int) -> None: class TaborDeviceControl(DeviceControl): def __init__(self, device: "TaborDevice"): super().__init__() - self._parent = device + self._parent = weakref.ref(device) def enable(self) -> None: """ This method immediately generates the selected output waveform, if the device is in continuous and armed run mode. """ - self._parent.send_cmd(":ENAB") + self._parent().send_cmd(":ENAB") def abort(self) -> None: """ With abort you can terminate the current generation of the output waveform. When the output waveform is terminated the output starts generating an idle waveform. """ - self._parent.send_cmd(":ABOR") + self._parent().send_cmd(":ABOR") def reset(self) -> None: """ Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and all channel tuples are cleared. """ - self._parent.send_cmd(':RES') - self._parent._coupled = None + self._parent().send_cmd(':RES') + self._parent()._coupled = None - self._parent._initialize() - for channel_tuple in self._parent.channel_tuples: + self._parent()._initialize() + for channel_tuple in self._parent().channel_tuples: channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: - self._parent.send_cmd(":TRIG") + self._parent().send_cmd(":TRIG") # Implementation @@ -367,12 +367,12 @@ def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: class TaborOffsetAmplitude(OffsetAmplitude): def __init__(self, channel: "TaborChannel"): super().__init__() - self._parent = channel + self._parent = weakref.ref(channel) @property def offset(self) -> float: return float( - self._parent.device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent.idn))) + self._parent.device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent().idn))) @offset.setter def offset(self, offset: float) -> None: @@ -380,18 +380,18 @@ def offset(self, offset: float) -> None: @property def amplitude(self) -> float: - coupling = self._parent.device._send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent.idn)) + coupling = self._parent().device._send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent().idn)) if coupling == "DC": - return float(self._parent.device._send_query(":VOLT?")) + return float(self._parent().device._send_query(":VOLT?")) elif coupling == "HV": - return float(self._parent.device._send_query(":VOLT:HV?")) + return float(self._parent().device._send_query(":VOLT:HV?")) else: raise TaborException("Unknown coupling: {}".format(coupling)) @amplitude.setter def amplitude(self, amplitude: float) -> None: - self._parent.device.send_cmd(":INST:SEL {channel}; :OUTP:COUP DC".format(channel=self._parent.idn)) - self._parent.device.send_cmd(":VOLT {amp}".format(amp=amplitude)) + self._parent().device.send_cmd(":INST:SEL {channel}; :OUTP:COUP DC".format(channel=self._parent().idn)) + self._parent().device.send_cmd(":VOLT {amp}".format(amp=amplitude)) @property def amplitude_offset_handling(self) -> str: @@ -402,13 +402,13 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: pass # TODO: to implement -class TaborChannelActivatable(ActivatableChannels): +class TaborActivatableChannels(ActivatableChannels): def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() self._parent = weakref.ref(marker_channel) @property - def active(self) -> bool: + def enabled(self) -> bool: pass # TODO: to implement def enable(self): @@ -465,14 +465,14 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): super().__init__() self._programs = {} self._armed_program = None - self._parent = channel_tuple + self._parent = weakref.ref(channel_tuple) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" - return set(program.name for program in self._parent._known_programs.keys()) + return set(program.name for program in self._parent()._known_programs.keys()) @with_configuration_guard @with_select @@ -493,7 +493,7 @@ def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], raise ValueError("Wrong number of voltage transformations") # adjust program to fit criteria - sample_rate = self._parent.device.channel_tuples[0].sample_rate + sample_rate = self._parent().device.channel_tuples[0].sample_rate make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, @@ -508,32 +508,32 @@ def remove(self, name: str) -> None: Args: name (str): The name of the program to remove. """ - self._parent.free_program(name) - self._parent.cleanup() + self._parent().free_program(name) + self._parent().cleanup() def clear(self) -> None: """Delete all segments and clear memory""" - self._parent.device.channels[0]._select() - self._parent.device.send_cmd(":TRAC:DEL:ALL") - self._parent.device.send_cmd(":SOUR:SEQ:DEL:ALL") - self._parent.device.send_cmd(":ASEQ:DEL") + self._parent().device.channels[0]._select() + self._parent().device.send_cmd(":TRAC:DEL:ALL") + self._parent().device.send_cmd(":SOUR:SEQ:DEL:ALL") + self._parent().device.send_cmd(":ASEQ:DEL") - self._parent.device.send_cmd(":TRAC:DEF 1, 192") - self._parent.device.send_cmd(":TRAC:SEL 1") - self._parent.device.send_cmd(":TRAC:MODE COMB") - self._parent.device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent._idle_segment.get_as_binary()) + self._parent().device.send_cmd(":TRAC:DEF 1, 192") + self._parent().device.send_cmd(":TRAC:SEL 1") + self._parent().device.send_cmd(":TRAC:MODE COMB") + self._parent().device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent()._idle_segment.get_as_binary()) - self._parent._segment_lengths = 192 * np.ones(1, dtype=np.uint32) - self._parent._segment_capacity = 192 * np.ones(1, dtype=np.uint32) - self._parent._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._parent._idle_segment) - self._parent._segment_references = np.ones(1, dtype=np.uint32) + self._parent()._segment_lengths = 192 * np.ones(1, dtype=np.uint32) + self._parent()._segment_capacity = 192 * np.ones(1, dtype=np.uint32) + self._parent()._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._parent()._idle_segment) + self._parent()._segment_references = np.ones(1, dtype=np.uint32) - self._parent._advanced_sequence_table = [] - self._parent._sequencer_tables = [] + self._parent()._advanced_sequence_table = [] + self._parent()._sequencer_tables = [] - self._parent._known_programs = dict() - self._parent[TaborProgramManagement].change_armed_program(None) + self._parent()._known_programs = dict() + self.change_armed_program(None) @with_select def arm(self, name: Optional[str]) -> None: @@ -543,10 +543,10 @@ def arm(self, name: Optional[str]) -> None: Args: name (str): the program the device should change to """ - if self._parent._current_program == name: - self._parent.device.send_cmd("SEQ:SEL 1") + if self._parent()._current_program == name: + self._parent().device.send_cmd("SEQ:SEL 1") else: - self._parent[TaborProgramManagement].change_armed_program(name) + self.change_armed_program(name) @with_select def run_current_program(self) -> None: @@ -556,8 +556,8 @@ def run_current_program(self) -> None: Throws: RuntimeError: This exception is thrown if there is no active program for this device """ - if self._parent._current_program: - self._parent.device.send_cmd(':TRIG', paranoia_level=self._parent.internal_paranoia_level) + if self._parent()._current_program: + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) else: raise RuntimeError("No program active") @@ -569,7 +569,7 @@ def change_armed_program(self, name: Optional[str]) -> None: sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] else: - waveform_to_segment_index, program = self._parent._known_programs[name] + waveform_to_segment_index, program = self._parent()._known_programs[name] waveform_to_segment_number = waveform_to_segment_index + 1 # translate waveform number to actual segment @@ -588,49 +588,49 @@ def change_armed_program(self, name: Optional[str]) -> None: assert len(advanced_sequencer_table) == 1 assert len(sequencer_tables) == 2 - while len(sequencer_tables[1]) < self._parent.device.dev_properties["min_seq_len"]: + while len(sequencer_tables[1]) < self._parent().device.dev_properties["min_seq_len"]: assert advanced_sequencer_table[0][0] == 1 sequencer_tables[1].append((1, 1, 0)) # insert idle sequence in advanced sequence table advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table - while len(advanced_sequencer_table) < self._parent.device.dev_properties["min_aseq_len"]: + while len(advanced_sequencer_table) < self._parent().device.dev_properties["min_aseq_len"]: advanced_sequencer_table.append((1, 1, 0)) - self._parent.device.send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent.internal_paranoia_level) - self._parent._sequencer_tables = [] - self._parent.device.send_cmd("ASEQ:DEL", paranoia_level=self._parent.internal_paranoia_level) - self._parent._advanced_sequence_table = [] + self._parent().device.send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent().internal_paranoia_level) + self._parent()._sequencer_tables = [] + self._parent().device.send_cmd("ASEQ:DEL", paranoia_level=self._parent().internal_paranoia_level) + self._parent()._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self._parent.device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self._parent.internal_paranoia_level) - self._parent.device._download_sequencer_table(sequencer_table) - self._parent._sequencer_tables = sequencer_tables - self._parent.device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent.internal_paranoia_level) + self._parent().device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self._parent().internal_paranoia_level) + self._parent().device._download_sequencer_table(sequencer_table) + self._parent()._sequencer_tables = sequencer_tables + self._parent().device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) - self._parent.device._download_adv_seq_table(advanced_sequencer_table) - self._parent._advanced_sequence_table = advanced_sequencer_table + self._parent().device._download_adv_seq_table(advanced_sequencer_table) + self._parent()._advanced_sequence_table = advanced_sequencer_table - self._parent._current_program = name + self._parent()._current_program = name def _select(self): - self._parent.channels[0]._select() + self._parent().channels[0]._select() @property def _configuration_guard_count(self): - return self._parent._configuration_guard_count + return self._parent()._configuration_guard_count @_configuration_guard_count.setter def _configuration_guard_count(self, configuration_guard_count): - self._parent._configuration_guard_count = configuration_guard_count + self._parent()._configuration_guard_count = configuration_guard_count def _enter_config_mode(self): - self._parent._enter_config_mode() + self._parent()._enter_config_mode() def _exit_config_mode(self): - self._parent._exit_config_mode() + self._parent()._exit_config_mode() class TaborChannelTuple(AWGChannelTuple): @@ -1121,7 +1121,7 @@ def _exit_config_mode(self) -> None: class TaborMarkerChannelActivatable(ActivatableChannels): def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() - self._parent = marker_channel + self._parent = weakref.ref(marker_channel) @property def status(self) -> bool: @@ -1131,10 +1131,10 @@ def status(self) -> bool: def status(self, channel_state: bool) -> None: command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}" command_string = command_string.format( - channel=self._parent.channel_tuple.channels[0].idn, - marker=self._parent.channel_tuple.marker_channels.index(self._parent) + 1, + channel=self._parent().channel_tuple.channels[0].idn, + marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1, active="ON" if channel_state else "OFF") - self._parent.device.send_cmd(command_string) + self._parent().device.send_cmd(command_string) # Implementation diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 7036258c1..dfc644925 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -9,7 +9,7 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs.features import DeviceControl +from qupulse.hardware.awgs.features import DeviceControl, OffsetAmplitude, ProgramManagement from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ TaborOffsetAmplitude, TaborDeviceControl, TaborProgramManagement @@ -101,7 +101,7 @@ def setUp(self): self.instrument = self.simulator_manager.connect() def tearDown(self): - self.instrument[TaborDeviceControl].reset() + self.instrument[DeviceControl].reset() self.simulator_manager.disconnect() @staticmethod @@ -135,12 +135,12 @@ def test_sample_rate(self): def test_amplitude(self): for channel in self.instrument.channels: - self.assertIsInstance(channel[TaborOffsetAmplitude].amplitude, float) + self.assertIsInstance(channel[OffsetAmplitude].amplitude, float) self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') self.instrument.send_cmd(':VOLT 0.7') - self.assertAlmostEqual(.7, self.instrument.channels[0][TaborOffsetAmplitude].amplitude) + self.assertAlmostEqual(.7, self.instrument.channels[0][OffsetAmplitude].amplitude) def test_select_marker(self): with self.assertRaises(IndexError): @@ -216,7 +216,7 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair[TaborProgramManagement].change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet + self.channel_pair[ProgramManagement].change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) @@ -243,7 +243,7 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequence_tables = [self.channel_pair[TaborProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) + actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] @@ -271,7 +271,7 @@ def test_set_volatile_parameter(self): self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) para = {'a': 5} - actual_sequence_tables = [self.channel_pair[TaborProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) + actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] @@ -293,4 +293,3 @@ def test_set_volatile_parameter(self): expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) np.testing.assert_equal(advanced_table, expected) - From eaf6e4e8c18269a12153e69f88e89a29563833ed Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 15 Apr 2020 07:34:19 +0200 Subject: [PATCH 065/107] Changed the loop in the synchronisation feature ActivatableMarkerChannel reworked --- qupulse/hardware/awgs/features.py | 33 ++++--- qupulse/hardware/awgs/tabor.py | 157 +++++++++++++++++------------- 2 files changed, 109 insertions(+), 81 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 8ed73706f..23e35d70a 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Callable, Optional, Set, Tuple +from typing import Callable, Optional, Set, Tuple, Dict, Union from qupulse._program._loop import Loop from qupulse.hardware.awgs.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature @@ -12,6 +12,7 @@ class ChannelSynchronization(AWGDeviceFeature, ABC): """This Feature is used to synchronise a certain ammount of channels""" + @abstractmethod def synchronize_channels(self, group_size: int) -> None: """ @@ -26,7 +27,8 @@ def synchronize_channels(self, group_size: int) -> None: class DeviceControl(AWGDeviceFeature, ABC): """This feature is used for basic communication with a AWG""" - #TODO (toCheck): is this Feature ok like this? + + # TODO (toCheck): is this Feature ok like this? @abstractmethod def enable(self) -> None: """This method generates the selected output waveform.""" @@ -54,6 +56,16 @@ def trigger(self) -> None: raise NotImplementedError() +class StatusTable(AWGDeviceFeature, ABC): + def get_status_table(self) -> Dict[str, Union[str, float, int]]: + """ + Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame + + Returns: + An ordered dictionary with the results + """ + raise NotImplementedError() + ######################################################################################################################## # channel tuple features @@ -189,19 +201,18 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: class ActivatableChannels(AWGChannelFeature): @property @abstractmethod - def status(self) -> bool: + def enabled(self) -> bool: """ Returns the the state a channel has at the moment. A channel is either activated or deactivated """ raise NotImplementedError() - @status.setter @abstractmethod - def status(self, channel_status: bool) -> None: - """ - Sets the current state of a channel. - - Args: - channel_status: the new state of a channel - """ + def enable(self): + """Disables the output of a certain channel""" raise NotImplementedError() + + @abstractmethod + def disable(self): + """Enables the output of a certain channel""" + raise NotImplementedError() \ No newline at end of file diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 1473b98c8..17ff6e226 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -13,7 +13,7 @@ from qupulse._program.waveforms import MultiChannelWaveform from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ - ProgramManagement, ActivatableChannels, DeviceControl + ProgramManagement, ActivatableChannels, DeviceControl, StatusTable from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times from qupulse.utils.types import Collection from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel @@ -56,6 +56,7 @@ def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: return guarding_method + def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: """Asserts the channel pair is selcted when the wrapped function is called""" @@ -79,15 +80,13 @@ def __init__(self, device: "TaborDevice"): def synchronize_channels(self, group_size: int) -> None: if group_size == 2: - i = 0 - while i < group_size: + for i in range((int)(len(self._parent().channels) / group_size)): self._parent()._channel_tuples.append( TaborChannelTuple((i + 1), self._parent(), self._parent().channels[(i * group_size):((i * group_size) + group_size)], self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) ) - i = i + 1 else: raise NotImplementedError() @@ -127,6 +126,53 @@ def trigger(self) -> None: self._parent().send_cmd(":TRIG") +class TaborStatusTable(StatusTable): + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = device + + def get_status_table(self) -> Dict[str, Union[str, float, int]]: + """ + Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame + + Returns: + An ordered dictionary with the results + """ + name_query_type_list = [("channel", ":INST:SEL?", int), + ("coupling", ":OUTP:COUP?", str), + ("volt_dc", ":SOUR:VOLT:LEV:AMPL:DC?", float), + ("volt_hv", ":VOLT:HV?", float), + ("offset", ":VOLT:OFFS?", float), + ("outp", ":OUTP?", str), + ("mode", ":SOUR:FUNC:MODE?", str), + ("shape", ":SOUR:FUNC:SHAPE?", str), + ("dc_offset", ":SOUR:DC?", float), + ("freq_rast", ":FREQ:RAST?", float), + + ("gated", ":INIT:GATE?", str), + ("continuous", ":INIT:CONT?", str), + ("continuous_enable", ":INIT:CONT:ENAB?", str), + ("continuous_source", ":INIT:CONT:ENAB:SOUR?", str), + ("marker_source", ":SOUR:MARK:SOUR?", str), + ("seq_jump_event", ":SOUR:SEQ:JUMP:EVEN?", str), + ("seq_adv_mode", ":SOUR:SEQ:ADV?", str), + ("aseq_adv_mode", ":SOUR:ASEQ:ADV?", str), + + ("marker", ":SOUR:MARK:SEL?", int), + ("marker_high", ":MARK:VOLT:HIGH?", str), + ("marker_low", ":MARK:VOLT:LOW?", str), + ("marker_width", ":MARK:WIDT?", int), + ("marker_state", ":MARK:STAT?", str)] + + data = OrderedDict((name, []) for name, *_ in name_query_type_list) + for ch in (1, 2, 3, 4): + self._parent.channels[ch - 1]._select() + self._parent.marker_channels[(ch - 1) % 2]._select() + for name, query, dtype in name_query_type_list: + data[name].append(dtype(self._parent._send_query(query))) + return data + + # Implementation class TaborDevice(AWGDevice): def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, @@ -170,6 +216,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._initialize() # TODO: change for synchronisation-feature + # TODO: auslagern? def is_coupled(self) -> bool: if self._coupled is None: return self._send_query(':INST:COUP:STAT?') == 'ON' @@ -277,47 +324,7 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: assert len(answers) == 0 - def get_status_table(self) -> Dict[str, Union[str, float, int]]: - """ - Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame - - Returns: - An ordered dictionary with the results - """ - name_query_type_list = [("channel", ":INST:SEL?", int), - ("coupling", ":OUTP:COUP?", str), - ("volt_dc", ":SOUR:VOLT:LEV:AMPL:DC?", float), - ("volt_hv", ":VOLT:HV?", float), - ("offset", ":VOLT:OFFS?", float), - ("outp", ":OUTP?", str), - ("mode", ":SOUR:FUNC:MODE?", str), - ("shape", ":SOUR:FUNC:SHAPE?", str), - ("dc_offset", ":SOUR:DC?", float), - ("freq_rast", ":FREQ:RAST?", float), - - ("gated", ":INIT:GATE?", str), - ("continuous", ":INIT:CONT?", str), - ("continuous_enable", ":INIT:CONT:ENAB?", str), - ("continuous_source", ":INIT:CONT:ENAB:SOUR?", str), - ("marker_source", ":SOUR:MARK:SOUR?", str), - ("seq_jump_event", ":SOUR:SEQ:JUMP:EVEN?", str), - ("seq_adv_mode", ":SOUR:SEQ:ADV?", str), - ("aseq_adv_mode", ":SOUR:ASEQ:ADV?", str), - - ("marker", ":SOUR:MARK:SEL?", int), - ("marker_high", ":MARK:VOLT:HIGH?", str), - ("marker_low", ":MARK:VOLT:LOW?", str), - ("marker_width", ":MARK:WIDT?", int), - ("marker_state", ":MARK:STAT?", str)] - - data = OrderedDict((name, []) for name, *_ in name_query_type_list) - for ch in (1, 2, 3, 4): - self.channels[ch - 1]._select() - self.marker_channels[(ch - 1) % 2]._select() - for name, query, dtype in name_query_type_list: - data[name].append(dtype(self._send_query(query))) - return data - + # TODO: auslagern? @property def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover @@ -339,7 +346,7 @@ def _initialize(self) -> None: self.send_cmd(":INST:SEL 3") self.send_cmd(setup_command) - def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: + def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: """ A method to get the first readable device out of all devices. A readable device is a device which you can read data from like a simulator. @@ -372,7 +379,7 @@ def __init__(self, channel: "TaborChannel"): @property def offset(self) -> float: return float( - self._parent.device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent().idn))) + self._parent().device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent().idn))) @offset.setter def offset(self, offset: float) -> None: @@ -380,7 +387,8 @@ def offset(self, offset: float) -> None: @property def amplitude(self) -> float: - coupling = self._parent().device._send_query(":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent().idn)) + coupling = self._parent().device._send_query( + ":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent().idn)) if coupling == "DC": return float(self._parent().device._send_query(":VOLT?")) elif coupling == "HV": @@ -605,7 +613,8 @@ def change_armed_program(self, name: Optional[str]) -> None: # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self._parent().device.send_cmd("SEQ:SEL {}".format(i + 1), paranoia_level=self._parent().internal_paranoia_level) + self._parent().device.send_cmd("SEQ:SEL {}".format(i + 1), + paranoia_level=self._parent().internal_paranoia_level) self._parent().device._download_sequencer_table(sequencer_table) self._parent()._sequencer_tables = sequencer_tables self._parent().device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) @@ -633,6 +642,7 @@ def _exit_config_mode(self): self._parent()._exit_config_mode() +# Implementation class TaborChannelTuple(AWGChannelTuple): CONFIG_MODE_PARANOIA_LEVEL = None @@ -719,10 +729,12 @@ def sample_rate(self) -> float: """Returns the sample rate that the channels of a channel tuple have""" return self.device._send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) + # TODO: auslagern? @property def total_capacity(self) -> int: return int(self.device.dev_properties["max_arb_mem"]) // 2 + # TODO: auslagern? def free_program(self, name: str) -> TaborProgramMemory: if name is None: raise TaborException("Removing 'None' program is forbidden.") @@ -732,20 +744,24 @@ def free_program(self, name: str) -> TaborProgramMemory: self[TaborProgramManagement].change_armed_program(None) return program + # TODO: auslagern? def _restore_program(self, name: str, program: TaborProgram) -> None: if name in self._known_programs: raise ValueError("Program cannot be restored as it is already known.") self._segment_references[program.waveform_to_segment] += 1 self._known_programs[name] = program + # TODO: auslagern? @property def _segment_reserved(self) -> np.ndarray: return self._segment_references > 0 + # TODO: auslagern? @property def _free_points_in_total(self) -> int: return self.total_capacity - np.sum(self._segment_capacity[self._segment_reserved]) + # TODO: auslagern? @property def _free_points_at_end(self) -> int: reserved_index = np.flatnonzero(self._segment_reserved) @@ -756,7 +772,7 @@ def _free_points_at_end(self) -> int: @with_select def read_waveforms(self) -> List[np.ndarray]: - device = self.device.get_readable_device(simulator=True) + device = self.device._get_readable_device(simulator=True) old_segment = device.send_query(":TRAC:SEL?") waveforms = [] @@ -769,9 +785,10 @@ def read_waveforms(self) -> List[np.ndarray]: device.send_cmd(':TRAC:SEL {}'.format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms + # TODO: auslagern? @with_select def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: - device = self.device.get_readable_device(simulator=True) + device = self.device._get_readable_device(simulator=True) old_sequence = device.send_query(":SEQ:SEL?") sequences = [] @@ -782,15 +799,18 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences + # TODO: auslagern? @with_select def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - return self.device.get_readable_device(simulator=True).read_adv_seq_table() + return self.device._get_readable_device(simulator=True).read_adv_seq_table() + # TODO: auslagern? def read_complete_program(self) -> PlottableProgram: return PlottableProgram.from_read_data(self.read_waveforms(), self.read_sequence_tables(), self.read_advanced_sequencer_table()) + # TODO: auslagern? def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ Tuple[np.ndarray, np.ndarray, np.ndarray]: # TODO: comment was not finished @@ -980,6 +1000,7 @@ def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> N cmd_str = ";".join(commands) self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) + # TODO: auslagern? def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters in program memory and device's (adv.) sequence tables if program is current program. @@ -1024,12 +1045,6 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu # Wait until AWG is finished _ = self.device.main_instrument._visa_inst.query('*OPC?') - def set_marker_state(self, marker: int, active: bool) -> None: - pass # TODO: to implement - - def set_channel_state(self, channel, active) -> None: - pass # TODO: to implement - @with_select def _arm(self, name: str) -> None: if self._current_program == name: @@ -1045,7 +1060,6 @@ def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table) def set_program_sequence_table(self, name, new_sequence_table): self._known_programs[name][1]._sequencer_tables = new_sequence_table - @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" @@ -1124,16 +1138,21 @@ def __init__(self, marker_channel: "TaborMarkerChannel"): self._parent = weakref.ref(marker_channel) @property - def status(self) -> bool: - raise NotImplementedError + def enabled(self) -> bool: + pass # TODO: to implement + + def enable(self): + command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" + command_string = command_string.format( + channel=self._parent().channel_tuple.channels[0].idn, + marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) + self._parent().device.send_cmd(command_string) - @status.setter - def status(self, channel_state: bool) -> None: - command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}" + def disable(self): + command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, - marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1, - active="ON" if channel_state else "OFF") + marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) self._parent().device.send_cmd(command_string) @@ -1146,19 +1165,17 @@ def __init__(self, idn: int, device: TaborDevice): # adding Features self.add_feature(TaborMarkerChannelActivatable(self)) - _channel_tuple: "TaborChannelTuple" - @property def device(self) -> TaborDevice: """Returns the device that this marker channel belongs to""" return self._device() @property - def channel_tuple(self) -> Optional[TaborChannelTuple]: + def channel_tuple(self) -> TaborChannelTuple: """Returns the channel tuple that this marker channel belongs to""" return self._channel_tuple() - def _set_channel_tuple(self, channel_tuple) -> None: + def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: """ The channel tuple 'channel_tuple' is assigned to this marker channel From de0378d22d1be501f008fd34ec17c730afc9c0f3 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 15 Apr 2020 14:47:27 +0200 Subject: [PATCH 066/107] Changed samplerate property to TimeType --- qupulse/hardware/awgs/tabor.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 17ff6e226..cdf2d8f1e 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -15,7 +15,7 @@ from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ ProgramManagement, ActivatableChannels, DeviceControl, StatusTable from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times -from qupulse.utils.types import Collection +from qupulse.utils.types import Collection, TimeType from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel from typing import Sequence from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ @@ -725,7 +725,7 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: return self._marker_channels @property - def sample_rate(self) -> float: + def sample_rate(self) -> TimeType: """Returns the sample rate that the channels of a channel tuple have""" return self.device._send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) @@ -1045,26 +1045,12 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu # Wait until AWG is finished _ = self.device.main_instrument._visa_inst.query('*OPC?') - @with_select - def _arm(self, name: str) -> None: - if self._current_program == name: - self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) - else: - self[TaborProgramManagement].change_armed_program(name) - - # arm im Feature - def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table def set_program_sequence_table(self, name, new_sequence_table): self._known_programs[name][1]._sequencer_tables = new_sequence_table - @property - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - return set(program for program in self._known_programs.keys()) - def _enter_config_mode(self) -> None: """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. @@ -1086,7 +1072,7 @@ def _enter_config_mode(self) -> None: marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature # for marker_ch in self.marker_channels: - # marker_ch[TaborMarkerChannelActivatable].status = False + # marker_ch[TaborMarkerChannelActivatable].disable() wf_mode_cmd = ':SOUR:FUNC:MODE FIX' @@ -1100,7 +1086,7 @@ def _exit_config_mode(self) -> None: # TODO: change implementation for channel synchronisation feature - if self.device._send_query(":INST:COUP:STAT?") == "ON": + if self.device.is_coupled(): # Coupled -> switch all channels at once other_channel_tuple: TaborChannelTuple if self.channels == self.device.channel_tuples[0].channels: From d9cfd1dac065af32543a7a33677f54792edb0b65 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 22 Apr 2020 10:56:08 +0200 Subject: [PATCH 067/107] TaborProgramManagement - upload Method fixed --- qupulse/hardware/awgs/tabor.py | 168 +++++++++++++++++++++---------- tests/hardware/awg_base_tests.py | 2 +- 2 files changed, 116 insertions(+), 54 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index cdf2d8f1e..96cc60436 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -31,11 +31,10 @@ __all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] -TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), - ('program', TaborProgram)]) +TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), + ("program", TaborProgram)]) -# TODO: How does this work? def with_configuration_guard(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[ ["TaborChannelTuple"], Any]: """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" @@ -115,7 +114,7 @@ def reset(self) -> None: Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and all channel tuples are cleared. """ - self._parent().send_cmd(':RES') + self._parent().send_cmd(":RES") self._parent()._coupled = None self._parent()._initialize() @@ -216,10 +215,9 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._initialize() # TODO: change for synchronisation-feature - # TODO: auslagern? - def is_coupled(self) -> bool: + def _is_coupled(self) -> bool: if self._coupled is None: - return self._send_query(':INST:COUP:STAT?') == 'ON' + return self._send_query(":INST:COUP:STAT?") == "ON" else: return self._coupled @@ -324,7 +322,6 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: assert len(answers) == 0 - # TODO: auslagern? @property def is_open(self) -> bool: return self._instr.visa_inst is not None # pragma: no cover @@ -403,11 +400,14 @@ def amplitude(self, amplitude: float) -> None: @property def amplitude_offset_handling(self) -> str: - pass # TODO: to implement + return self._parent()._amplitude_offset_handling @amplitude_offset_handling.setter def amplitude_offset_handling(self, amp_offs_handling: str) -> None: - pass # TODO: to implement + if amp_offs_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET or amp_offs_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: + self._parent()._amplitude_offset_handling = amp_offs_handling + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(amp_offs_handling)) class TaborActivatableChannels(ActivatableChannels): @@ -434,25 +434,24 @@ def __init__(self, idn: int, device: TaborDevice): super().__init__(idn) self._device = weakref.ref(device) + self._amplitude_offset_handling = AmplitudeOffsetHandling.IGNORE_OFFSET # adding Features self.add_feature(TaborOffsetAmplitude(self)) - _channel_tuple: "TaborChannelTuple" - @property def device(self) -> TaborDevice: """Returns the device that the channel belongs to""" return self._device() @property - def channel_tuple(self) -> Optional[AWGChannelTuple]: + def channel_tuple(self) -> "TaborChannelTuple": """Returns the channel tuple that this channel belongs to""" return self._channel_tuple() - def _set_channel_tuple(self, channel_tuple) -> None: + def _set_channel_tuple(self, channel_tuple: "TaborChannelTuple") -> None: """ - The channel tuple 'channel_tuple' is assigned to this channel + The channel tuple "channel_tuple" is assigned to this channel Args: channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to @@ -484,8 +483,11 @@ def programs(self) -> Set[str]: @with_configuration_guard @with_select - def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], - markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]], + voltage_transformation: Tuple[Callable, Callable], force: bool = False) -> None: """ Upload a program to the AWG. @@ -493,11 +495,13 @@ def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], The policy is to prefer amending the unknown waveforms to overwriting old ones. """ # TODO: Change this if statements because num methods are deleted + """ if len(channels) != self.num_channels: raise ValueError("Channel ID not specified") if len(markers) != self.num_markers: raise ValueError("Markers not specified") - if len(voltage_transformation) != self.num_channels: + """ + if len(voltage_transformation) != len(self._parent().channels): raise ValueError("Wrong number of voltage transformations") # adjust program to fit criteria @@ -507,6 +511,73 @@ def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], waveform_quantum=16, sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) + if name in self._parent()._known_programs: + if force: + self._parent().free_program(name) + else: + raise ValueError('{} is already known on {}'.format(name, self._parent().idn)) + + # They call the peak to peak range amplitude + # TODO: Are the replacments for a variable size of channel tuples okay? + ranges = [] + for channel in self._parent().channels: + ranges.append(channel[OffsetAmplitude].amplitude) + ranges = tuple(ranges) + + """ + ranges = (self.device.amplitude(self._channels[0]), + self.device.amplitude(self._channels[1])) + """ + + voltage_amplitudes = [] + for range in ranges: + voltage_amplitudes.append(range / 2) + voltage_amplitudes = tuple(voltage_amplitudes) + + """ + voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) + """ + + voltage_offsets = [] + for channel in self._parent().channels: + if channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets.append(0) + elif channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets.append(channel[OffsetAmplitude].offset) + else: + raise ValueError( + '{} is invalid as AWGAmplitudeOffsetHandling'.format(channel._amplitude_offset_handling)) + voltage_offsets = tuple(voltage_offsets) + + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=markers, + device_properties=self._parent().device.dev_properties, + sample_rate=sample_rate / 10 ** 9, + amplitudes=voltage_amplitudes, + offsets=voltage_offsets, + voltage_transformations=voltage_transformation) + + segments, segment_lengths = tabor_program.get_sampled_segments() + + waveform_to_segment, to_amend, to_insert = self._parent()._find_place_for_segments_in_memory(segments, + segment_lengths) + + self._parent()._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 + + for wf_index in np.flatnonzero(to_insert > 0): + segment_index = to_insert[wf_index] + self._parent()._upload_segment(to_insert[wf_index], segments[wf_index]) + waveform_to_segment[wf_index] = segment_index + + if np.any(to_amend): + segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] + waveform_to_segment[to_amend] = self._parent()._amend_segments(segments_to_amend) + + self._parent()._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, + program=tabor_program) + def remove(self, name: str) -> None: """ Remove a program from the AWG. @@ -546,7 +617,7 @@ def clear(self) -> None: @with_select def arm(self, name: Optional[str]) -> None: """ - The program 'name' gets armed + The program "name" gets armed Args: name (str): the program the device should change to @@ -729,12 +800,10 @@ def sample_rate(self) -> TimeType: """Returns the sample rate that the channels of a channel tuple have""" return self.device._send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) - # TODO: auslagern? @property def total_capacity(self) -> int: return int(self.device.dev_properties["max_arb_mem"]) // 2 - # TODO: auslagern? def free_program(self, name: str) -> TaborProgramMemory: if name is None: raise TaborException("Removing 'None' program is forbidden.") @@ -744,24 +813,20 @@ def free_program(self, name: str) -> TaborProgramMemory: self[TaborProgramManagement].change_armed_program(None) return program - # TODO: auslagern? def _restore_program(self, name: str, program: TaborProgram) -> None: if name in self._known_programs: raise ValueError("Program cannot be restored as it is already known.") self._segment_references[program.waveform_to_segment] += 1 self._known_programs[name] = program - # TODO: auslagern? @property def _segment_reserved(self) -> np.ndarray: return self._segment_references > 0 - # TODO: auslagern? @property def _free_points_in_total(self) -> int: return self.total_capacity - np.sum(self._segment_capacity[self._segment_reserved]) - # TODO: auslagern? @property def _free_points_at_end(self) -> int: reserved_index = np.flatnonzero(self._segment_reserved) @@ -780,12 +845,11 @@ def read_waveforms(self) -> List[np.ndarray]: self._segment_references) + 1 for segment in uploaded_waveform_indices: - device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) - device.send_cmd(':TRAC:SEL {}'.format(old_segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms - # TODO: auslagern? @with_select def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: device = self.device._get_readable_device(simulator=True) @@ -794,23 +858,20 @@ def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray] sequences = [] uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: - device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) - device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences - # TODO: auslagern? @with_select def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.device._get_readable_device(simulator=True).read_adv_seq_table() - # TODO: auslagern? def read_complete_program(self) -> PlottableProgram: return PlottableProgram.from_read_data(self.read_waveforms(), self.read_sequence_tables(), self.read_advanced_sequencer_table()) - # TODO: auslagern? def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ Tuple[np.ndarray, np.ndarray, np.ndarray]: # TODO: comment was not finished @@ -912,13 +973,13 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: segment_no = segment_index + 1 - self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), + self.device.send_cmd(":TRAC:DEF {}, {}".format(segment_no, segment.num_points), paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points - self.device.send_cmd(':TRAC:SEL {}'.format(segment_no), paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(":TRAC:SEL {}".format(segment_no), paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) @@ -936,13 +997,13 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: segment_index = len(self._segment_capacity) first_segment_number = segment_index + 1 - self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len), + self.device.send_cmd(":TRAC:DEF {},{}".format(first_segment_number, trac_len), paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number), + self.device.send_cmd(":TRAC:SEL {}".format(first_segment_number), paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB', + self.device.send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) - self.device._send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) + self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) @@ -952,7 +1013,7 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: if len(segments) < old_to_update: for i, segment in enumerate(segments): current_segment_number = first_segment_number + i - self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points), + self.device.send_cmd(":TRAC:DEF {},{}".format(current_segment_number, segment.num_points), paranoia_level=self.internal_paranoia_level) else: # flush the capacity @@ -1000,7 +1061,6 @@ def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> N cmd_str = ";".join(commands) self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) - # TODO: auslagern? def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters in program memory and device's (adv.) sequence tables if program is current program. @@ -1029,7 +1089,7 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu for position, entry in modifications.items(): if not entry.repetition_count > 0: - raise ValueError('Repetition must be > 0') + raise ValueError("Repetition must be > 0") if isinstance(position, int): commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, @@ -1043,7 +1103,7 @@ def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, nu self._execute_multiple_commands_with_config_guard(commands) # Wait until AWG is finished - _ = self.device.main_instrument._visa_inst.query('*OPC?') + _ = self.device.main_instrument._visa_inst.query("*OPC?") def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table @@ -1052,31 +1112,33 @@ def set_program_sequence_table(self, name, new_sequence_table): self._known_programs[name][1]._sequencer_tables = new_sequence_table def _enter_config_mode(self) -> None: - """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the + """ + Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. - When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip.""" + When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip. + """ if self._is_in_config_mode is False: # 1. Selct channel pair # 2. Select DC as function shape # 3. Select build-in waveform mode - if self.device.is_coupled(): - out_cmd = ':OUTP:ALL OFF' + if self.device._is_coupled(): + out_cmd = ":OUTP:ALL OFF" else: out_cmd = "" for channel in self.channels: out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=channel.idn) - marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature # for marker_ch in self.marker_channels: # marker_ch[TaborMarkerChannelActivatable].disable() - wf_mode_cmd = ':SOUR:FUNC:MODE FIX' + wf_mode_cmd = ":SOUR:FUNC:MODE FIX" - cmd = ';'.join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + cmd = ";".join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = True @@ -1086,7 +1148,7 @@ def _exit_config_mode(self) -> None: # TODO: change implementation for channel synchronisation feature - if self.device.is_coupled(): + if self.device._is_coupled(): # Coupled -> switch all channels at once other_channel_tuple: TaborChannelTuple if self.channels == self.device.channel_tuples[0].channels: diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index aaceb6a96..d8c4e224a 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -58,7 +58,7 @@ def amplitude_offset_handling(self) -> str: return self._parent._ampl_offs_handling @amplitude_offset_handling.setter - def amplitude_offset_handling(self, ampl_offs_handling: float) -> None: + def amplitude_offset_handling(self, ampl_offs_handling: str) -> None: """Set amplitude-offset-handling of TestAWGChannel""" self._parent._ampl_offs_handling = ampl_offs_handling From 5c4e6c3aa748b26f79bdd0d57364fe0d19d9383d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 22 Apr 2020 15:09:27 +0200 Subject: [PATCH 068/107] Added a few docstrings --- qupulse/hardware/awgs/features.py | 4 ++-- qupulse/hardware/awgs/tabor.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 23e35d70a..990e67cc2 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -209,10 +209,10 @@ def enabled(self) -> bool: @abstractmethod def enable(self): - """Disables the output of a certain channel""" + """Enables the output of a certain channel""" raise NotImplementedError() @abstractmethod def disable(self): - """Enables the output of a certain channel""" + """Disables the output of a certain channel""" raise NotImplementedError() \ No newline at end of file diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 96cc60436..625050ea6 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -78,6 +78,13 @@ def __init__(self, device: "TaborDevice"): self._parent = weakref.ref(device) def synchronize_channels(self, group_size: int) -> None: + """ + Synchronize in groups of `group_size` channels. Groups of synchronized channels will be provided as + AWGChannelTuples. The channel_size must be evenly dividable by the number of channels + + Args: + group_size: Number of channels per channel tuple + """ if group_size == 2: for i in range((int)(len(self._parent().channels) / group_size)): self._parent()._channel_tuples.append( @@ -122,6 +129,10 @@ def reset(self) -> None: channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: + """ + This method triggers a device remotely + """ + # TODO: comment is missing self._parent().send_cmd(":TRIG") @@ -216,6 +227,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._initialize() # TODO: change for synchronisation-feature def _is_coupled(self) -> bool: + # TODO: comment is missing if self._coupled is None: return self._send_query(":INST:COUP:STAT?") == "ON" else: @@ -228,14 +240,17 @@ def cleanup(self) -> None: @property def channels(self) -> Collection["TaborChannel"]: + """Returns a list of all channels of a Device""" return self._channels @property def marker_channels(self) -> Collection["TaborMarkerChannel"]: + """Returns a list of all marker channels of a device. The collection may be empty""" return self._channel_marker @property def channel_tuples(self) -> Collection["TaborChannelTuple"]: + """Returns a list of all channel tuples of a list""" return self._channel_tuples @property @@ -375,15 +390,18 @@ def __init__(self, channel: "TaborChannel"): @property def offset(self) -> float: + """Get offset of AWG channel""" return float( self._parent().device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent().idn))) @offset.setter def offset(self, offset: float) -> None: + """Set offset for AWG channel""" pass # TODO: to implement @property def amplitude(self) -> float: + """Get amplitude of AWG channel""" coupling = self._parent().device._send_query( ":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent().idn)) if coupling == "DC": @@ -395,15 +413,23 @@ def amplitude(self) -> float: @amplitude.setter def amplitude(self, amplitude: float) -> None: + """Set amplitude for AWG channel""" self._parent().device.send_cmd(":INST:SEL {channel}; :OUTP:COUP DC".format(channel=self._parent().idn)) self._parent().device.send_cmd(":VOLT {amp}".format(amp=amplitude)) @property def amplitude_offset_handling(self) -> str: + """ + Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and + offset settings are constant or if these should be optimized by the driver + """ return self._parent()._amplitude_offset_handling @amplitude_offset_handling.setter def amplitude_offset_handling(self, amp_offs_handling: str) -> None: + """ + amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` + """ if amp_offs_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET or amp_offs_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: self._parent()._amplitude_offset_handling = amp_offs_handling else: @@ -417,13 +443,18 @@ def __init__(self, marker_channel: "TaborMarkerChannel"): @property def enabled(self) -> bool: + """ + Returns the the state a channel has at the moment. A channel is either activated or deactivated + """ pass # TODO: to implement def enable(self): + """Enables the output of a certain channel""" command_string = ":INST:SEL {ch_id}; :OUTP ON".format(ch_id=self._parent().idn) self._parent().device.send_cmd(command_string) def disable(self): + """Disables the output of a certain channel""" command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self._parent().idn) self._parent().device.send_cmd(command_string) From 30bdd8ee32c40110f1d5ef68c2d3aecaa64f9396 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 23 Apr 2020 10:34:07 +0200 Subject: [PATCH 069/107] Worked on some docstrings A few methods have been improved --- qupulse/hardware/awgs/features.py | 4 +- qupulse/hardware/awgs/tabor.py | 91 ++++++++++++++++++------------- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 990e67cc2..94a52fd14 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -49,9 +49,8 @@ def reset(self) -> None: @abstractmethod def trigger(self) -> None: - # TODO (toDo): Docstring missing """ - + This method triggers a device remotely. """ raise NotImplementedError() @@ -141,6 +140,7 @@ def run_current_program(self) -> None: @abstractmethod def change_armed_program(self) -> None: + """The armed program is changed to the program with the name 'name'""" raise NotImplementedError() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 625050ea6..8f1842c12 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -73,6 +73,8 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: # Features class TaborChannelSynchronization(ChannelSynchronization): + """This Feature is used to synchronise a certain ammount of channels""" + def __init__(self, device: "TaborDevice"): super().__init__() self._parent = weakref.ref(device) @@ -86,6 +88,7 @@ def synchronize_channels(self, group_size: int) -> None: group_size: Number of channels per channel tuple """ if group_size == 2: + self._parent()._channel_tuples = [] for i in range((int)(len(self._parent().channels) / group_size)): self._parent()._channel_tuples.append( TaborChannelTuple((i + 1), @@ -98,6 +101,8 @@ def synchronize_channels(self, group_size: int) -> None: class TaborDeviceControl(DeviceControl): + """This feature is used for basic communication with a AWG""" + def __init__(self, device: "TaborDevice"): super().__init__() self._parent = weakref.ref(device) @@ -130,7 +135,7 @@ def reset(self) -> None: def trigger(self) -> None: """ - This method triggers a device remotely + This method triggers a device remotely. """ # TODO: comment is missing self._parent().send_cmd(":TRIG") @@ -261,6 +266,10 @@ def main_instrument(self) -> teawg.TEWXAwg: def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: return self._mirrors + @property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr,) + self._mirrors + @property def paranoia_level(self) -> int: return self._instr.paranoia_level @@ -274,10 +283,6 @@ def paranoia_level(self, val): def dev_properties(self) -> dict: return self._instr.dev_properties - @property - def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr,) + self._mirrors - def send_cmd(self, cmd_str, paranoia_level=None): # TODO (LuL): This function should be private for instr in self.all_devices: @@ -389,10 +394,11 @@ def __init__(self, channel: "TaborChannel"): self._parent = weakref.ref(channel) @property + @with_select def offset(self) -> float: """Get offset of AWG channel""" return float( - self._parent().device._send_query(":INST:SEL {channel}; :VOLT:OFFS?".format(channel=self._parent().idn))) + self._parent().device._send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) @offset.setter def offset(self, offset: float) -> None: @@ -400,10 +406,10 @@ def offset(self, offset: float) -> None: pass # TODO: to implement @property + @with_select def amplitude(self) -> float: """Get amplitude of AWG channel""" - coupling = self._parent().device._send_query( - ":INST:SEL {channel}; :OUTP:COUP?".format(channel=self._parent().idn)) + coupling = self._parent().device._send_query(":OUTP:COUP?") if coupling == "DC": return float(self._parent().device._send_query(":VOLT?")) elif coupling == "HV": @@ -412,9 +418,10 @@ def amplitude(self) -> float: raise TaborException("Unknown coupling: {}".format(coupling)) @amplitude.setter + @with_select def amplitude(self, amplitude: float) -> None: """Set amplitude for AWG channel""" - self._parent().device.send_cmd(":INST:SEL {channel}; :OUTP:COUP DC".format(channel=self._parent().idn)) + self._parent().device.send_cmd(":OUTP:COUP DC".format(channel=self._parent().idn)) self._parent().device.send_cmd(":VOLT {amp}".format(amp=amplitude)) @property @@ -435,6 +442,9 @@ def amplitude_offset_handling(self, amp_offs_handling: str) -> None: else: raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(amp_offs_handling)) + def _select(self) -> None: + self._parent()._select() + class TaborActivatableChannels(ActivatableChannels): def __init__(self, marker_channel: "TaborMarkerChannel"): @@ -448,16 +458,21 @@ def enabled(self) -> bool: """ pass # TODO: to implement + @with_select def enable(self): """Enables the output of a certain channel""" - command_string = ":INST:SEL {ch_id}; :OUTP ON".format(ch_id=self._parent().idn) + command_string = ":OUTP ON".format(ch_id=self._parent().idn) self._parent().device.send_cmd(command_string) + @with_select def disable(self): """Disables the output of a certain channel""" - command_string = ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=self._parent().idn) + command_string = ":OUTP OFF".format(ch_id=self._parent().idn) self._parent().device.send_cmd(command_string) + def _select(self) -> None: + self._parent()._select() + # Implementation class TaborChannel(AWGChannel): @@ -469,6 +484,7 @@ def __init__(self, idn: int, device: TaborDevice): # adding Features self.add_feature(TaborOffsetAmplitude(self)) + self.add_feature(TaborActivatableChannels(self)) @property def device(self) -> TaborDevice: @@ -507,11 +523,6 @@ def __init__(self, channel_tuple: "TaborChannelTuple", ): self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] - @property - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - return set(program.name for program in self._parent()._known_programs.keys()) - @with_configuration_guard @with_select def upload(self, name: str, @@ -550,24 +561,10 @@ def upload(self, name: str, # They call the peak to peak range amplitude # TODO: Are the replacments for a variable size of channel tuples okay? - ranges = [] - for channel in self._parent().channels: - ranges.append(channel[OffsetAmplitude].amplitude) - ranges = tuple(ranges) - """ - ranges = (self.device.amplitude(self._channels[0]), - self.device.amplitude(self._channels[1])) - """ + ranges = tuple(ch[OffsetAmplitude].amplitude for ch in self._parent().channels) - voltage_amplitudes = [] - for range in ranges: - voltage_amplitudes.append(range / 2) - voltage_amplitudes = tuple(voltage_amplitudes) - - """ - voltage_amplitudes = (ranges[0] / 2, ranges[1] / 2) - """ + voltage_amplitudes = tuple(range / 2 for range in ranges) voltage_offsets = [] for channel in self._parent().channels: @@ -622,7 +619,11 @@ def remove(self, name: str) -> None: self._parent().cleanup() def clear(self) -> None: - """Delete all segments and clear memory""" + """ + Removes all programs and waveforms from the AWG. + + Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! + """ self._parent().device.channels[0]._select() self._parent().device.send_cmd(":TRAC:DEL:ALL") @@ -647,8 +648,9 @@ def clear(self) -> None: @with_select def arm(self, name: Optional[str]) -> None: + # TODO: does dearm work? """ - The program "name" gets armed + Load the program 'name' and arm the device for running it. Args: name (str): the program the device should change to @@ -658,6 +660,11 @@ def arm(self, name: Optional[str]) -> None: else: self.change_armed_program(name) + @property + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program.name for program in self._parent()._known_programs.keys()) + @with_select def run_current_program(self) -> None: """ @@ -674,7 +681,7 @@ def run_current_program(self) -> None: @with_select @with_configuration_guard def change_armed_program(self, name: Optional[str]) -> None: - """The armed program of the channel tuple is change to the program with the name 'name'""" + """The armed program of the channel tuple is changed to the program with the name 'name'""" if name is None: sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] @@ -827,9 +834,10 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: return self._marker_channels @property + @with_select def sample_rate(self) -> TimeType: """Returns the sample rate that the channels of a channel tuple have""" - return self.device._send_query(":INST:SEL {channel}; :FREQ:RAST?".format(channel=self.channels[0].idn)) + return self.device._send_query(":FREQ:RAST?".format(channel=self.channels[0].idn)) @property def total_capacity(self) -> int: @@ -1198,7 +1206,7 @@ def _exit_config_mode(self) -> None: cmd = "" for channel in self.channels: - cmd = cmd + ":INST:SEL {}; :OUTP ON;".format(channel.idn) + channel[ActivatableChannels].enable() self.device.send_cmd(cmd[:-1]) for marker_ch in self.marker_channels: @@ -1220,20 +1228,25 @@ def __init__(self, marker_channel: "TaborMarkerChannel"): def enabled(self) -> bool: pass # TODO: to implement + @with_select def enable(self): - command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" + command_string = ":SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) self._parent().device.send_cmd(command_string) + @with_select def disable(self): - command_string = ":INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" + command_string = ":SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) self._parent().device.send_cmd(command_string) + def _select(self) -> None: + self._parent()._select() + # Implementation class TaborMarkerChannel(AWGMarkerChannel): From affc17abdd06786e6e47c9f9214a143a1e2612ef Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 19 May 2020 15:50:18 +0200 Subject: [PATCH 070/107] -removed the setter for amplitude and offset -first try of the synchronisation feature with 4 channel -changed some methods to private -minor changes and the removal of some methods of the ProgramManagement, TaborOffsetAmplitude, StatusTable and DeviceControll Feature --- qupulse/hardware/awgs/features.py | 56 ++- qupulse/hardware/awgs/tabor.py | 346 +++++++++--------- tests/hardware/awg_base_tests.py | 18 +- tests/hardware/tabor_simulator_based_tests.py | 20 +- 4 files changed, 221 insertions(+), 219 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 94a52fd14..23c865e6a 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -9,6 +9,15 @@ ######################################################################################################################## # device features ######################################################################################################################## +class SCPI(AWGDeviceFeature, ABC): + @abstractmethod + def send_cmd(self, cmd_str): + pass + + @abstractmethod + def send_query(self, query_str): + pass + class ChannelSynchronization(AWGDeviceFeature, ABC): """This Feature is used to synchronise a certain ammount of channels""" @@ -28,17 +37,6 @@ def synchronize_channels(self, group_size: int) -> None: class DeviceControl(AWGDeviceFeature, ABC): """This feature is used for basic communication with a AWG""" - # TODO (toCheck): is this Feature ok like this? - @abstractmethod - def enable(self) -> None: - """This method generates the selected output waveform.""" - raise NotImplementedError() - - @abstractmethod - def abort(self) -> None: - """This method terminates the current generation of the output waveform.""" - raise NotImplementedError() - @abstractmethod def reset(self) -> None: """ @@ -70,6 +68,16 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: # channel tuple features ######################################################################################################################## +class ReadProgram(AWGChannelTupleFeature, ABC): + @abstractmethod + def read_complete_program(self): + pass + +class VolatileParameters(AWGChannelTupleFeature, ABC): + @abstractmethod + def set_volatile_parameters(self, program_name, parameters) -> None: + raise NotImplementedError() + class ProgramManagement(AWGChannelTupleFeature, ABC): @abstractmethod def upload(self, name: str, @@ -138,12 +146,6 @@ def run_current_program(self) -> None: """This method starts running the active program""" raise NotImplementedError() - @abstractmethod - def change_armed_program(self) -> None: - """The armed program is changed to the program with the name 'name'""" - raise NotImplementedError() - - ######################################################################################################################## # channel features ######################################################################################################################## @@ -155,31 +157,19 @@ class AmplitudeOffsetHandling: _valid = (IGNORE_OFFSET, CONSIDER_OFFSET) -class OffsetAmplitude(AWGChannelFeature): +class VoltageRange(AWGChannelFeature): @property @abstractmethod def offset(self) -> float: """Get offset of AWG channel""" raise NotImplementedError() - @offset.setter - @abstractmethod - def offset(self, offset: float) -> None: - """Set offset for AWG channel""" - raise NotImplementedError() - @property @abstractmethod def amplitude(self) -> float: """Get amplitude of AWG channel""" raise NotImplementedError() - @amplitude.setter - @abstractmethod - def amplitude(self, amplitude: float) -> None: - """Set amplitude for AWG channel""" - raise NotImplementedError() - @property @abstractmethod def amplitude_offset_handling(self) -> str: @@ -215,4 +205,8 @@ def enable(self): @abstractmethod def disable(self): """Disables the output of a certain channel""" - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() + + +class RepetionMode(AWGChannelFeature): + pass \ No newline at end of file diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 8f1842c12..d71ad7819 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,19 +1,17 @@ -import fractions import functools import logging import numbers import sys import weakref -from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple, \ - Generator, Iterable +from typing import List, Tuple, Set, Callable, Optional, Any, cast, Union, Dict, Mapping, NamedTuple, Iterable from collections import OrderedDict import numpy as np from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible -from qupulse._program.waveforms import MultiChannelWaveform from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter -from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, OffsetAmplitude, \ - ProgramManagement, ActivatableChannels, DeviceControl, StatusTable +from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, VoltageRange, \ + ProgramManagement, ActivatableChannels, DeviceControl, StatusTable, SCPI, RepetionMode, VolatileParameters, \ + ReadProgram from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times from qupulse.utils.types import Collection, TimeType from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel @@ -70,7 +68,22 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: ######################################################################################################################## # Device ######################################################################################################################## -# Features +# FeaturesBes +class TaborSCPI(SCPI): + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = weakref.ref(device) + + def send_cmd(self, cmd_str, paranoia_level=None): + for instr in self._parent().all_devices: + instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) + + def send_query(self, query_str, query_mirrors=False) -> Any: + if query_mirrors: + return tuple(instr.send_query(query_str) for instr in self.all_devices) + else: + return self._parent().main_instrument.send_query(query_str) + class TaborChannelSynchronization(ChannelSynchronization): """This Feature is used to synchronise a certain ammount of channels""" @@ -96,8 +109,15 @@ def synchronize_channels(self, group_size: int) -> None: self._parent().channels[(i * group_size):((i * group_size) + group_size)], self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) ) + self._parent()[SCPI].send_cmd(":INST:COUP:STAT OFF") + elif group_size == 4: + self._parent()._channel_tuples = [TaborChannelTuple(1, + self._parent(), + self._parent().channels, + self._parent().marker_channels)] + self._parent()[SCPI].send_cmd(":INST:COUP:STAT ON") else: - raise NotImplementedError() + raise TaborException("Invalid group size") class TaborDeviceControl(DeviceControl): @@ -107,26 +127,12 @@ def __init__(self, device: "TaborDevice"): super().__init__() self._parent = weakref.ref(device) - def enable(self) -> None: - """ - This method immediately generates the selected output waveform, if the device is in continuous and armed - run mode. - """ - self._parent().send_cmd(":ENAB") - - def abort(self) -> None: - """ - With abort you can terminate the current generation of the output waveform. When the output waveform is - terminated the output starts generating an idle waveform. - """ - self._parent().send_cmd(":ABOR") - def reset(self) -> None: """ Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and all channel tuples are cleared. """ - self._parent().send_cmd(":RES") + self._parent()[SCPI].send_cmd(":RES") self._parent()._coupled = None self._parent()._initialize() @@ -137,8 +143,7 @@ def trigger(self) -> None: """ This method triggers a device remotely. """ - # TODO: comment is missing - self._parent().send_cmd(":TRIG") + self._parent()[SCPI].send_cmd(":TRIG") class TaborStatusTable(StatusTable): @@ -184,7 +189,7 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: self._parent.channels[ch - 1]._select() self._parent.marker_channels[(ch - 1) % 2]._select() for name, query, dtype in name_query_type_list: - data[name].append(dtype(self._parent._send_query(query))) + data[name].append(dtype(self._parent[SCPI].send_query(query))) return data @@ -210,8 +215,12 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._coupled = None self._clock_marker = [0, 0, 0, 0] + self.add_feature(TaborSCPI(self)) + self.add_feature(TaborDeviceControl(self)) + self.add_feature(TaborStatusTable(self)) + if reset: - self.send_cmd(":RES") + self[SCPI].send_cmd(":RES") # Channel self._channels = [TaborChannel(i + 1, self) for i in range(4)] @@ -219,22 +228,35 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # ChannelMarker self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] + self._initialize() # TODO: change for synchronisation-feature + # ChannelTuple self._channel_tuples = [] - self.add_feature(TaborDeviceControl(self)) self.add_feature(TaborChannelSynchronization(self)) self[TaborChannelSynchronization].synchronize_channels(2) if external_trigger: raise NotImplementedError() # pragma: no cover - self._initialize() # TODO: change for synchronisation-feature + def enable(self) -> None: + """ + This method immediately generates the selected output waveform, if the device is in continuous and armed + run mode. + """ + self[SCPI].send_cmd(":ENAB") + + def abort(self) -> None: + """ + With abort you can terminate the current generation of the output waveform. When the output waveform is + terminated the output starts generating an idle waveform. + """ + self[SCPI].send_cmd(":ABOR") def _is_coupled(self) -> bool: # TODO: comment is missing if self._coupled is None: - return self._send_query(":INST:COUP:STAT?") == "ON" + return self[SCPI].send_query(":INST:COUP:STAT?") == "ON" else: return self._coupled @@ -271,11 +293,11 @@ def all_devices(self) -> Sequence[teawg.TEWXAwg]: return (self._instr,) + self._mirrors @property - def paranoia_level(self) -> int: + def _paranoia_level(self) -> int: return self._instr.paranoia_level - @paranoia_level.setter - def paranoia_level(self, val): + @_paranoia_level.setter + def _paranoia_level(self, val): for instr in self.all_devices: instr.paranoia_level = val @@ -284,16 +306,9 @@ def dev_properties(self) -> dict: return self._instr.dev_properties def send_cmd(self, cmd_str, paranoia_level=None): - # TODO (LuL): This function should be private for instr in self.all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) - def _send_query(self, query_str, query_mirrors=False) -> Any: - if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self.all_devices) - else: - return self._instr.send_query(query_str) - def _send_binary_data(self, pref, bin_dat, paranoia_level=None): for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) @@ -342,10 +357,6 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: assert len(answers) == 0 - @property - def is_open(self) -> bool: - return self._instr.visa_inst is not None # pragma: no cover - def _initialize(self) -> None: # 1. Select channel # 2. Turn off gated mode @@ -358,10 +369,10 @@ def _initialize(self) -> None: ":INIT:GATE OFF; :INIT:CONT ON; " ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") - self.send_cmd(":INST:SEL 1") - self.send_cmd(setup_command) - self.send_cmd(":INST:SEL 3") - self.send_cmd(setup_command) + self[SCPI].send_cmd(":INST:SEL 1") + self[SCPI].send_cmd(setup_command) + self[SCPI].send_cmd(":INST:SEL 3") + self[SCPI].send_cmd(setup_command) def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: """ @@ -388,7 +399,7 @@ def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: # Channel ######################################################################################################################## # Features -class TaborOffsetAmplitude(OffsetAmplitude): +class TaborVoltageRange(VoltageRange): def __init__(self, channel: "TaborChannel"): super().__init__() self._parent = weakref.ref(channel) @@ -398,32 +409,20 @@ def __init__(self, channel: "TaborChannel"): def offset(self) -> float: """Get offset of AWG channel""" return float( - self._parent().device._send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) - - @offset.setter - def offset(self, offset: float) -> None: - """Set offset for AWG channel""" - pass # TODO: to implement + self._parent().device[SCPI].send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) @property @with_select def amplitude(self) -> float: """Get amplitude of AWG channel""" - coupling = self._parent().device._send_query(":OUTP:COUP?") + coupling = self._parent().device[SCPI].send_query(":OUTP:COUP?") if coupling == "DC": - return float(self._parent().device._send_query(":VOLT?")) + return float(self._parent().device[SCPI].send_query(":VOLT?")) elif coupling == "HV": - return float(self._parent().device._send_query(":VOLT:HV?")) + return float(self._parent().device[SCPI].send_query(":VOLT:HV?")) else: raise TaborException("Unknown coupling: {}".format(coupling)) - @amplitude.setter - @with_select - def amplitude(self, amplitude: float) -> None: - """Set amplitude for AWG channel""" - self._parent().device.send_cmd(":OUTP:COUP DC".format(channel=self._parent().idn)) - self._parent().device.send_cmd(":VOLT {amp}".format(amp=amplitude)) - @property def amplitude_offset_handling(self) -> str: """ @@ -447,9 +446,9 @@ def _select(self) -> None: class TaborActivatableChannels(ActivatableChannels): - def __init__(self, marker_channel: "TaborMarkerChannel"): + def __init__(self, channel: "TaborChannel"): super().__init__() - self._parent = weakref.ref(marker_channel) + self._parent = weakref.ref(channel) @property def enabled(self) -> bool: @@ -462,18 +461,23 @@ def enabled(self) -> bool: def enable(self): """Enables the output of a certain channel""" command_string = ":OUTP ON".format(ch_id=self._parent().idn) - self._parent().device.send_cmd(command_string) + self._parent().device[SCPI].send_cmd(command_string) @with_select def disable(self): """Disables the output of a certain channel""" command_string = ":OUTP OFF".format(ch_id=self._parent().idn) - self._parent().device.send_cmd(command_string) + self._parent().device[SCPI].send_cmd(command_string) def _select(self) -> None: self._parent()._select() +class TaborRepetionMode(RepetionMode): + # TODO: fill with functionalty + pass + + # Implementation class TaborChannel(AWGChannel): def __init__(self, idn: int, device: TaborDevice): @@ -483,7 +487,7 @@ def __init__(self, idn: int, device: TaborDevice): self._amplitude_offset_handling = AmplitudeOffsetHandling.IGNORE_OFFSET # adding Features - self.add_feature(TaborOffsetAmplitude(self)) + self.add_feature(TaborVoltageRange(self)) self.add_feature(TaborActivatableChannels(self)) @property @@ -536,13 +540,7 @@ def upload(self, name: str, The policy is to prefer amending the unknown waveforms to overwriting old ones. """ - # TODO: Change this if statements because num methods are deleted - """ - if len(channels) != self.num_channels: - raise ValueError("Channel ID not specified") - if len(markers) != self.num_markers: - raise ValueError("Markers not specified") - """ + if len(voltage_transformation) != len(self._parent().channels): raise ValueError("Wrong number of voltage transformations") @@ -551,7 +549,7 @@ def upload(self, name: str, make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10 ** 9)) + sample_rate=sample_rate / 10 ** 9) if name in self._parent()._known_programs: if force: @@ -562,7 +560,7 @@ def upload(self, name: str, # They call the peak to peak range amplitude # TODO: Are the replacments for a variable size of channel tuples okay? - ranges = tuple(ch[OffsetAmplitude].amplitude for ch in self._parent().channels) + ranges = tuple(ch[VoltageRange].amplitude for ch in self._parent().channels) voltage_amplitudes = tuple(range / 2 for range in ranges) @@ -571,7 +569,7 @@ def upload(self, name: str, if channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets.append(0) elif channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets.append(channel[OffsetAmplitude].offset) + voltage_offsets.append(channel[VoltageRange].offset) else: raise ValueError( '{} is invalid as AWGAmplitudeOffsetHandling'.format(channel._amplitude_offset_handling)) @@ -644,7 +642,7 @@ def clear(self) -> None: self._parent()._sequencer_tables = [] self._parent()._known_programs = dict() - self.change_armed_program(None) + self._change_armed_program(None) @with_select def arm(self, name: Optional[str]) -> None: @@ -658,7 +656,7 @@ def arm(self, name: Optional[str]) -> None: if self._parent()._current_program == name: self._parent().device.send_cmd("SEQ:SEL 1") else: - self.change_armed_program(name) + self._change_armed_program(name) @property def programs(self) -> Set[str]: @@ -680,7 +678,7 @@ def run_current_program(self) -> None: @with_select @with_configuration_guard - def change_armed_program(self, name: Optional[str]) -> None: + def _change_armed_program(self, name: Optional[str]) -> None: """The armed program of the channel tuple is changed to the program with the name 'name'""" if name is None: sequencer_tables = [self._idle_sequence_table] @@ -751,6 +749,68 @@ def _exit_config_mode(self): self._parent()._exit_config_mode() +class TaborVolatileParameters(VolatileParameters): + def __init__(self, channel_tuple: "TaborChannelTuple", ): + super().__init__() + self._parent = weakref.ref(channel_tuple) + + def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: + """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters + in program memory and device's (adv.) sequence tables if program is current program. + + If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to + enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) + and set device._is_coupled. + + Args: + program_name: Name of program which should be changed. + parameters: Names of volatile parameters and respective values to which they should be set. + """ + waveform_to_segment_index, program = self._parent()._known_programs[program_name] + modifications = program.update_volatile_parameters(parameters) + + self._parent().logger.debug("parameter modifications: %r" % modifications) + + if not modifications: + self._parent().logger.info( + "There are no volatile parameters to update. Either there are no volatile parameters with " + "these names,\nthe respective repetition counts already have the given values or the " + "volatile parameters were dropped during upload.") + return + + if program_name == self._parent()._current_program: + commands = [] + + for position, entry in modifications.items(): + if not entry.repetition_count > 0: + raise ValueError("Repetition must be > 0") + + if isinstance(position, int): + commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, + entry.repetition_count, entry.jump_flag)) + else: + table_num, step_num = position + commands.append(":SEQ:SEL {}".format(table_num + 2)) + commands.append(":SEQ:DEF {},{},{},{}".format(step_num, + waveform_to_segment_index[entry.element_id] + 1, + entry.repetition_count, entry.jump_flag)) + self._parent()._execute_multiple_commands_with_config_guard(commands) + + # Wait until AWG is finished + _ = self._parent().device.main_instrument._visa_inst.query("*OPC?") + + +class TaborReadProgram(ReadProgram): + def __init__(self, channel_tuple: "TaborChannelTuple", ): + super().__init__() + self._parent = weakref.ref(channel_tuple) + + def read_complete_program(self): + return PlottableProgram.from_read_data(self._parent().read_waveforms(), + self._parent().read_sequence_tables(), + self._parent().read_advanced_sequencer_table()) + + # Implementation class TaborChannelTuple(AWGChannelTuple): CONFIG_MODE_PARANOIA_LEVEL = None @@ -774,6 +834,7 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann # adding Features self.add_feature(TaborProgramManagement(self)) + self.add_feature(TaborVolatileParameters(self)) self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, @@ -837,7 +898,8 @@ def marker_channels(self) -> Collection["TaborMarkerChannel"]: @with_select def sample_rate(self) -> TimeType: """Returns the sample rate that the channels of a channel tuple have""" - return self.device._send_query(":FREQ:RAST?".format(channel=self.channels[0].idn)) + return TimeType.from_float( + float(self.device[SCPI].send_query(":FREQ:RAST?".format(channel=self.channels[0].idn)))) @property def total_capacity(self) -> int: @@ -849,7 +911,7 @@ def free_program(self, name: str) -> TaborProgramMemory: program = self._known_programs.pop(name) self._segment_references[program.waveform_to_segment] -= 1 if self._current_program == name: - self[TaborProgramManagement].change_armed_program(None) + self[TaborProgramManagement]._change_armed_program(None) return program def _restore_program(self, name: str, program: TaborProgram) -> None: @@ -1100,50 +1162,6 @@ def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> N cmd_str = ";".join(commands) self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) - def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: - """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters - in program memory and device's (adv.) sequence tables if program is current program. - - If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to - enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) - and set device._is_coupled. - - Args: - program_name: Name of program which should be changed. - parameters: Names of volatile parameters and respective values to which they should be set. - """ - waveform_to_segment_index, program = self._known_programs[program_name] - modifications = program.update_volatile_parameters(parameters) - - self.logger.debug("parameter modifications: %r" % modifications) - - if not modifications: - self.logger.info("There are no volatile parameters to update. Either there are no volatile parameters with " - "these names,\nthe respective repetition counts already have the given values or the " - "volatile parameters were dropped during upload.") - return - - if program_name == self._current_program: - commands = [] - - for position, entry in modifications.items(): - if not entry.repetition_count > 0: - raise ValueError("Repetition must be > 0") - - if isinstance(position, int): - commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, - entry.repetition_count, entry.jump_flag)) - else: - table_num, step_num = position - commands.append(":SEQ:SEL {}".format(table_num + 2)) - commands.append(":SEQ:DEF {},{},{},{}".format(step_num, - waveform_to_segment_index[entry.element_id] + 1, - entry.repetition_count, entry.jump_flag)) - self._execute_multiple_commands_with_config_guard(commands) - - # Wait until AWG is finished - _ = self.device.main_instrument._visa_inst.query("*OPC?") - def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table @@ -1156,30 +1174,32 @@ def _enter_config_mode(self) -> None: sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip. """ - if self._is_in_config_mode is False: + for num in range(0, int(len(self.channels) / 2)): + if self._is_in_config_mode is False: - # 1. Selct channel pair - # 2. Select DC as function shape - # 3. Select build-in waveform mode + # 1. Selct channel pair + # 2. Select DC as function shape + # 3. Select build-in waveform mode - if self.device._is_coupled(): - out_cmd = ":OUTP:ALL OFF" - else: - out_cmd = "" - for channel in self.channels: - out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF".format(ch_id=channel.idn) + if self.device._is_coupled(): + out_cmd = ":OUTP:ALL OFF;" + else: + out_cmd = "" + for channel in self.channels[num*2:num*2+2]: + out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) - marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature - # for marker_ch in self.marker_channels: - # marker_ch[TaborMarkerChannelActivatable].disable() + marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature + # for marker_ch in self.marker_channels: + # marker_ch[TaborMarkerChannelActivatable].disable() - wf_mode_cmd = ":SOUR:FUNC:MODE FIX" + wf_mode_cmd = ":SOUR:FUNC:MODE FIX" - cmd = ";".join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) - self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = True + cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + cmd = out_cmd + cmd + self.device[SCPI].send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self._is_in_config_mode = True @with_select def _exit_config_mode(self) -> None: @@ -1187,20 +1207,7 @@ def _exit_config_mode(self) -> None: # TODO: change implementation for channel synchronisation feature - if self.device._is_coupled(): - # Coupled -> switch all channels at once - other_channel_tuple: TaborChannelTuple - if self.channels == self.device.channel_tuples[0].channels: - other_channel_tuple = self.device.channel_tuples[1] - else: - other_channel_tuple = self.device.channel_tuples[0] - - if not other_channel_tuple._is_in_config_mode: - self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") - self.device.send_cmd(":SEQ:SEL 1") - self.device.send_cmd(":OUTP:ALL ON") - - else: + if not self.device._is_coupled(): self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") self.device.send_cmd(":SEQ:SEL 1") @@ -1210,8 +1217,7 @@ def _exit_config_mode(self) -> None: self.device.send_cmd(cmd[:-1]) for marker_ch in self.marker_channels: - marker_ch[TaborMarkerChannelActivatable].status = True - + marker_ch[TaborActivatableMarkerChannels].enable() self._is_in_config_mode = False @@ -1219,7 +1225,8 @@ def _exit_config_mode(self) -> None: # Marker Channel ######################################################################################################################## # Features -class TaborMarkerChannelActivatable(ActivatableChannels): + +class TaborActivatableMarkerChannels(ActivatableChannels): def __init__(self, marker_channel: "TaborMarkerChannel"): super().__init__() self._parent = weakref.ref(marker_channel) @@ -1230,7 +1237,7 @@ def enabled(self) -> bool: @with_select def enable(self): - command_string = ":SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" + command_string = "SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) @@ -1238,7 +1245,7 @@ def enable(self): @with_select def disable(self): - command_string = ":SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" + command_string = ":SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) @@ -1255,7 +1262,7 @@ def __init__(self, idn: int, device: TaborDevice): self._device = weakref.ref(device) # adding Features - self.add_feature(TaborMarkerChannelActivatable(self)) + self.add_feature(TaborActivatableMarkerChannels(self)) @property def device(self) -> TaborDevice: @@ -1280,7 +1287,8 @@ def _select(self) -> None: """ This marker channel is selected and is now the active channel marker of the device """ - self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=self.idn)) + self.device.channels[int((self.idn-1)/2)]._select() + self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn-1)%2)+1))) ######################################################################################################################## diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index d8c4e224a..02ac7fb6b 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -5,7 +5,7 @@ from qupulse import ChannelID from qupulse._program._loop import Loop from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel -from qupulse.hardware.awgs.features import ChannelSynchronization, ProgramManagement, OffsetAmplitude, \ +from qupulse.hardware.awgs.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ AmplitudeOffsetHandling from qupulse.utils.types import Collection @@ -27,7 +27,7 @@ def synchronize_channels(self, group_size: int) -> None: self._parent.synchronize_channels(group_size) -class TestOffsetAmplitudeFeature(OffsetAmplitude): +class TestVoltageRangeFeature(VoltageRange): def __init__(self, channel: "TestAWGChannel"): super().__init__() self._parent = channel @@ -191,7 +191,7 @@ def __init__(self, idn: int, device: TestAWGDevice): # Add feature to this object (self) # During this call, all functions of the feature are dynamically added to this object - self.add_feature(TestOffsetAmplitudeFeature(self)) + self.add_feature(TestVoltageRangeFeature(self)) self._device = device self._channel_tuple = None @@ -225,17 +225,17 @@ def test_device(self): def test_channels(self): for i, channel in enumerate(self.device.channels): self.assertEqual(channel.idn, i), "Invalid channel id" - self.assertEqual(channel[OffsetAmplitude].offset, 0, "Invalid default offset for channel {}".format(i)) - self.assertEqual(channel[OffsetAmplitude].amplitude, 5.0, + self.assertEqual(channel[VoltageRange].offset, 0, "Invalid default offset for channel {}".format(i)) + self.assertEqual(channel[VoltageRange].amplitude, 5.0, "Invalid default amplitude for channel {}".format(i)) offs = -0.1 * i ampl = 0.5 + 3 * i - channel[OffsetAmplitude].offset = offs - channel[OffsetAmplitude].amplitude = ampl + channel[VoltageRange].offset = offs + channel[VoltageRange].amplitude = ampl - self.assertEqual(channel[OffsetAmplitude].offset, offs, "Invalid offset for channel {}".format(i)) - self.assertEqual(channel[OffsetAmplitude].amplitude, ampl, "Invalid amplitude for channel {}".format(i)) + self.assertEqual(channel[VoltageRange].offset, offs, "Invalid offset for channel {}".format(i)) + self.assertEqual(channel[VoltageRange].amplitude, ampl, "Invalid amplitude for channel {}".format(i)) def test_channel_tuples(self): for group_size in [2, 4, 8]: diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index dfc644925..703b37e32 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -9,9 +9,9 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs.features import DeviceControl, OffsetAmplitude, ProgramManagement +from qupulse.hardware.awgs.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ - TaborOffsetAmplitude, TaborDeviceControl, TaborProgramManagement + TaborVoltageRange, TaborDeviceControl, TaborProgramManagement class TaborSimulatorManager: @@ -135,23 +135,23 @@ def test_sample_rate(self): def test_amplitude(self): for channel in self.instrument.channels: - self.assertIsInstance(channel[OffsetAmplitude].amplitude, float) + self.assertIsInstance(channel[VoltageRange].amplitude, float) self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') self.instrument.send_cmd(':VOLT 0.7') - self.assertAlmostEqual(.7, self.instrument.channels[0][OffsetAmplitude].amplitude) + self.assertAlmostEqual(.7, self.instrument.channels[0][VoltageRange].amplitude) def test_select_marker(self): with self.assertRaises(IndexError): self.instrument.marker_channels[6]._select() self.instrument.marker_channels[1]._select() - selected = self.instrument._send_query(':SOUR:MARK:SEL?') + selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') self.instrument.marker_channels[0]._select() - selected = self.instrument._send_query(':SOUR:MARK:SEL?') + selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): @@ -159,10 +159,10 @@ def test_select_channel(self): self.instrument.channels[6]._select() self.instrument.channels[0]._select() - self.assertEqual(self.instrument._send_query(':INST:SEL?'), '1') + self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '1') self.instrument.channels[3]._select() - self.assertEqual(self.instrument._send_query(':INST:SEL?'), '4') + self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): @@ -216,7 +216,7 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair[ProgramManagement].change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet + self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) @@ -278,7 +278,7 @@ def test_set_volatile_parameter(self): actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] - self.channel_pair.set_volatile_parameters('dummy_program', parameters=para) + self.channel_pair[VolatileParameters].set_volatile_parameters('dummy_program', parameters=para) actual_sequence_tables[1][1] = (50, 3, 0) actual_advanced_table[2] = (5, 3, 0) From 5016b3b9fb8a098630311bfda8ebb8b852796623 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 26 May 2020 10:50:56 +0200 Subject: [PATCH 071/107] SCPI-Feature generalization implemented --- qupulse/hardware/awgs/features.py | 12 ++++++++---- qupulse/hardware/awgs/tabor.py | 9 ++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 23c865e6a..ca1ee6f95 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -5,18 +5,22 @@ from qupulse.hardware.awgs.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature from qupulse.utils.types import ChannelID +import pyvisa ######################################################################################################################## # device features ######################################################################################################################## class SCPI(AWGDeviceFeature, ABC): - @abstractmethod + def __init__(self, visa: pyvisa.resources.MessageBasedResource): + super().__init__() + + self._socket = visa + def send_cmd(self, cmd_str): - pass + self._socket.write(cmd_str) - @abstractmethod def send_query(self, query_str): - pass + self._socket.query(query_str) class ChannelSynchronization(AWGDeviceFeature, ABC): diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index d71ad7819..0b7d41ca1 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -18,6 +18,7 @@ from typing import Sequence from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ make_combined_wave +import pyvisa # Provided by Tabor electronics for python 2.7 # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech @@ -70,10 +71,12 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: ######################################################################################################################## # FeaturesBes class TaborSCPI(SCPI): - def __init__(self, device: "TaborDevice"): - super().__init__() + def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedResource): + super().__init__(visa) + self._parent = weakref.ref(device) + def send_cmd(self, cmd_str, paranoia_level=None): for instr in self._parent().all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) @@ -215,7 +218,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._coupled = None self._clock_marker = [0, 0, 0, 0] - self.add_feature(TaborSCPI(self)) + self.add_feature(TaborSCPI(self,self.main_instrument._visa_inst)) self.add_feature(TaborDeviceControl(self)) self.add_feature(TaborStatusTable(self)) From 303cfcb5136ae3ae4642049892edc0aa3d6b1c88 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 4 Jun 2020 14:19:24 +0200 Subject: [PATCH 072/107] Changed run_current_programm to work with coupling --- qupulse/hardware/awgs/features.py | 3 + qupulse/hardware/awgs/tabor.py | 92 +++++++++++++++++++------------ 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index ca1ee6f95..5d1fa1a22 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -22,6 +22,9 @@ def send_cmd(self, cmd_str): def send_query(self, query_str): self._socket.query(query_str) + def close(self): + self._socket.close() + class ChannelSynchronization(AWGDeviceFeature, ABC): """This Feature is used to synchronise a certain ammount of channels""" diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 0b7d41ca1..22b72e4b8 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -76,7 +76,6 @@ def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedRes self._parent = weakref.ref(device) - def send_cmd(self, cmd_str, paranoia_level=None): for instr in self._parent().all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) @@ -218,7 +217,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external self._coupled = None self._clock_marker = [0, 0, 0, 0] - self.add_feature(TaborSCPI(self,self.main_instrument._visa_inst)) + self.add_feature(TaborSCPI(self, self.main_instrument._visa_inst)) self.add_feature(TaborDeviceControl(self)) self.add_feature(TaborStatusTable(self)) @@ -522,7 +521,7 @@ def _select(self) -> None: ######################################################################################################################## # Features class TaborProgramManagement(ProgramManagement): - def __init__(self, channel_tuple: "TaborChannelTuple", ): + def __init__(self, channel_tuple: "TaborChannelTuple"): super().__init__() self._programs = {} self._armed_program = None @@ -543,7 +542,10 @@ def upload(self, name: str, The policy is to prefer amending the unknown waveforms to overwriting old ones. """ - + if len(channels) != len(self._parent().channels): + raise ValueError("Wrong number of channels") + if len(markers) != len(self._parent().marker): + raise ValueError("Wrong number of marker") if len(voltage_transformation) != len(self._parent().channels): raise ValueError("Wrong number of voltage transformations") @@ -569,9 +571,9 @@ def upload(self, name: str, voltage_offsets = [] for channel in self._parent().channels: - if channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.IGNORE_OFFSET: + if channel._amplitude_offset_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets.append(0) - elif channel._amplitude_offset_handling == self._parent().AmplitudeOffsetHandling.CONSIDER_OFFSET: + elif channel._amplitude_offset_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET: voltage_offsets.append(channel[VoltageRange].offset) else: raise ValueError( @@ -649,7 +651,6 @@ def clear(self) -> None: @with_select def arm(self, name: Optional[str]) -> None: - # TODO: does dearm work? """ Load the program 'name' and arm the device for running it. @@ -674,10 +675,18 @@ def run_current_program(self) -> None: Throws: RuntimeError: This exception is thrown if there is no active program for this device """ - if self._parent()._current_program: - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + if(self._parent().device()._is_coupled()): + for tuple in self._parent().device().channel_tuples: + if not tuple._parent()._current_program: + raise RuntimeError("The device is couple and one of the channel tuples has no program active") + self._parent().device().channel_tuples[0]._select() + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + else: - raise RuntimeError("No program active") + if self._parent()._current_program: + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + else: + raise RuntimeError("No program active") @with_select @with_configuration_guard @@ -1177,32 +1186,31 @@ def _enter_config_mode(self) -> None: sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip. """ - for num in range(0, int(len(self.channels) / 2)): - if self._is_in_config_mode is False: + if self._is_in_config_mode is False: - # 1. Selct channel pair - # 2. Select DC as function shape - # 3. Select build-in waveform mode + # 1. Selct channel pair + # 2. Select DC as function shape + # 3. Select build-in waveform mode - if self.device._is_coupled(): - out_cmd = ":OUTP:ALL OFF;" - else: - out_cmd = "" - for channel in self.channels[num*2:num*2+2]: - out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) + if self.device._is_coupled(): + out_cmd = ":OUTP:ALL OFF" + else: + out_cmd = "" + for channel in self.channels: + out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) - marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature - # for marker_ch in self.marker_channels: - # marker_ch[TaborMarkerChannelActivatable].disable() + marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature + # for marker_ch in self.marker_channels: + # marker_ch[TaborMarkerChannelActivatable].disable() - wf_mode_cmd = ":SOUR:FUNC:MODE FIX" + wf_mode_cmd = ":SOUR:FUNC:MODE FIX" - cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) - cmd = out_cmd + cmd - self.device[SCPI].send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = True + cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + cmd = out_cmd + cmd + self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self._is_in_config_mode = True @with_select def _exit_config_mode(self) -> None: @@ -1210,7 +1218,20 @@ def _exit_config_mode(self) -> None: # TODO: change implementation for channel synchronisation feature - if not self.device._is_coupled(): + if self.device._is_coupled(): + # Coupled -> switch all channels at once + other_channel_tuple: TaborChannelTuple + if self.channels == self.device.channel_tuples[0].channels: + other_channel_tuple = self.device.channel_tuples[1] + else: + other_channel_tuple = self.device.channel_tuples[0] + + if not other_channel_tuple._is_in_config_mode: + self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device.send_cmd(":SEQ:SEL 1") + self.device.send_cmd(":OUTP:ALL ON") + + else: self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") self.device.send_cmd(":SEQ:SEL 1") @@ -1220,7 +1241,8 @@ def _exit_config_mode(self) -> None: self.device.send_cmd(cmd[:-1]) for marker_ch in self.marker_channels: - marker_ch[TaborActivatableMarkerChannels].enable() + marker_ch[ActivatableChannels].enable() + self._is_in_config_mode = False @@ -1290,8 +1312,8 @@ def _select(self) -> None: """ This marker channel is selected and is now the active channel marker of the device """ - self.device.channels[int((self.idn-1)/2)]._select() - self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn-1)%2)+1))) + self.device.channels[int((self.idn - 1) / 2)]._select() + self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) ######################################################################################################################## From 76207cf24b171966e896949b89d93f33ffd0bfce Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 25 Jun 2020 09:09:14 +0200 Subject: [PATCH 073/107] Beginning of the ContinuousRunMode Feature --- qupulse/hardware/awgs/tabor.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 22b72e4b8..78cea9972 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -145,6 +145,8 @@ def trigger(self) -> None: """ This method triggers a device remotely. """ + #TODO: why does this work? + #self._parent()[SCPI].send_cmd(":ENAB") self._parent()[SCPI].send_cmd(":TRIG") @@ -230,7 +232,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # ChannelMarker self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] - self._initialize() # TODO: change for synchronisation-feature + self._initialize() # ChannelTuple self._channel_tuples = [] @@ -255,6 +257,15 @@ def abort(self) -> None: """ self[SCPI].send_cmd(":ABOR") + def set_coupled(self, coupled: bool) -> None: + """ + Thats the coupling of the device to 'coupled' + """ + if coupled: + self.send_cmd("INST:COUP:STAT ON") + else: + self.send_cmd("INST:COUP:STAT OFF") + def _is_coupled(self) -> bool: # TODO: comment is missing if self._coupled is None: @@ -679,8 +690,8 @@ def run_current_program(self) -> None: for tuple in self._parent().device().channel_tuples: if not tuple._parent()._current_program: raise RuntimeError("The device is couple and one of the channel tuples has no program active") - self._parent().device().channel_tuples[0]._select() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + self._parent().device().channel_tuples[0]._select() + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) else: if self._parent()._current_program: From 24bb3646521685c92e78b890de4eccf2c3ea756b Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 27 Aug 2020 13:49:17 +0200 Subject: [PATCH 074/107] continued work on the ContinuousRunMode Feature --- qupulse/_program/tabor.py | 4 +- qupulse/hardware/awgs/features.py | 6 ++ qupulse/hardware/awgs/tabor.py | 84 ++++++++++++++----- tests/hardware/tabor_simulator_based_tests.py | 6 +- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 96359b34c..1bb7c384f 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -389,7 +389,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'])) @@ -420,6 +421,7 @@ def __init__(self, self._parsed_program = None # type: Optional[ParsedProgram] self._mode = None self._device_properties = device_properties + self._repetition_mode = repeat assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode" if mode == TaborSequencing.SINGLE: diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 5d1fa1a22..721ef65f9 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -92,6 +92,7 @@ def upload(self, name: str, channels: Tuple[Optional[ChannelID], ...], markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], + repetition_mode = None, force: bool = False) -> None: """ Upload a program to the AWG. @@ -108,6 +109,7 @@ def upload(self, name: str, markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel voltage_transformation: transformations applied to the waveforms extracted rom the program. Position in the list corresponds to the AWG channel + repetition_mode: how often the signal should be sent force: If a different sequence is already present with the same name, it is overwritten if force is set to True. (default = False) """ @@ -153,6 +155,10 @@ def run_current_program(self) -> None: """This method starts running the active program""" raise NotImplementedError() + + + + ######################################################################################################################## # channel features ######################################################################################################################## diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 78cea9972..80530c6e9 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -69,7 +69,7 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: ######################################################################################################################## # Device ######################################################################################################################## -# FeaturesBes +# Features class TaborSCPI(SCPI): def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedResource): super().__init__(visa) @@ -145,8 +145,8 @@ def trigger(self) -> None: """ This method triggers a device remotely. """ - #TODO: why does this work? - #self._parent()[SCPI].send_cmd(":ENAB") + # TODO: why does this work? + # self._parent()[SCPI].send_cmd(":ENAB") self._parent()[SCPI].send_cmd(":TRIG") @@ -371,6 +371,8 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: assert len(answers) == 0 def _initialize(self) -> None: + # TODO: work on this comment + # 1. Select channel # 2. Turn off gated mode # 3. continous mode @@ -378,9 +380,12 @@ def _initialize(self) -> None: # 5. Expect enable signal from (USB / LAN / GPIB) # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) + + # TODO: Test here + # ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS;" // :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR TRIG; setup_command = ( ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS;" ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") self[SCPI].send_cmd(":INST:SEL 1") self[SCPI].send_cmd(setup_command) @@ -486,11 +491,6 @@ def _select(self) -> None: self._parent()._select() -class TaborRepetionMode(RepetionMode): - # TODO: fill with functionalty - pass - - # Implementation class TaborChannel(AWGChannel): def __init__(self, idn: int, device: TaborDevice): @@ -520,7 +520,6 @@ def _set_channel_tuple(self, channel_tuple: "TaborChannelTuple") -> None: Args: channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to """ - # TODO (toCheck): problem with the _? self._channel_tuple = weakref.ref(channel_tuple) def _select(self) -> None: @@ -540,6 +539,29 @@ def __init__(self, channel_tuple: "TaborChannelTuple"): self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + def get_runmode(self, program_name: str) -> str: + """ + Returns the default runmode of a certain program + Args: + program_name (str): name of the program whose runmode should be returned + """ + return self._parent()._known_programs[program_name].program._repetition_mode + + def set_runmode(self, program_name: str, runmode: str) -> None: + """ + Changes the default runmode of a certain program + + Args: + program_name (str): name of the program whose runmode should be changed + + Throws: + ValueError: this Exception is thrown when an invalid runmode is given + """ + if runmode is "infinite" or runmode is "once": + self._parent()._known_programs[program_name].program._repetition_mode = runmode + else: + raise ValueError("{} is no vaild repetition mode".format(runmode)) + @with_configuration_guard @with_select def upload(self, name: str, @@ -547,12 +569,16 @@ def upload(self, name: str, channels: Tuple[Optional[ChannelID], Optional[ChannelID]], markers: Tuple[Optional[ChannelID], Optional[ChannelID]], voltage_transformation: Tuple[Callable, Callable], + repetition_mode: str = None, force: bool = False) -> None: """ Upload a program to the AWG. The policy is to prefer amending the unknown waveforms to overwriting old ones. """ + if repetition_mode is None: + repetition_mode = "infinite" + if len(channels) != len(self._parent().channels): raise ValueError("Wrong number of channels") if len(markers) != len(self._parent().marker): @@ -574,7 +600,6 @@ def upload(self, name: str, raise ValueError('{} is already known on {}'.format(name, self._parent().idn)) # They call the peak to peak range amplitude - # TODO: Are the replacments for a variable size of channel tuples okay? ranges = tuple(ch[VoltageRange].amplitude for ch in self._parent().channels) @@ -620,6 +645,9 @@ def upload(self, name: str, self._parent()._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, program=tabor_program) + # set the default repetionmode for a programm + self.set_runmode(program_name=name, runmode=repetition_mode) + def remove(self, name: str) -> None: """ Remove a program from the AWG. @@ -686,16 +714,23 @@ def run_current_program(self) -> None: Throws: RuntimeError: This exception is thrown if there is no active program for this device """ - if(self._parent().device()._is_coupled()): - for tuple in self._parent().device().channel_tuples: - if not tuple._parent()._current_program: - raise RuntimeError("The device is couple and one of the channel tuples has no program active") - self._parent().device().channel_tuples[0]._select() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + if (self._parent().device()._is_coupled()): + raise NotImplementedError else: if self._parent()._current_program: - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + repetition_mode = self._parent()._known_programs[ + self._parent()._current_program].program._repetition_mode + if repetition_mode is "infinite": + self._cont_runmode() + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + elif repetition_mode is "once": + self._trig_runmode() + self._parent().device.send_cmd(':INIT:CONT OFF;', + paranoia_level=self._parent().internal_paranoia_level) + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + else: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) else: raise RuntimeError("No program active") @@ -731,7 +766,8 @@ def _change_armed_program(self, name: Optional[str]) -> None: sequencer_tables[1].append((1, 1, 0)) # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table + # TODO: Does it work without this line? + advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table while len(advanced_sequencer_table) < self._parent().device.dev_properties["min_aseq_len"]: advanced_sequencer_table.append((1, 1, 0)) @@ -771,6 +807,16 @@ def _enter_config_mode(self): def _exit_config_mode(self): self._parent()._exit_config_mode() + @with_select + def _cont_runmode(self): + """Changes the run mode of this channel tuple to continous mode""" + self._parent().device.send_cmd(":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS") + + @with_select + def _trig_runmode(self): + """Changes the run mode of this channel tuple to triggered mode""" + self._parent().device.send_cmd(":INIT:CONT 0") + class TaborVolatileParameters(VolatileParameters): def __init__(self, channel_tuple: "TaborChannelTuple", ): diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 703b37e32..e85f6d717 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -257,7 +257,9 @@ def test_read_advanced_sequencer_table(self): self.channel_pair._amend_segments(self.segments) self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) - actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + #TODO: test here + #actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) @@ -276,7 +278,7 @@ def test_set_volatile_parameter(self): for table in self.sequence_tables_raw] - actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] self.channel_pair[VolatileParameters].set_volatile_parameters('dummy_program', parameters=para) From 647a26a5060696bcd8ff040de274c53ea3aa7ff9 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 28 Aug 2020 08:24:04 +0200 Subject: [PATCH 075/107] -fixes from tests with device ("once" still not working) -repetion_mode is now the name everywhere (not runmode anymore) --- qupulse/hardware/awgs/tabor.py | 46 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 80530c6e9..c376924db 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -246,7 +246,7 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external def enable(self) -> None: """ This method immediately generates the selected output waveform, if the device is in continuous and armed - run mode. + repetition mode. """ self[SCPI].send_cmd(":ENAB") @@ -538,29 +538,30 @@ def __init__(self, channel_tuple: "TaborChannelTuple"): self._parent = weakref.ref(channel_tuple) self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + self._trigger_source = 'BUS' - def get_runmode(self, program_name: str) -> str: + def get_repetition_mode(self, program_name: str) -> str: """ - Returns the default runmode of a certain program + Returns the default repetition mode of a certain program Args: - program_name (str): name of the program whose runmode should be returned + program_name (str): name of the program whose repetition mode should be returned """ return self._parent()._known_programs[program_name].program._repetition_mode - def set_runmode(self, program_name: str, runmode: str) -> None: + def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: """ - Changes the default runmode of a certain program + Changes the default repetition mode of a certain program Args: - program_name (str): name of the program whose runmode should be changed + program_name (str): name of the program whose repetition mode should be changed Throws: - ValueError: this Exception is thrown when an invalid runmode is given + ValueError: this Exception is thrown when an invalid repetition mode is given """ - if runmode is "infinite" or runmode is "once": - self._parent()._known_programs[program_name].program._repetition_mode = runmode + if repetition_mode is "infinite" or repetition_mode is "once": + self._parent()._known_programs[program_name].program._repetition_mode = repetition_mode else: - raise ValueError("{} is no vaild repetition mode".format(runmode)) + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) @with_configuration_guard @with_select @@ -576,12 +577,10 @@ def upload(self, name: str, The policy is to prefer amending the unknown waveforms to overwriting old ones. """ - if repetition_mode is None: - repetition_mode = "infinite" if len(channels) != len(self._parent().channels): raise ValueError("Wrong number of channels") - if len(markers) != len(self._parent().marker): + if len(markers) != len(self._parent().marker_channels): raise ValueError("Wrong number of marker") if len(voltage_transformation) != len(self._parent().channels): raise ValueError("Wrong number of voltage transformations") @@ -646,7 +645,7 @@ def upload(self, name: str, program=tabor_program) # set the default repetionmode for a programm - self.set_runmode(program_name=name, runmode=repetition_mode) + self.set_repetition_mode(program_name=name, repetition_mode=repetition_mode) def remove(self, name: str) -> None: """ @@ -714,20 +713,19 @@ def run_current_program(self) -> None: Throws: RuntimeError: This exception is thrown if there is no active program for this device """ - if (self._parent().device()._is_coupled()): + if (self._parent().device._is_coupled()): raise NotImplementedError + #TODO: continue else: if self._parent()._current_program: repetition_mode = self._parent()._known_programs[ self._parent()._current_program].program._repetition_mode if repetition_mode is "infinite": - self._cont_runmode() + self._cont_repetition_mode() self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) elif repetition_mode is "once": - self._trig_runmode() - self._parent().device.send_cmd(':INIT:CONT OFF;', - paranoia_level=self._parent().internal_paranoia_level) + self._trig_repetition_mode() self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) @@ -808,14 +806,14 @@ def _exit_config_mode(self): self._parent()._exit_config_mode() @with_select - def _cont_runmode(self): + def _cont_repetition_mode(self): """Changes the run mode of this channel tuple to continous mode""" - self._parent().device.send_cmd(":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS") + self._parent().device.send_cmd(f":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") @with_select - def _trig_runmode(self): + def _trig_repetition_mode(self): """Changes the run mode of this channel tuple to triggered mode""" - self._parent().device.send_cmd(":INIT:CONT 0") + self._parent().device.send_cmd(f":INIT:CONT 0; :TRIG:SOUR:ADV {self._trigger_source}") class TaborVolatileParameters(VolatileParameters): From 60762ad256525c5d47f9ca7dd5aeaa44cf3f3ac4 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 28 Aug 2020 09:55:53 +0200 Subject: [PATCH 076/107] -small fixes --- qupulse/hardware/awgs/tabor.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index c376924db..c3d30fd07 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -19,6 +19,7 @@ from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ make_combined_wave import pyvisa +import warnings # Provided by Tabor electronics for python 2.7 # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech @@ -145,8 +146,6 @@ def trigger(self) -> None: """ This method triggers a device remotely. """ - # TODO: why does this work? - # self._parent()[SCPI].send_cmd(":ENAB") self._parent()[SCPI].send_cmd(":TRIG") @@ -381,8 +380,6 @@ def _initialize(self) -> None: # 6. Use arbitrary waveforms as marker source # 7. Expect jump command for sequencing from (USB / LAN / GPIB) - # TODO: Test here - # ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS;" // :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR TRIG; setup_command = ( ":INIT:GATE OFF; :INIT:CONT ON; " ":INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS;" @@ -578,6 +575,11 @@ def upload(self, name: str, The policy is to prefer amending the unknown waveforms to overwriting old ones. """ + # TODO: Default mode should be "once" for backward compability - change when once is working + if repetition_mode is None: + warnings.warn("TaborWarning - upload() - no repetition mode given!") + repetition_mode = "infinite" + if len(channels) != len(self._parent().channels): raise ValueError("Wrong number of channels") if len(markers) != len(self._parent().marker_channels): From 33e65800b5402baed5d77aa4bef728e799e0978c Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 28 Aug 2020 09:59:34 +0200 Subject: [PATCH 077/107] marker_channls is now the name for the consistently --- qupulse/hardware/awgs/features.py | 4 ++-- qupulse/hardware/awgs/tabor.py | 12 ++++++------ qupulse/hardware/setup.py | 2 +- tests/hardware/awg_base_tests.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index 721ef65f9..df12cdf29 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -90,7 +90,7 @@ class ProgramManagement(AWGChannelTupleFeature, ABC): def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], - markers: Tuple[Optional[ChannelID], ...], + marker_channels: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], repetition_mode = None, force: bool = False) -> None: @@ -106,7 +106,7 @@ def upload(self, name: str, name: A name for the program on the AWG. program: The program (a sequence of instructions) to upload. channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel - markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel + marker_channels: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel voltage_transformation: transformations applied to the waveforms extracted rom the program. Position in the list corresponds to the AWG channel repetition_mode: how often the signal should be sent diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index c3d30fd07..df960ed40 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -228,8 +228,8 @@ def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external # Channel self._channels = [TaborChannel(i + 1, self) for i in range(4)] - # ChannelMarker - self._channel_marker = [TaborMarkerChannel(i + 1, self) for i in range(4)] + # MarkerChannels + self._marker_channels = [TaborMarkerChannel(i + 1, self) for i in range(4)] self._initialize() @@ -285,7 +285,7 @@ def channels(self) -> Collection["TaborChannel"]: @property def marker_channels(self) -> Collection["TaborMarkerChannel"]: """Returns a list of all marker channels of a device. The collection may be empty""" - return self._channel_marker + return self._marker_channels @property def channel_tuples(self) -> Collection["TaborChannelTuple"]: @@ -565,7 +565,7 @@ def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]], + marker_channels: Tuple[Optional[ChannelID], Optional[ChannelID]], voltage_transformation: Tuple[Callable, Callable], repetition_mode: str = None, force: bool = False) -> None: @@ -582,7 +582,7 @@ def upload(self, name: str, if len(channels) != len(self._parent().channels): raise ValueError("Wrong number of channels") - if len(markers) != len(self._parent().marker_channels): + if len(marker_channels) != len(self._parent().marker_channels): raise ValueError("Wrong number of marker") if len(voltage_transformation) != len(self._parent().channels): raise ValueError("Wrong number of voltage transformations") @@ -620,7 +620,7 @@ def upload(self, name: str, # parse to tabor program tabor_program = TaborProgram(program, channels=tuple(channels), - markers=markers, + markers=marker_channels, device_properties=self._parent().device.dev_properties, sample_rate=sample_rate / 10 ** 9, amplitudes=voltage_amplitudes, diff --git a/qupulse/hardware/setup.py b/qupulse/hardware/setup.py index 0460d9e71..a45a5d849 100644 --- a/qupulse/hardware/setup.py +++ b/qupulse/hardware/setup.py @@ -152,7 +152,7 @@ def get_default_info(awg): awg.upload(name, program=program, channels=tuple(playback_ids), - markers=tuple(marker_ids), + marker_channels=tuple(marker_ids), force=update, voltage_transformation=tuple(voltage_trafos)) diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 02ac7fb6b..6e30e8ad3 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -70,7 +70,7 @@ def __init__(self): self._armed_program = None def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], - markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], + marker_channels: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: if name in self._programs: raise KeyError("Program with name \"{}\" is already on the instrument.".format(name)) From f9c5d972523d5df856753f85424ebd0cef97d484 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 28 Aug 2020 10:20:10 +0200 Subject: [PATCH 078/107] first test of run_current_program for a coupled device --- qupulse/hardware/awgs/features.py | 10 +++++----- qupulse/hardware/awgs/tabor.py | 26 +++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs/features.py index df12cdf29..ceaf8b8e5 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs/features.py @@ -7,6 +7,7 @@ import pyvisa + ######################################################################################################################## # device features ######################################################################################################################## @@ -80,11 +81,13 @@ class ReadProgram(AWGChannelTupleFeature, ABC): def read_complete_program(self): pass + class VolatileParameters(AWGChannelTupleFeature, ABC): @abstractmethod def set_volatile_parameters(self, program_name, parameters) -> None: raise NotImplementedError() + class ProgramManagement(AWGChannelTupleFeature, ABC): @abstractmethod def upload(self, name: str, @@ -92,7 +95,7 @@ def upload(self, name: str, channels: Tuple[Optional[ChannelID], ...], marker_channels: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Optional[Callable], ...], - repetition_mode = None, + repetition_mode=None, force: bool = False) -> None: """ Upload a program to the AWG. @@ -156,9 +159,6 @@ def run_current_program(self) -> None: raise NotImplementedError() - - - ######################################################################################################################## # channel features ######################################################################################################################## @@ -222,4 +222,4 @@ def disable(self): class RepetionMode(AWGChannelFeature): - pass \ No newline at end of file + pass diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index df960ed40..fdb7f8d52 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -716,9 +716,26 @@ def run_current_program(self) -> None: RuntimeError: This exception is thrown if there is no active program for this device """ if (self._parent().device._is_coupled()): - raise NotImplementedError + # channel tuple is the first channel tuple + if (self._parent.device._channel_tuples[0] == self): + if self._parent()._current_program: + repetition_mode = self._parent()._known_programs[ + self._parent()._current_program].program._repetition_mode + if repetition_mode is "infinite": + self._cont_repetition_mode() + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + elif repetition_mode is "once": + self._trig_repetition_mode() + self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + else: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + else: + raise RuntimeError("No program active") + else: + warnings.warn( + "TaborWarning - run_current_program() - the device is coupled - runthe program via the first channel tuple") - #TODO: continue + # TODO: continue else: if self._parent()._current_program: repetition_mode = self._parent()._known_programs[ @@ -734,6 +751,8 @@ def run_current_program(self) -> None: else: raise RuntimeError("No program active") + + @with_select @with_configuration_guard def _change_armed_program(self, name: Optional[str]) -> None: @@ -810,7 +829,8 @@ def _exit_config_mode(self): @with_select def _cont_repetition_mode(self): """Changes the run mode of this channel tuple to continous mode""" - self._parent().device.send_cmd(f":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + self._parent().device.send_cmd( + f":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") @with_select def _trig_repetition_mode(self): From ccd737dc1a76616bd9d15e2c57d36a79086275ad Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 20 Nov 2020 07:53:23 +0100 Subject: [PATCH 079/107] fixed __init__ of the tabor program --- qupulse/_program/tabor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 1bb7c384f..6e40cb45d 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -421,7 +421,7 @@ def __init__(self, self._parsed_program = None # type: Optional[ParsedProgram] self._mode = None self._device_properties = device_properties - self._repetition_mode = repeat + self._repetition_mode = repetition_mode assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode" if mode == TaborSequencing.SINGLE: From 73b138cb8a3ce4973ac65da60974b0d46ab84823 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 2 Dec 2020 08:08:57 +0100 Subject: [PATCH 080/107] -moved _send_cmd into the SCPI feature of a device -changed all send_cmd() to [SCPI].send(cmd) --- qupulse/hardware/awgs/tabor.py | 181 ++++++++++++++++----------------- 1 file changed, 88 insertions(+), 93 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index fdb7f8d52..5313e0a3f 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -26,7 +26,6 @@ # Beware of the string encoding change! import teawg -# What does this mean? assert (sys.byteorder == "little") __all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] @@ -83,10 +82,40 @@ def send_cmd(self, cmd_str, paranoia_level=None): def send_query(self, query_str, query_mirrors=False) -> Any: if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self.all_devices) + return tuple(instr.send_query(query_str) for instr in self._parent().all_devices) else: return self._parent().main_instrument.send_query(query_str) + def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: + """Overwrite send_cmd for paranoia_level > 3""" + if paranoia_level is None: + paranoia_level = self._parent().paranoia_level + + if paranoia_level < 3: + self._parent().super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover + else: + cmd_str = cmd_str.rstrip() + + if len(cmd_str) > 0: + ask_str = cmd_str + "; *OPC?; :SYST:ERR?" + else: + ask_str = "*OPC?; :SYST:ERR?" + + *answers, opc, error_code_msg = self._parent()._visa_inst.ask(ask_str).split(";") + + error_code, error_msg = error_code_msg.split(",") + error_code = int(error_code) + if error_code != 0: + _ = self._parent()._visa_inst.ask("*CLS; *OPC?") + + if error_code == -450: + # query queue overflow + self.send_cmd(cmd_str) + else: + raise RuntimeError("Cannot execute command: {}\n{}: {}".format(cmd_str, error_code, error_msg)) + + assert len(answers) == 0 + class TaborChannelSynchronization(ChannelSynchronization): """This Feature is used to synchronise a certain ammount of channels""" @@ -261,9 +290,9 @@ def set_coupled(self, coupled: bool) -> None: Thats the coupling of the device to 'coupled' """ if coupled: - self.send_cmd("INST:COUP:STAT ON") + self[SCPI].send_cmd("INST:COUP:STAT ON") else: - self.send_cmd("INST:COUP:STAT OFF") + self[SCPI].send_cmd("INST:COUP:STAT OFF") def _is_coupled(self) -> bool: # TODO: comment is missing @@ -317,10 +346,6 @@ def _paranoia_level(self, val): def dev_properties(self) -> dict: return self._instr.dev_properties - def send_cmd(self, cmd_str, paranoia_level=None): - for instr in self.all_devices: - instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) - def _send_binary_data(self, pref, bin_dat, paranoia_level=None): for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) @@ -339,36 +364,6 @@ def _download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=N make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) - def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: - """Overwrite send_cmd for paranoia_level > 3""" - if paranoia_level is None: - paranoia_level = self.paranoia_level - - if paranoia_level < 3: - super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover - else: - cmd_str = cmd_str.rstrip() - - if len(cmd_str) > 0: - ask_str = cmd_str + "; *OPC?; :SYST:ERR?" - else: - ask_str = "*OPC?; :SYST:ERR?" - - *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(";") - - error_code, error_msg = error_code_msg.split(",") - error_code = int(error_code) - if error_code != 0: - _ = self._visa_inst.ask("*CLS; *OPC?") - - if error_code == -450: - # query queue overflow - self.send_cmd(cmd_str) - else: - raise RuntimeError("Cannot execute command: {}\n{}: {}".format(cmd_str, error_code, error_msg)) - - assert len(answers) == 0 - def _initialize(self) -> None: # TODO: work on this comment @@ -520,7 +515,7 @@ def _set_channel_tuple(self, channel_tuple: "TaborChannelTuple") -> None: self._channel_tuple = weakref.ref(channel_tuple) def _select(self) -> None: - self.device.send_cmd(":INST:SEL {channel}".format(channel=self.idn)) + self.device[SCPI].send_cmd(":INST:SEL {channel}".format(channel=self.idn)) ######################################################################################################################## @@ -669,13 +664,13 @@ def clear(self) -> None: """ self._parent().device.channels[0]._select() - self._parent().device.send_cmd(":TRAC:DEL:ALL") - self._parent().device.send_cmd(":SOUR:SEQ:DEL:ALL") - self._parent().device.send_cmd(":ASEQ:DEL") + self._parent().device[SCPI].send_cmd(":TRAC:DEL:ALL") + self._parent().device[SCPI].send_cmd(":SOUR:SEQ:DEL:ALL") + self._parent().device[SCPI].send_cmd(":ASEQ:DEL") - self._parent().device.send_cmd(":TRAC:DEF 1, 192") - self._parent().device.send_cmd(":TRAC:SEL 1") - self._parent().device.send_cmd(":TRAC:MODE COMB") + self._parent().device[SCPI].send_cmd(":TRAC:DEF 1, 192") + self._parent().device[SCPI].send_cmd(":TRAC:SEL 1") + self._parent().device[SCPI].send_cmd(":TRAC:MODE COMB") self._parent().device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent()._idle_segment.get_as_binary()) self._parent()._segment_lengths = 192 * np.ones(1, dtype=np.uint32) @@ -698,7 +693,7 @@ def arm(self, name: Optional[str]) -> None: name (str): the program the device should change to """ if self._parent()._current_program == name: - self._parent().device.send_cmd("SEQ:SEL 1") + self._parent().device[SCPI].send_cmd("SEQ:SEL 1") else: self._change_armed_program(name) @@ -723,10 +718,12 @@ def run_current_program(self) -> None: self._parent()._current_program].program._repetition_mode if repetition_mode is "infinite": self._cont_repetition_mode() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd(':TRIG', + paranoia_level=self._parent().internal_paranoia_level) elif repetition_mode is "once": self._trig_repetition_mode() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd(':TRIG', + paranoia_level=self._parent().internal_paranoia_level) else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) else: @@ -742,17 +739,15 @@ def run_current_program(self) -> None: self._parent()._current_program].program._repetition_mode if repetition_mode is "infinite": self._cont_repetition_mode() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) elif repetition_mode is "once": self._trig_repetition_mode() - self._parent().device.send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) else: raise RuntimeError("No program active") - - @with_select @with_configuration_guard def _change_armed_program(self, name: Optional[str]) -> None: @@ -785,24 +780,23 @@ def _change_armed_program(self, name: Optional[str]) -> None: sequencer_tables[1].append((1, 1, 0)) # insert idle sequence in advanced sequence table - # TODO: Does it work without this line? advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table while len(advanced_sequencer_table) < self._parent().device.dev_properties["min_aseq_len"]: advanced_sequencer_table.append((1, 1, 0)) - self._parent().device.send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent().internal_paranoia_level) self._parent()._sequencer_tables = [] - self._parent().device.send_cmd("ASEQ:DEL", paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd("ASEQ:DEL", paranoia_level=self._parent().internal_paranoia_level) self._parent()._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): - self._parent().device.send_cmd("SEQ:SEL {}".format(i + 1), - paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd("SEQ:SEL {}".format(i + 1), + paranoia_level=self._parent().internal_paranoia_level) self._parent().device._download_sequencer_table(sequencer_table) self._parent()._sequencer_tables = sequencer_tables - self._parent().device.send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) + self._parent().device[SCPI].send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) self._parent().device._download_adv_seq_table(advanced_sequencer_table) self._parent()._advanced_sequence_table = advanced_sequencer_table @@ -829,13 +823,16 @@ def _exit_config_mode(self): @with_select def _cont_repetition_mode(self): """Changes the run mode of this channel tuple to continous mode""" - self._parent().device.send_cmd( - f":INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + self._parent().device[SCPI].send_cmd(f":TRIG:SOUR:ADV EXT") + self._parent().device[SCPI].send_cmd( + f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") @with_select def _trig_repetition_mode(self): """Changes the run mode of this channel tuple to triggered mode""" - self._parent().device.send_cmd(f":INIT:CONT 0; :TRIG:SOUR:ADV {self._trigger_source}") + self._parent().device[SCPI].send_cmd(":INIT: CONT:ENAB:SOUR EVEN") + self._parent().device[SCPI].send_cmd( + f":INIT:CONT 0; :INIT:CONT:ENAB ARM; :TRIG:SOUR:ADV {self._trigger_source}") class TaborVolatileParameters(VolatileParameters): @@ -1029,28 +1026,28 @@ def _free_points_at_end(self) -> int: def read_waveforms(self) -> List[np.ndarray]: device = self.device._get_readable_device(simulator=True) - old_segment = device.send_query(":TRAC:SEL?") + old_segment = device[SCPI].send_query(":TRAC:SEL?") waveforms = [] uploaded_waveform_indices = np.flatnonzero( self._segment_references) + 1 for segment in uploaded_waveform_indices: - device.send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) + device[SCPI].send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) - device.send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) + device[SCPI].send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms @with_select def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: device = self.device._get_readable_device(simulator=True) - old_sequence = device.send_query(":SEQ:SEL?") + old_sequence = device[SCPI].send_query(":SEQ:SEL?") sequences = [] uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: - device.send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) + device[SCPI].send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) - device.send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) + device[SCPI].send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences @with_select @@ -1163,13 +1160,13 @@ def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: segment_no = segment_index + 1 - self.device.send_cmd(":TRAC:DEF {}, {}".format(segment_no, segment.num_points), - paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:DEF {}, {}".format(segment_no, segment.num_points), + paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points - self.device.send_cmd(":TRAC:SEL {}".format(segment_no), paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:SEL {}".format(segment_no), paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) @@ -1187,12 +1184,12 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: segment_index = len(self._segment_capacity) first_segment_number = segment_index + 1 - self.device.send_cmd(":TRAC:DEF {},{}".format(first_segment_number, trac_len), - paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(":TRAC:SEL {}".format(first_segment_number), - paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(":TRAC:MODE COMB", - paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(first_segment_number, trac_len), + paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:SEL {}".format(first_segment_number), + paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:MODE COMB", + paranoia_level=self.internal_paranoia_level) self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) @@ -1203,15 +1200,15 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: if len(segments) < old_to_update: for i, segment in enumerate(segments): current_segment_number = first_segment_number + i - self.device.send_cmd(":TRAC:DEF {},{}".format(current_segment_number, segment.num_points), - paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(current_segment_number, segment.num_points), + paranoia_level=self.internal_paranoia_level) else: # flush the capacity self.device._download_segment_lengths(segment_capacity) # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(":TRAC:DEF {},{}".format(i + 1, segment_lengths[i])) + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(i + 1, segment_lengths[i])) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -1236,7 +1233,7 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd("; ".join("TRAC:DEL {}".format(i + 1) + self.device[SCPI].send_cmd("; ".join("TRAC:DEL {}".format(i + 1) for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) except Exception as e: raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e @@ -1249,7 +1246,7 @@ def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> N commands: Commands that should be executed. """ cmd_str = ";".join(commands) - self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table @@ -1286,7 +1283,7 @@ def _enter_config_mode(self) -> None: cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) cmd = out_cmd + cmd - self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self.device[SCPI].send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) self._is_in_config_mode = True @with_select @@ -1304,18 +1301,16 @@ def _exit_config_mode(self) -> None: other_channel_tuple = self.device.channel_tuples[0] if not other_channel_tuple._is_in_config_mode: - self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") - self.device.send_cmd(":SEQ:SEL 1") - self.device.send_cmd(":OUTP:ALL ON") + self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device[SCPI].send_cmd(":SEQ:SEL 1") + self.device[SCPI].send_cmd(":OUTP:ALL ON") else: - self.device.send_cmd(":SOUR:FUNC:MODE ASEQ") - self.device.send_cmd(":SEQ:SEL 1") + self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device[SCPI].send_cmd(":SEQ:SEL 1") - cmd = "" for channel in self.channels: channel[ActivatableChannels].enable() - self.device.send_cmd(cmd[:-1]) for marker_ch in self.marker_channels: marker_ch[ActivatableChannels].enable() @@ -1343,7 +1338,7 @@ def enable(self): command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device.send_cmd(command_string) + self._parent().device[SCPI].send_cmd(command_string) @with_select def disable(self): @@ -1351,7 +1346,7 @@ def disable(self): command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device.send_cmd(command_string) + self._parent().device[SCPI].send_cmd(command_string) def _select(self) -> None: self._parent()._select() @@ -1390,7 +1385,7 @@ def _select(self) -> None: This marker channel is selected and is now the active channel marker of the device """ self.device.channels[int((self.idn - 1) / 2)]._select() - self.device.send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) + self.device[SCPI].send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) ######################################################################################################################## From 8ba3b3ebc87c1c60de91cef1a3f63e793c1ec1be Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 2 Dec 2020 08:10:28 +0100 Subject: [PATCH 081/107] the getitem of a feature can now also be used with a string --- qupulse/hardware/awgs/base_features.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs/base_features.py index f03059e08..dd2b31164 100644 --- a/qupulse/hardware/awgs/base_features.py +++ b/qupulse/hardware/awgs/base_features.py @@ -2,7 +2,6 @@ from typing import Callable, Generic, Mapping, Optional, Type, TypeVar from abc import ABC - __all__ = ["Feature", "FeatureAble"] @@ -17,6 +16,7 @@ def __init__(self, target_type: Type["FeatureAble"]): def target_type(self) -> Type["FeatureAble"]: return self._target_type + FeatureType = TypeVar("FeatureType", bound=Feature) GetItemFeatureType = TypeVar("GetItemFeatureType", bound=Feature) @@ -32,6 +32,8 @@ def __init__(self): 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__)) @@ -58,6 +60,8 @@ def add_feature(self, feature: FeatureType) -> None: 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]: From d2ea69e44e43ee94fdaa6d93dfe2e1602a6ee91d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 9 Dec 2020 09:41:26 +0100 Subject: [PATCH 082/107] -removed support once repetition mode in the method upload and run_current_program -removed _once_repetition_mode method --- qupulse/hardware/awgs/tabor.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 5313e0a3f..721f166a2 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -77,6 +77,7 @@ def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedRes self._parent = weakref.ref(device) def send_cmd(self, cmd_str, paranoia_level=None): + print(cmd_str) for instr in self._parent().all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) @@ -570,10 +571,11 @@ def upload(self, name: str, The policy is to prefer amending the unknown waveforms to overwriting old ones. """ - # TODO: Default mode should be "once" for backward compability - change when once is working if repetition_mode is None: warnings.warn("TaborWarning - upload() - no repetition mode given!") repetition_mode = "infinite" + elif repetition_mode not in ("infinite"): + raise TaborException("Invalid Repetionmode: " + repetition_mode) if len(channels) != len(self._parent().channels): raise ValueError("Wrong number of channels") @@ -720,10 +722,6 @@ def run_current_program(self) -> None: self._cont_repetition_mode() self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) - elif repetition_mode is "once": - self._trig_repetition_mode() - self._parent().device[SCPI].send_cmd(':TRIG', - paranoia_level=self._parent().internal_paranoia_level) else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) else: @@ -732,7 +730,6 @@ def run_current_program(self) -> None: warnings.warn( "TaborWarning - run_current_program() - the device is coupled - runthe program via the first channel tuple") - # TODO: continue else: if self._parent()._current_program: repetition_mode = self._parent()._known_programs[ @@ -740,9 +737,6 @@ def run_current_program(self) -> None: if repetition_mode is "infinite": self._cont_repetition_mode() self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) - elif repetition_mode is "once": - self._trig_repetition_mode() - self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) else: @@ -827,13 +821,6 @@ def _cont_repetition_mode(self): self._parent().device[SCPI].send_cmd( f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") - @with_select - def _trig_repetition_mode(self): - """Changes the run mode of this channel tuple to triggered mode""" - self._parent().device[SCPI].send_cmd(":INIT: CONT:ENAB:SOUR EVEN") - self._parent().device[SCPI].send_cmd( - f":INIT:CONT 0; :INIT:CONT:ENAB ARM; :TRIG:SOUR:ADV {self._trigger_source}") - class TaborVolatileParameters(VolatileParameters): def __init__(self, channel_tuple: "TaborChannelTuple", ): From d525c8e870218517574a145969690f9360083d90 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 17 Dec 2020 16:05:48 +0100 Subject: [PATCH 083/107] Moved the tabor driver with the new logic into their own package "awgs_new_driver" Renamed the old_base and old_tabor back to their original names base and tabor --- doc/source/examples/hardware/tabor.py | 2 +- qupulse/_program/seqc.py | 2 +- qupulse/_program/tabor.py | 3 +- qupulse/examples/VolatileParameters.py | 2 +- qupulse/hardware/awgs/__init__.py | 2 +- qupulse/hardware/awgs/base.py | 234 ++- qupulse/hardware/awgs/old_base.py | 259 --- qupulse/hardware/awgs/old_tabor.py | 943 ---------- qupulse/hardware/awgs/tabor.py | 1563 ++++++----------- qupulse/hardware/awgs/tektronix.py | 2 +- qupulse/hardware/awgs/zihdawg.py | 2 +- qupulse/hardware/awgs_new_driver/base.py | 295 ++++ .../base_features.py | 0 .../channel_tuple_wrapper.py | 14 +- .../{awgs => awgs_new_driver}/features.py | 2 +- qupulse/hardware/awgs_new_driver/tabor.py | 1398 +++++++++++++++ qupulse/hardware/setup.py | 2 +- .../tabor_backward_compatibility_tests.py | 2 +- tests/hardware/awg_base_tests.py | 6 +- tests/hardware/dummy_devices.py | 2 +- .../old_tabor_simulator_based_tests.py | 2 +- tests/hardware/tabor_clock_tests.py | 6 +- tests/hardware/tabor_dummy_based_tests.py | 10 +- tests/hardware/tabor_exex_test.py | 2 +- tests/hardware/tabor_simulator_based_tests.py | 5 +- tests/hardware/tabor_tests.py | 15 +- 26 files changed, 2380 insertions(+), 2395 deletions(-) delete mode 100644 qupulse/hardware/awgs/old_base.py delete mode 100644 qupulse/hardware/awgs/old_tabor.py create mode 100644 qupulse/hardware/awgs_new_driver/base.py rename qupulse/hardware/{awgs => awgs_new_driver}/base_features.py (100%) rename qupulse/hardware/{awgs => awgs_new_driver}/channel_tuple_wrapper.py (78%) rename qupulse/hardware/{awgs => awgs_new_driver}/features.py (98%) create mode 100644 qupulse/hardware/awgs_new_driver/tabor.py diff --git a/doc/source/examples/hardware/tabor.py b/doc/source/examples/hardware/tabor.py index e80d8fc03..a579e89b4 100644 --- a/doc/source/examples/hardware/tabor.py +++ b/doc/source/examples/hardware/tabor.py @@ -1,5 +1,5 @@ from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel -from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborAWGRepresentation +from qupulse.hardware.awgs_new_driver.tabor import TaborAWGRepresentation import pyvisa diff --git a/qupulse/_program/seqc.py b/qupulse/_program/seqc.py index f8ee5673b..4be45e50b 100644 --- a/qupulse/_program/seqc.py +++ b/qupulse/_program/seqc.py @@ -35,7 +35,7 @@ from qupulse._program._loop import Loop from qupulse._program.volatile import VolatileRepetitionCount, VolatileProperty -from qupulse.hardware.awgs.old_base import ProgramEntry +from qupulse.hardware.awgs.base import ProgramEntry try: import zhinst.utils diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 6e40cb45d..047b015d3 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -9,9 +9,8 @@ import numpy as np from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.awgs.base import ProgramEntry +from qupulse.hardware.awgs_new_driver.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 diff --git a/qupulse/examples/VolatileParameters.py b/qupulse/examples/VolatileParameters.py index 53dcc3dab..e8ce07a12 100644 --- a/qupulse/examples/VolatileParameters.py +++ b/qupulse/examples/VolatileParameters.py @@ -29,7 +29,7 @@ awg_channel = awg.channel_pair_AB elif awg_name == 'TABOR': - from qupulse.hardware.awgs.tabor import TaborAWGRepresentation + from qupulse.hardware.awgs_new_driver.tabor import TaborAWGRepresentation awg = TaborAWGRepresentation(awg_address, reset=True) channel_pairs = [] diff --git a/qupulse/hardware/awgs/__init__.py b/qupulse/hardware/awgs/__init__.py index 8d496a2a9..5c0bc4291 100644 --- a/qupulse/hardware/awgs/__init__.py +++ b/qupulse/hardware/awgs/__init__.py @@ -4,7 +4,7 @@ __all__ = ["install_requirements"] try: - from qupulse.hardware.awgs.tabor import TaborDevice, TaborChannelTuple + from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborChannelTuple __all__.extend(["TaborAWGRepresentation", "TaborChannelPair"]) except ImportError: pass diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index 6bb691e4e..dd8750907 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -1,192 +1,156 @@ -from abc import ABC, abstractmethod -from typing import Optional, Tuple, Sequence, Callable, List +"""This module defines the common interface for arbitrary waveform generators. + +Classes: + - AWG: Common AWG interface. + - DummyAWG: A software stub implementation of the AWG interface. + - ProgramOverwriteException + - OutOfWaveformMemoryException +""" + +from abc import abstractmethod +from numbers import Real +from typing import Set, Tuple, Callable, Optional, Mapping, Sequence, List from collections import OrderedDict -import numpy - +from qupulse.hardware.util import get_sample_times +from qupulse.utils.types import ChannelID from qupulse._program._loop import Loop from qupulse._program.waveforms import Waveform -from qupulse.hardware.awgs import channel_tuple_wrapper -from qupulse.hardware.awgs.base_features import Feature, FeatureAble -from qupulse.hardware.util import get_sample_times -from qupulse.utils.types import Collection, TimeType, ChannelID +from qupulse.comparable import Comparable +from qupulse.utils.types import TimeType -__all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature", - "AWGChannelTupleFeature"] +import numpy +__all__ = ["AWG", "Program", "ProgramOverwriteException", + "OutOfWaveformMemoryException", "AWGAmplitudeOffsetHandling"] -class AWGDeviceFeature(Feature, ABC): - """Base class for features that are used for `AWGDevice`s""" - def __init__(self): - super().__init__(AWGDevice) +Program = Loop -class AWGChannelFeature(Feature, ABC): - """Base class for features that are used for `AWGChannel`s""" - def __init__(self): - super().__init__(_BaseAWGChannel) +class AWGAmplitudeOffsetHandling: + IGNORE_OFFSET = 'ignore_offset' # Offset is ignored. + CONSIDER_OFFSET = 'consider_offset' # Offset is discounted from the waveforms. + # TODO OPTIMIZED = 'optimized' # Offset and amplitude are set depending on the waveforms to maximize the waveforms resolution + _valid = [IGNORE_OFFSET, CONSIDER_OFFSET] -class AWGChannelTupleFeature(Feature, ABC): - """Base class for features that are used for `AWGChannelTuple`s""" - def __init__(self): - super().__init__(AWGChannelTuple) +class AWG(Comparable): + """An arbitrary waveform generator abstraction class. -class AWGDevice(FeatureAble[AWGDeviceFeature], ABC): - """Base class for all drivers of all arbitrary waveform generators""" + It represents a set of channels that have to have(hardware enforced) the same: + -control flow + -sample rate - def __init__(self, name: str): - """ - Args: - name: The name of the device as a String - """ - super().__init__() - self._name = name + It keeps track of the AWG state and manages waveforms and programs on the hardware. + """ - #def __del__(self): - # self.cleanup() + def __init__(self, identifier: str): + self._identifier = identifier + self._amplitude_offset_handling = AWGAmplitudeOffsetHandling.IGNORE_OFFSET @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() + def identifier(self) -> str: + return self._identifier @property - @abstractmethod - def channels(self) -> Collection["AWGChannel"]: - """Returns a list of all channels of a Device""" - raise NotImplementedError() + def amplitude_offset_handling(self) -> str: + return self._amplitude_offset_handling + + @amplitude_offset_handling.setter + def amplitude_offset_handling(self, value): + """ + value (str): See possible values at `AWGAmplitudeOffsetHandling` + """ + if value not in AWGAmplitudeOffsetHandling._valid: + raise ValueError('"{}" is invalid as AWGAmplitudeOffsetHandling'.format(value)) + + self._amplitude_offset_handling = value @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() + def num_channels(self): + """Number of channels""" @property @abstractmethod - def channel_tuples(self) -> Collection["AWGChannelTuple"]: - """Returns a list of all channel tuples of a list""" - raise NotImplementedError() - + def num_markers(self): + """Number of marker channels""" -class AWGChannelTuple(FeatureAble[AWGChannelTupleFeature], ABC): - """Base class for all groups of synchronized channels of an AWG""" + @abstractmethod + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], + voltage_transformation: Tuple[Optional[Callable], ...], + force: bool=False) -> None: + """Upload a program to the AWG. + + Physically uploads all waveforms required by the program - excluding those already present - + to the device and sets up playback sequences accordingly. + This method should be cheap for program already on the device and can therefore be used + for syncing. Programs that are uploaded should be fast(~1 sec) to arm. - def __init__(self, idn: int): - """ Args: - idn: The identification number of a channel tuple + name: A name for the program on the AWG. + program: The program (a sequence of instructions) to upload. + channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel + markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel + voltage_transformation: transformations applied to the waveforms extracted rom the program. Position + in the list corresponds to the AWG channel + force: If a different sequence is already present with the same name, it is + overwritten if force is set to True. (default = False) """ - super().__init__() - - self._idn = idn - @property @abstractmethod - def channel_tuple_adapter(self) -> channel_tuple_wrapper: - pass + def remove(self, name: str) -> None: + """Remove a program from the AWG. - @property - def idn(self) -> int: - """Returns the identification number of a channel tuple""" - return self._idn + Also discards all waveforms referenced only by the program identified by name. - @property - def name(self) -> str: - """Returns the name of a channel tuple""" - return "{dev}_CT{idn}".format(dev=self.device.name, idn=self.idn) + Args: + name: The name of the program to remove. + """ - @property @abstractmethod - def sample_rate(self) -> float: - """Returns the sample rate of a channel tuple as a float""" - raise NotImplementedError() + def clear(self) -> None: + """Removes all programs and waveforms from the AWG. - # Optional sample_rate-setter - # @sample_rate.setter - # def sample_rate(self, sample_rate: float) -> None: + Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! + """ - @property @abstractmethod - def device(self) -> AWGDevice: - """Returns the device which the channel tuple belong to""" - raise NotImplementedError() + def arm(self, name: Optional[str]) -> None: + """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current + program.""" @property @abstractmethod - def channels(self) -> Collection["AWGChannel"]: - """Returns a list of all channels of the channel tuple""" - raise NotImplementedError() + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" @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 + def sample_rate(self) -> float: + """The sample rate of the AWG.""" @property - def idn(self) -> int: - """Returns the identification number of a channel""" - return self._idn + def compare_key(self) -> int: + """Comparison and hashing is based on the id of the AWG so different devices with the same properties + are ot equal""" + return id(self) - @property @abstractmethod - def device(self) -> AWGDevice: - """Returns the device which the channel belongs to""" - raise NotImplementedError() + def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, Real]): + """Set the values of parameters which were marked as volatile on program creation.""" - @property - @abstractmethod - def channel_tuple(self) -> Optional[AWGChannelTuple]: - """Returns the channel tuple which a channel belongs to""" + def __copy__(self) -> None: 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 - """ + def __deepcopy__(self, memodict={}) -> None: 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(Exception): def __init__(self, name) -> None: diff --git a/qupulse/hardware/awgs/old_base.py b/qupulse/hardware/awgs/old_base.py deleted file mode 100644 index dd8750907..000000000 --- a/qupulse/hardware/awgs/old_base.py +++ /dev/null @@ -1,259 +0,0 @@ -"""This module defines the common interface for arbitrary waveform generators. - -Classes: - - AWG: Common AWG interface. - - DummyAWG: A software stub implementation of the AWG interface. - - ProgramOverwriteException - - OutOfWaveformMemoryException -""" - -from abc import abstractmethod -from numbers import Real -from typing import Set, Tuple, Callable, Optional, Mapping, Sequence, List -from collections import OrderedDict - -from qupulse.hardware.util import get_sample_times -from qupulse.utils.types import ChannelID -from qupulse._program._loop import Loop -from qupulse._program.waveforms import Waveform -from qupulse.comparable import Comparable -from qupulse.utils.types import TimeType - -import numpy - -__all__ = ["AWG", "Program", "ProgramOverwriteException", - "OutOfWaveformMemoryException", "AWGAmplitudeOffsetHandling"] - -Program = Loop - - -class AWGAmplitudeOffsetHandling: - IGNORE_OFFSET = 'ignore_offset' # Offset is ignored. - CONSIDER_OFFSET = 'consider_offset' # Offset is discounted from the waveforms. - # TODO OPTIMIZED = 'optimized' # Offset and amplitude are set depending on the waveforms to maximize the waveforms resolution - - _valid = [IGNORE_OFFSET, CONSIDER_OFFSET] - - -class AWG(Comparable): - """An arbitrary waveform generator abstraction class. - - It represents a set of channels that have to have(hardware enforced) the same: - -control flow - -sample rate - - It keeps track of the AWG state and manages waveforms and programs on the hardware. - """ - - def __init__(self, identifier: str): - self._identifier = identifier - self._amplitude_offset_handling = AWGAmplitudeOffsetHandling.IGNORE_OFFSET - - @property - def identifier(self) -> str: - return self._identifier - - @property - def amplitude_offset_handling(self) -> str: - return self._amplitude_offset_handling - - @amplitude_offset_handling.setter - def amplitude_offset_handling(self, value): - """ - value (str): See possible values at `AWGAmplitudeOffsetHandling` - """ - if value not in AWGAmplitudeOffsetHandling._valid: - raise ValueError('"{}" is invalid as AWGAmplitudeOffsetHandling'.format(value)) - - self._amplitude_offset_handling = value - - @property - @abstractmethod - def num_channels(self): - """Number of channels""" - - @property - @abstractmethod - def num_markers(self): - """Number of marker channels""" - - @abstractmethod - def upload(self, name: str, - program: Loop, - channels: Tuple[Optional[ChannelID], ...], - markers: Tuple[Optional[ChannelID], ...], - voltage_transformation: Tuple[Optional[Callable], ...], - force: bool=False) -> None: - """Upload a program to the AWG. - - Physically uploads all waveforms required by the program - excluding those already present - - to the device and sets up playback sequences accordingly. - This method should be cheap for program already on the device and can therefore be used - for syncing. Programs that are uploaded should be fast(~1 sec) to arm. - - Args: - name: A name for the program on the AWG. - program: The program (a sequence of instructions) to upload. - channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel - markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel - voltage_transformation: transformations applied to the waveforms extracted rom the program. Position - in the list corresponds to the AWG channel - force: If a different sequence is already present with the same name, it is - overwritten if force is set to True. (default = False) - """ - - @abstractmethod - def remove(self, name: str) -> None: - """Remove a program from the AWG. - - Also discards all waveforms referenced only by the program identified by name. - - Args: - name: The name of the program to remove. - """ - - @abstractmethod - def clear(self) -> None: - """Removes all programs and waveforms from the AWG. - - Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! - """ - - @abstractmethod - def arm(self, name: Optional[str]) -> None: - """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current - program.""" - - @property - @abstractmethod - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - - @property - @abstractmethod - def sample_rate(self) -> float: - """The sample rate of the AWG.""" - - @property - def compare_key(self) -> int: - """Comparison and hashing is based on the id of the AWG so different devices with the same properties - are ot equal""" - return id(self) - - @abstractmethod - def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, Real]): - """Set the values of parameters which were marked as volatile on program creation.""" - - def __copy__(self) -> None: - raise NotImplementedError() - - def __deepcopy__(self, memodict={}) -> None: - raise NotImplementedError() - - -class ProgramOverwriteException(Exception): - - 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) - - -class ProgramEntry: - """This is a helper class for implementing awgs drivers. A driver can subclass it to help organizing sampled - waveforms""" - def __init__(self, loop: Loop, - channels: Tuple[Optional[ChannelID], ...], - markers: Tuple[Optional[ChannelID], ...], - amplitudes: Tuple[float, ...], - offsets: Tuple[float, ...], - voltage_transformations: Tuple[Optional[Callable], ...], - sample_rate: TimeType, - waveforms: Sequence[Waveform] = None): - """ - - Args: - loop: - channels: - markers: - amplitudes: - offsets: - voltage_transformations: - sample_rate: - waveforms: These waveforms are sampled and stored in _waveforms. If None the waveforms are extracted from - loop - """ - assert len(channels) == len(amplitudes) == len(offsets) == len(voltage_transformations) - - self._channels = tuple(channels) - self._markers = tuple(markers) - self._amplitudes = tuple(amplitudes) - self._offsets = tuple(offsets) - self._voltage_transformations = tuple(voltage_transformations) - - self._sample_rate = sample_rate - - self._loop = loop - - if waveforms is None: - waveforms = OrderedDict((node.waveform, None) - for node in loop.get_depth_first_iterator() if node.is_leaf()).keys() - if waveforms: - self._waveforms = OrderedDict(zip(waveforms, self._sample_waveforms(waveforms))) - else: - self._waveforms = OrderedDict() - - def _sample_empty_channel(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: - """Override this in derived class to change how """ - return None - - def _sample_empty_marker(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: - return None - - def _sample_waveforms(self, waveforms: Sequence[Waveform]) -> List[Tuple[Tuple[numpy.ndarray, ...], - Tuple[numpy.ndarray, ...]]]: - sampled_waveforms = [] - - time_array, segment_lengths = get_sample_times(waveforms, self._sample_rate) - for waveform, segment_length in zip(waveforms, segment_lengths): - wf_time = time_array[:segment_length] - - sampled_channels = [] - for channel, trafo, amplitude, offset in zip(self._channels, self._voltage_transformations, - self._amplitudes, self._offsets): - if channel is None: - sampled_channels.append(self._sample_empty_channel()) - else: - sampled = waveform.get_sampled(channel, wf_time) - if trafo is not None: - sampled = trafo(sampled) - sampled = sampled - offset - sampled /= amplitude - sampled_channels.append(waveform.get_sampled(channel, wf_time)) - - sampled_markers = [] - for marker in self._markers: - if marker is None: - sampled_markers.append(None) - else: - sampled_markers.append(waveform.get_sampled(marker, wf_time) != 0) - - sampled_waveforms.append((tuple(sampled_channels), tuple(sampled_markers))) - return sampled_waveforms - - -class OutOfWaveformMemoryException(Exception): - - def __str__(self) -> str: - return "Out of memory error adding waveform to waveform memory." - - -class ChannelNotFoundException(Exception): - def __init__(self, channel): - self.channel = channel - - def __str__(self) -> str: - return 'Marker or channel not found: {}'.format(self.channel) diff --git a/qupulse/hardware/awgs/old_tabor.py b/qupulse/hardware/awgs/old_tabor.py deleted file mode 100644 index 6b664d867..000000000 --- a/qupulse/hardware/awgs/old_tabor.py +++ /dev/null @@ -1,943 +0,0 @@ -import fractions -import functools -import weakref -import logging -import numbers -from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple -from collections import OrderedDict - -# Provided by Tabor electronics for python 2.7 -# a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech -# Beware of the string encoding change! -import teawg -import numpy as np - -from qupulse.utils.types import ChannelID -from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.util import voltage_to_uint16, find_positions -from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling -from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ - make_combined_wave - - -__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] - - -class TaborAWGRepresentation: - def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): - """ - :param instr_addr: Instrument address that is forwarded to teawg - :param paranoia_level: Paranoia level that is forwarded to teawg - :param external_trigger: Not supported yet - :param reset: - :param mirror_addresses: - """ - self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) - self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) - self._coupled = None - - self._clock_marker = [0, 0, 0, 0] - - if external_trigger: - raise NotImplementedError() # pragma: no cover - - if reset: - self.send_cmd(':RES') - - self.initialize() - - self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') - self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') - - def is_coupled(self) -> bool: - if self._coupled is None: - return self.send_query(':INST:COUP:STAT?') == 'ON' - else: - return self._coupled - - @property - def channel_pair_AB(self) -> 'TaborChannelPair': - return self._channel_pair_AB - - @property - def channel_pair_CD(self) -> 'TaborChannelPair': - return self._channel_pair_CD - - @property - def main_instrument(self) -> teawg.TEWXAwg: - return self._instr - - @property - def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: - return self._mirrors - - @property - def paranoia_level(self) -> int: - return self._instr.paranoia_level - - @paranoia_level.setter - def paranoia_level(self, val): - for instr in self.all_devices: - instr.paranoia_level = val - - @property - def dev_properties(self) -> dict: - return self._instr.dev_properties - - @property - def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr, ) + self._mirrors - - def send_cmd(self, cmd_str, paranoia_level=None): - for instr in self.all_devices: - instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) - - def send_query(self, query_str, query_mirrors=False) -> Any: - if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self.all_devices) - else: - return self._instr.send_query(query_str) - - def send_binary_data(self, pref, bin_dat, paranoia_level=None): - for instr in self.all_devices: - instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) - - def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): - for instr in self.all_devices: - instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) - - def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): - for instr in self.all_devices: - instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) - - def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): - for instr in self.all_devices: - instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) - - make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) - - def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: - """Overwrite send_cmd for paranoia_level > 3""" - if paranoia_level is None: - paranoia_level = self.paranoia_level - - if paranoia_level < 3: - super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover - else: - cmd_str = cmd_str.rstrip() - - if len(cmd_str) > 0: - ask_str = cmd_str + '; *OPC?; :SYST:ERR?' - else: - ask_str = '*OPC?; :SYST:ERR?' - - *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(';') - - error_code, error_msg = error_code_msg.split(',') - error_code = int(error_code) - if error_code != 0: - _ = self._visa_inst.ask('*CLS; *OPC?') - - if error_code == -450: - # query queue overflow - self.send_cmd(cmd_str) - else: - raise RuntimeError('Cannot execute command: {}\n{}: {}'.format(cmd_str, error_code, error_msg)) - - assert len(answers) == 0 - - def get_status_table(self) -> Dict[str, Union[str, float, int]]: - """Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame - - Returns: - An ordered dictionary with the results - """ - name_query_type_list = [('channel', ':INST:SEL?', int), - ('coupling', ':OUTP:COUP?', str), - ('volt_dc', ':SOUR:VOLT:LEV:AMPL:DC?', float), - ('volt_hv', ':VOLT:HV?', float), - ('offset', ':VOLT:OFFS?', float), - ('outp', ':OUTP?', str), - ('mode', ':SOUR:FUNC:MODE?', str), - ('shape', ':SOUR:FUNC:SHAPE?', str), - ('dc_offset', ':SOUR:DC?', float), - ('freq_rast', ':FREQ:RAST?', float), - - ('gated', ':INIT:GATE?', str), - ('continuous', ':INIT:CONT?', str), - ('continuous_enable', ':INIT:CONT:ENAB?', str), - ('continuous_source', ':INIT:CONT:ENAB:SOUR?', str), - ('marker_source', ':SOUR:MARK:SOUR?', str), - ('seq_jump_event', ':SOUR:SEQ:JUMP:EVEN?', str), - ('seq_adv_mode', ':SOUR:SEQ:ADV?', str), - ('aseq_adv_mode', ':SOUR:ASEQ:ADV?', str), - - ('marker', ':SOUR:MARK:SEL?', int), - ('marker_high', ':MARK:VOLT:HIGH?', str), - ('marker_low', ':MARK:VOLT:LOW?', str), - ('marker_width', ':MARK:WIDT?', int), - ('marker_state', ':MARK:STAT?', str)] - - data = OrderedDict((name, []) for name, *_ in name_query_type_list) - for ch in (1, 2, 3, 4): - self.select_channel(ch) - self.select_marker((ch-1) % 2 + 1) - for name, query, dtype in name_query_type_list: - data[name].append(dtype(self.send_query(query))) - return data - - @property - def is_open(self) -> bool: - return self._instr.visa_inst is not None # pragma: no cover - - def select_channel(self, channel) -> None: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - - self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) - - def select_marker(self, marker: int) -> None: - """Select marker 1 or 2 of the currently active channel pair.""" - if marker not in (1, 2): - raise TaborException('Invalid marker: {}'.format(marker)) - self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker)) - - def sample_rate(self, channel) -> int: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) - - def amplitude(self, channel) -> float: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) - if coupling == 'DC': - return float(self.send_query(':VOLT?')) - elif coupling == 'HV': - return float(self.send_query(':VOLT:HV?')) - else: - raise TaborException('Unknown coupling: {}'.format(coupling)) - - def offset(self, channel: int) -> float: - if channel not in (1, 2, 3, 4): - raise TaborException('Invalid channel: {}'.format(channel)) - return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) - - def enable(self) -> None: - self.send_cmd(':ENAB') - - def abort(self) -> None: - self.send_cmd(':ABOR') - - def initialize(self) -> None: - # 1. Select channel - # 2. Turn off gated mode - # 3. continous mode - # 4. Armed mode (onlz generate waveforms after enab command) - # 5. Expect enable signal from (USB / LAN / GPIB) - # 6. Use arbitrary waveforms as marker source - # 7. Expect jump command for sequencing from (USB / LAN / GPIB) - setup_command = ( - ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " - ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") - self.send_cmd(':INST:SEL 1') - self.send_cmd(setup_command) - self.send_cmd(':INST:SEL 3') - self.send_cmd(setup_command) - - def reset(self) -> None: - self.send_cmd(':RES') - self._coupled = None - self.initialize() - self.channel_pair_AB.clear() - self.channel_pair_CD.clear() - - def trigger(self) -> None: - self.send_cmd(':TRIG') - - def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: - for device in self.all_devices: - if device.fw_ver >= 3.0: - if simulator: - if device.is_simulator: - return device - else: - return device - raise TaborException('No device capable of device data read') - - -TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), - ('program', TaborProgram)]) - - -def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], - Any]: - """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" - @functools.wraps(function_object) - def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: - if channel_pair._configuration_guard_count == 0: - channel_pair._enter_config_mode() - channel_pair._configuration_guard_count += 1 - - try: - return function_object(channel_pair, *args, **kwargs) - finally: - channel_pair._configuration_guard_count -= 1 - if channel_pair._configuration_guard_count == 0: - channel_pair._exit_config_mode() - - return guarding_method - - -def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: - """Asserts the channel pair is selected when the wrapped function is called""" - @functools.wraps(function_object) - def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: - channel_pair.select() - return function_object(channel_pair, *args, **kwargs) - - return selector - - -class TaborChannelPair(AWG): - CONFIG_MODE_PARANOIA_LEVEL = None - - def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): - super().__init__(identifier) - self._device = weakref.ref(tabor_device) - - self._configuration_guard_count = 0 - self._is_in_config_mode = False - - if channels not in ((1, 2), (3, 4)): - raise ValueError('Invalid channel pair: {}'.format(channels)) - self._channels = channels - - self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - voltage_to_uint16(voltage=np.zeros(192), - output_amplitude=0.5, - output_offset=0., resolution=14), - None, None) - self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] - - self._known_programs = dict() # type: Dict[str, TaborProgramMemory] - self._current_program = None - - self._segment_lengths = None - self._segment_capacity = None - self._segment_hashes = None - self._segment_references = None - - self._sequencer_tables = None - self._advanced_sequence_table = None - - self._internal_paranoia_level = 0 - - self.clear() - - @property - def internal_paranoia_level(self) -> Optional[int]: - return self._internal_paranoia_level - - @internal_paranoia_level.setter - def internal_paranoia_level(self, paranoia_level: Optional[int]): - """ Sets the paranoia level with which commands from within methods are called """ - assert paranoia_level in (None, 0, 1, 2) - self._internal_paranoia_level = paranoia_level - - def select(self) -> None: - self.device.send_cmd(':INST:SEL {}'.format(self._channels[0]), - paranoia_level=self.internal_paranoia_level) - - @property - def total_capacity(self) -> int: - return int(self.device.dev_properties['max_arb_mem']) // 2 - - @property - def logger(self): - return logging.getLogger("qupulse.tabor") - - @property - def device(self) -> TaborAWGRepresentation: - return self._device() - - def free_program(self, name: str) -> TaborProgramMemory: - if name is None: - raise TaborException('Removing "None" program is forbidden.') - program = self._known_programs.pop(name) - self._segment_references[program.waveform_to_segment] -= 1 - if self._current_program == name: - self.change_armed_program(None) - return program - - def _restore_program(self, name: str, program: TaborProgram) -> None: - if name in self._known_programs: - raise ValueError('Program cannot be restored as it is already known.') - self._segment_references[program.waveform_to_segment] += 1 - self._known_programs[name] = program - - @property - def _segment_reserved(self) -> np.ndarray: - return self._segment_references > 0 - - @property - def _free_points_in_total(self) -> int: - return self.total_capacity - np.sum(self._segment_capacity[self._segment_reserved]) - - @property - def _free_points_at_end(self) -> int: - reserved_index = np.flatnonzero(self._segment_reserved) - if len(reserved_index): - return self.total_capacity - np.sum(self._segment_capacity[:reserved_index[-1]]) - else: - return self.total_capacity - - @with_select - def read_waveforms(self) -> List[np.ndarray]: - device = self.device.get_readable_device(simulator=True) - - old_segment = device.send_query(':TRAC:SEL?') - waveforms = [] - uploaded_waveform_indices = np.flatnonzero(self._segment_references) + 1 - for segment in uploaded_waveform_indices: - device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) - waveforms.append(device.read_act_seg_dat()) - device.send_cmd(':TRAC:SEL {}'.format(old_segment), paranoia_level=self.internal_paranoia_level) - return waveforms - - @with_select - def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: - device = self.device.get_readable_device(simulator=True) - - old_sequence = device.send_query(':SEQ:SEL?') - sequences = [] - uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 - for sequence in uploaded_sequence_indices: - device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) - sequences.append(device.read_sequencer_table()) - device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) - return sequences - - @with_select - def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - return self.device.get_readable_device(simulator=True).read_adv_seq_table() - - def read_complete_program(self) -> PlottableProgram: - return PlottableProgram.from_read_data(self.read_waveforms(), - self.read_sequence_tables(), - self.read_advanced_sequencer_table()) - - @with_configuration_guard - @with_select - def upload(self, name: str, - program: Loop, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - markers: Tuple[Optional[ChannelID], Optional[ChannelID]], - voltage_transformation: Tuple[Callable, Callable], - force: bool = False) -> None: - """Upload a program to the AWG. - - The policy is to prefer amending the unknown waveforms to overwriting old ones.""" - - if len(channels) != self.num_channels: - raise ValueError('Channel ID not specified') - if len(markers) != self.num_markers: - raise ValueError('Markers not specified') - if len(voltage_transformation) != self.num_channels: - raise ValueError('Wrong number of voltage transformations') - - # adjust program to fit criteria - sample_rate = self.device.sample_rate(self._channels[0]) - make_compatible(program, - minimal_waveform_length=192, - waveform_quantum=16, - sample_rate=fractions.Fraction(sample_rate, 10**9)) - - if name in self._known_programs: - if force: - self.free_program(name) - else: - raise ValueError('{} is already known on {}'.format(name, self.identifier)) - - # They call the peak to peak range amplitude - ranges = (self.device.amplitude(self._channels[0]), - self.device.amplitude(self._channels[1])) - - voltage_amplitudes = (ranges[0]/2, ranges[1]/2) - - if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: - voltage_offsets = (0, 0) - elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets = (self.device.offset(self._channels[0]), - self.device.offset(self._channels[1])) - else: - raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) - - # parse to tabor program - tabor_program = TaborProgram(program, - channels=tuple(channels), - markers=markers, - device_properties=self.device.dev_properties, - sample_rate=sample_rate / 10**9, - amplitudes=voltage_amplitudes, - offsets=voltage_offsets, - voltage_transformations=voltage_transformation) - - segments, segment_lengths = tabor_program.get_sampled_segments() - - waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, - segment_lengths) - - self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 - - for wf_index in np.flatnonzero(to_insert > 0): - segment_index = to_insert[wf_index] - self._upload_segment(to_insert[wf_index], segments[wf_index]) - waveform_to_segment[wf_index] = segment_index - - if np.any(to_amend): - segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] - waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) - - self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, - program=tabor_program) - - @with_configuration_guard - @with_select - def clear(self) -> None: - """Delete all segments and clear memory""" - self.device.select_channel(self._channels[0]) - self.device.send_cmd(':TRAC:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':SOUR:SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':ASEQ:DEL', paranoia_level=self.internal_paranoia_level) - - self.device.send_cmd(':TRAC:DEF 1, 192', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:SEL 1', paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) - - self._segment_lengths = 192*np.ones(1, dtype=np.uint32) - self._segment_capacity = 192*np.ones(1, dtype=np.uint32) - self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) - self._segment_references = np.ones(1, dtype=np.uint32) - - self._advanced_sequence_table = [] - self._sequencer_tables = [] - - self._known_programs = dict() - self.change_armed_program(None) - - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - 1. Find known segments - 2. Find empty spaces with fitting length - 3. Find empty spaces with bigger length - 4. Amend remaining segments - :param segments: - :param segment_lengths: - :return: - """ - segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) - - waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) - - # separate into known and unknown - unknown = (waveform_to_segment == -1) - known = ~unknown - - known_pos_in_memory = waveform_to_segment[known] - - assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) - - new_reference_counter = self._segment_references.copy() - new_reference_counter[known_pos_in_memory] += 1 - - to_upload_size = np.sum(segment_lengths[unknown] + 16) - free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) - if free_points_in_total < to_upload_size: - raise MemoryError('Not enough free memory', - free_points_in_total, - to_upload_size, - self._free_points_in_total) - - to_amend = cast(np.ndarray, unknown) - to_insert = np.full(len(segments), fill_value=-1, dtype=np.int64) - - reserved_indices = np.flatnonzero(new_reference_counter > 0) - first_free = reserved_indices[-1] + 1 if len(reserved_indices) else 0 - - free_segments = new_reference_counter[:first_free] == 0 - free_segment_count = np.sum(free_segments) - - # look for a free segment place with the same length - for segment_idx in np.flatnonzero(to_amend): - if free_segment_count == 0: - break - - pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) - idx_same_length = np.argmax(pos_of_same_length) - if pos_of_same_length[idx_same_length]: - free_segments[idx_same_length] = False - free_segment_count -= 1 - - to_amend[segment_idx] = False - to_insert[segment_idx] = idx_same_length - - # try to find places that are larger than the segments to fit in starting with the large segments and large - # free spaces - segment_indices = np.flatnonzero(to_amend)[np.argsort(segment_lengths[to_amend])[::-1]] - capacities = self._segment_capacity[:first_free] - for segment_idx in segment_indices: - free_capacities = capacities[free_segments] - free_segments_indices = np.flatnonzero(free_segments)[np.argsort(free_capacities)[::-1]] - - if len(free_segments_indices) == 0: - break - - fitting_segment = np.argmax((free_capacities >= segment_lengths[segment_idx])[::-1]) - fitting_segment = free_segments_indices[fitting_segment] - if self._segment_capacity[fitting_segment] >= segment_lengths[segment_idx]: - free_segments[fitting_segment] = False - to_amend[segment_idx] = False - to_insert[segment_idx] = fitting_segment - - free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) - if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: - raise MemoryError('Fragmentation does not allow upload.', - np.sum(segment_lengths[to_amend] + 16), - free_points_at_end, - self._free_points_at_end) - - return waveform_to_segment, to_amend, to_insert - - @with_select - @with_configuration_guard - def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: - if self._segment_references[segment_index] > 0: - raise ValueError('Reference count not zero') - if segment.num_points > self._segment_capacity[segment_index]: - raise ValueError('Cannot upload segment here.') - - segment_no = segment_index + 1 - - self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), - paranoia_level=self.internal_paranoia_level) - self._segment_lengths[segment_index] = segment.num_points - - self.device.send_cmd(':TRAC:SEL {}'.format(segment_no), paranoia_level=self.internal_paranoia_level) - - self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) - wf_data = segment.get_as_binary() - - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) - self._segment_references[segment_index] = 1 - self._segment_hashes[segment_index] = hash(segment) - - @with_select - @with_configuration_guard - def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: - new_lengths = np.asarray([s.num_points for s in segments], dtype=np.uint32) - - wf_data = make_combined_wave(segments) - trac_len = len(wf_data) // 2 - - segment_index = len(self._segment_capacity) - first_segment_number = segment_index + 1 - self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len), - paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number), - paranoia_level=self.internal_paranoia_level) - self.device.send_cmd(':TRAC:MODE COMB', - paranoia_level=self.internal_paranoia_level) - self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) - - old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) - segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) - segment_lengths = np.concatenate((self._segment_lengths, new_lengths)) - segment_references = np.concatenate((self._segment_references, np.ones(len(segments), dtype=int))) - segment_hashes = np.concatenate((self._segment_hashes, [hash(s) for s in segments])) - if len(segments) < old_to_update: - for i, segment in enumerate(segments): - current_segment_number = first_segment_number + i - self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points), - paranoia_level=self.internal_paranoia_level) - else: - # flush the capacity - self.device.download_segment_lengths(segment_capacity) - - # update non fitting lengths - for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i]), - paranoia_level=self.internal_paranoia_level) - - self._segment_capacity = segment_capacity - self._segment_lengths = segment_lengths - self._segment_hashes = segment_hashes - self._segment_references = segment_references - - return segment_index + np.arange(len(segments), dtype=np.int64) - - @with_select - @with_configuration_guard - def cleanup(self) -> None: - """Discard all segments after the last which is still referenced""" - reserved_indices = np.flatnonzero(self._segment_references > 0) - old_end = len(self._segment_lengths) - new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 - self._segment_lengths = self._segment_lengths[:new_end] - self._segment_capacity = self._segment_capacity[:new_end] - self._segment_hashes = self._segment_hashes[:new_end] - self._segment_references = self._segment_references[:new_end] - - try: - # send max 10 commands at once - chunk_size = 10 - for chunk_start in range(new_end, old_end, chunk_size): - self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) - for i in range(chunk_start, min(chunk_start+chunk_size, old_end))), - paranoia_level=self.internal_paranoia_level) - except Exception as e: - raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e - - def remove(self, name: str) -> None: - """Remove a program from the AWG. - - Also discards all waveforms referenced only by the program identified by name. - - Args: - name (str): The name of the program to remove. - """ - self.free_program(name) - self.cleanup() - - @with_configuration_guard - def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: - """ Joins the given commands into one and executes it with configuration guard. - - Args: - commands: Commands that should be executed. - """ - cmd_str = ";".join(commands) - self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) - - def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: - """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters - in program memory and device's (adv.) sequence tables if program is current program. - - If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to - enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) - and set device._is_coupled. - - Args: - program_name: Name of program which should be changed. - parameters: Names of volatile parameters and respective values to which they should be set. - """ - - waveform_to_segment_index, program = self._known_programs[program_name] - - modifications = program.update_volatile_parameters(parameters) - - self.logger.debug("parameter modifications: %r" % modifications) - - if not modifications: - self.logger.info("There are no volatile parameters to update. Either there are no volatile parameters with " - "these names,\nthe respective repetition counts already have the given values or the " - "volatile parameters were dropped during upload.") - return - - if program_name == self._current_program: - commands = [] - - for position, entry in modifications.items(): - if not entry.repetition_count > 0: - raise ValueError('Repetition must be > 0') - - if isinstance(position, int): - commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, - entry.repetition_count, entry.jump_flag)) - else: - table_num, step_num = position - commands.append(":SEQ:SEL {}".format(table_num + 2)) - commands.append(":SEQ:DEF {},{},{},{}".format(step_num, - waveform_to_segment_index[entry.element_id] + 1, - entry.repetition_count, entry.jump_flag)) - self._execute_multiple_commands_with_config_guard(commands) - - # Wait until AWG is finished - _ = self.device.main_instrument._visa_inst.query('*OPC?') - - def set_marker_state(self, marker: int, active: bool) -> None: - """Sets the marker state of this channel pair. - According to the manual one cannot turn them off/on separately.""" - command_string = ':INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}' - command_string = command_string.format( - channel=self._channels[0], - marker=(1, 2)[marker], - active='ON' if active else 'OFF') - self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) - - def set_channel_state(self, channel, active) -> None: - command_string = ':INST:SEL {}; :OUTP {}'.format(self._channels[channel], 'ON' if active else 'OFF') - self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) - - @with_select - def arm(self, name: str) -> None: - if self._current_program == name: - self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) - else: - self.change_armed_program(name) - - def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): - self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table - - def set_program_sequence_table(self, name, new_sequence_table): - self._known_programs[name][1]._sequencer_tables = new_sequence_table - - @with_select - @with_configuration_guard - def change_armed_program(self, name: Optional[str]) -> None: - if name is None: - sequencer_tables = [self._idle_sequence_table] - advanced_sequencer_table = [(1, 1, 0)] - else: - waveform_to_segment_index, program = self._known_programs[name] - waveform_to_segment_number = waveform_to_segment_index + 1 - - # translate waveform number to actual segment - sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for ((rep_count, wf_index, jump_flag), _) in sequencer_table] - for sequencer_table in program.get_sequencer_tables()] - - # insert idle sequence - sequencer_tables = [self._idle_sequence_table] + sequencer_tables - - # adjust advanced sequence table entries by idle sequence table offset - advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) - for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] - - if program.waveform_mode == TaborSequencing.SINGLE: - assert len(advanced_sequencer_table) == 1 - assert len(sequencer_tables) == 2 - - while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: - assert advanced_sequencer_table[0][0] == 1 - sequencer_tables[1].append((1, 1, 0)) - - # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table - - while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: - advanced_sequencer_table.append((1, 1, 0)) - - # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs - self.device.send_cmd('SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) - self._sequencer_tables = [] - self.device.send_cmd('ASEQ:DEL', paranoia_level=self.internal_paranoia_level) - self._advanced_sequence_table = [] - - # download all sequence tables - for i, sequencer_table in enumerate(sequencer_tables): - self.device.send_cmd('SEQ:SEL {}'.format(i+1), paranoia_level=self.internal_paranoia_level) - self.device.download_sequencer_table(sequencer_table) - self._sequencer_tables = sequencer_tables - self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) - - self.device.download_adv_seq_table(advanced_sequencer_table) - self._advanced_sequence_table = advanced_sequencer_table - - self._current_program = name - - @with_select - def run_current_program(self) -> None: - if self._current_program: - self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) - else: - raise RuntimeError('No program active') - - @property - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - return set(program for program in self._known_programs.keys()) - - @property - def sample_rate(self) -> float: - return self.device.sample_rate(self._channels[0]) - - @property - def num_channels(self) -> int: - return 2 - - @property - def num_markers(self) -> int: - return 2 - - def _enter_config_mode(self) -> None: - """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the - sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. - When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip.""" - if self._is_in_config_mode is False: - - # 1. Select channel pair - # 2. Select DC as function shape - # 3. Select build-in waveform mode - - if self.device.is_coupled(): - out_cmd = ':OUTP:ALL OFF' - else: - out_cmd = ':INST:SEL {};:OUTP OFF;:INST:SEL {};:OUTP OFF'.format(*self._channels) - - marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' - - wf_mode_cmd = ':SOUR:FUNC:MODE FIX' - - cmd = ';'.join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) - self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = True - - def _exit_config_mode(self) -> None: - """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" - - sel_ch = ':INST:SEL {}'.format(self._channels[0]) - aseq_cmd = ':SOUR:FUNC:MODE ASEQ;SEQ:SEL 1' - - cmds = [sel_ch, aseq_cmd] - - if self.device.is_coupled(): - # Coupled -> switch all channels at once - if self._channels == (1, 2): - other_channel_pair = self.device.channel_pair_CD - else: - assert self._channels == (3, 4) - other_channel_pair = self.device.channel_pair_AB - - if not other_channel_pair._is_in_config_mode: - cmds.append(':OUTP:ALL ON') - - else: - # ch 0 already selected - cmds.append(':OUTP ON; :INST:SEL {}; :OUTP ON'.format(self._channels[1])) - - cmds.append(':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') - cmds.append(':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') - cmd = ';'.join(cmds) - self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = False - - -class TaborUndefinedState(TaborException): - """If this exception is raised the attached tabor device is in an undefined state. - It is highly recommended to call reset it.""" - - def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): - super().__init__(*args) - self.device = device - - def reset_device(self): - if isinstance(self.device, TaborAWGRepresentation): - self.device.reset() - elif isinstance(self.device, TaborChannelPair): - self.device.clear() diff --git a/qupulse/hardware/awgs/tabor.py b/qupulse/hardware/awgs/tabor.py index 721f166a2..90e7b1831 100644 --- a/qupulse/hardware/awgs/tabor.py +++ b/qupulse/hardware/awgs/tabor.py @@ -1,326 +1,67 @@ +import fractions import functools +import weakref import logging import numbers -import sys -import weakref -from typing import List, Tuple, Set, Callable, Optional, Any, cast, Union, Dict, Mapping, NamedTuple, Iterable +from typing import List, Tuple, Set, Callable, Optional, Any, Sequence, cast, Union, Dict, Mapping, NamedTuple from collections import OrderedDict -import numpy as np -from qupulse import ChannelID -from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.awgs.channel_tuple_wrapper import ChannelTupleAdapter -from qupulse.hardware.awgs.features import ChannelSynchronization, AmplitudeOffsetHandling, VoltageRange, \ - ProgramManagement, ActivatableChannels, DeviceControl, StatusTable, SCPI, RepetionMode, VolatileParameters, \ - ReadProgram -from qupulse.hardware.util import voltage_to_uint16, find_positions, get_sample_times -from qupulse.utils.types import Collection, TimeType -from qupulse.hardware.awgs.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel -from typing import Sequence -from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ - make_combined_wave -import pyvisa -import warnings # Provided by Tabor electronics for python 2.7 # a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech # Beware of the string encoding change! import teawg +import numpy as np -assert (sys.byteorder == "little") - -__all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] - -TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), - ("program", TaborProgram)]) - - -def with_configuration_guard(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[ - ["TaborChannelTuple"], Any]: - """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" - - @functools.wraps(function_object) - def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: - - if channel_pair._configuration_guard_count == 0: - channel_pair._enter_config_mode() - channel_pair._configuration_guard_count += 1 - - try: - return function_object(channel_pair, *args, **kwargs) - finally: - channel_pair._configuration_guard_count -= 1 - if channel_pair._configuration_guard_count == 0: - channel_pair._exit_config_mode() - - return guarding_method - - -def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: - """Asserts the channel pair is selcted when the wrapped function is called""" - - @functools.wraps(function_object) - def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: - channel_tuple._select() - return function_object(channel_tuple, *args, **kwargs) - - return selector - - -######################################################################################################################## -# Device -######################################################################################################################## -# Features -class TaborSCPI(SCPI): - def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedResource): - super().__init__(visa) - - self._parent = weakref.ref(device) - - def send_cmd(self, cmd_str, paranoia_level=None): - print(cmd_str) - for instr in self._parent().all_devices: - instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) - - def send_query(self, query_str, query_mirrors=False) -> Any: - if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self._parent().all_devices) - else: - return self._parent().main_instrument.send_query(query_str) - - def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: - """Overwrite send_cmd for paranoia_level > 3""" - if paranoia_level is None: - paranoia_level = self._parent().paranoia_level - - if paranoia_level < 3: - self._parent().super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover - else: - cmd_str = cmd_str.rstrip() - - if len(cmd_str) > 0: - ask_str = cmd_str + "; *OPC?; :SYST:ERR?" - else: - ask_str = "*OPC?; :SYST:ERR?" - - *answers, opc, error_code_msg = self._parent()._visa_inst.ask(ask_str).split(";") - - error_code, error_msg = error_code_msg.split(",") - error_code = int(error_code) - if error_code != 0: - _ = self._parent()._visa_inst.ask("*CLS; *OPC?") - - if error_code == -450: - # query queue overflow - self.send_cmd(cmd_str) - else: - raise RuntimeError("Cannot execute command: {}\n{}: {}".format(cmd_str, error_code, error_msg)) - - assert len(answers) == 0 - - -class TaborChannelSynchronization(ChannelSynchronization): - """This Feature is used to synchronise a certain ammount of channels""" - - def __init__(self, device: "TaborDevice"): - super().__init__() - self._parent = weakref.ref(device) - - def synchronize_channels(self, group_size: int) -> None: - """ - Synchronize in groups of `group_size` channels. Groups of synchronized channels will be provided as - AWGChannelTuples. The channel_size must be evenly dividable by the number of channels - - Args: - group_size: Number of channels per channel tuple - """ - if group_size == 2: - self._parent()._channel_tuples = [] - for i in range((int)(len(self._parent().channels) / group_size)): - self._parent()._channel_tuples.append( - TaborChannelTuple((i + 1), - self._parent(), - self._parent().channels[(i * group_size):((i * group_size) + group_size)], - self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) - ) - self._parent()[SCPI].send_cmd(":INST:COUP:STAT OFF") - elif group_size == 4: - self._parent()._channel_tuples = [TaborChannelTuple(1, - self._parent(), - self._parent().channels, - self._parent().marker_channels)] - self._parent()[SCPI].send_cmd(":INST:COUP:STAT ON") - else: - raise TaborException("Invalid group size") - - -class TaborDeviceControl(DeviceControl): - """This feature is used for basic communication with a AWG""" - - def __init__(self, device: "TaborDevice"): - super().__init__() - self._parent = weakref.ref(device) - - def reset(self) -> None: - """ - Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and - all channel tuples are cleared. - """ - self._parent()[SCPI].send_cmd(":RES") - self._parent()._coupled = None - - self._parent()._initialize() - for channel_tuple in self._parent().channel_tuples: - channel_tuple[TaborProgramManagement].clear() - - def trigger(self) -> None: - """ - This method triggers a device remotely. - """ - self._parent()[SCPI].send_cmd(":TRIG") - - -class TaborStatusTable(StatusTable): - def __init__(self, device: "TaborDevice"): - super().__init__() - self._parent = device - - def get_status_table(self) -> Dict[str, Union[str, float, int]]: - """ - Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame +from qupulse.utils.types import ChannelID +from qupulse._program._loop import Loop, make_compatible +from qupulse.hardware.util import voltage_to_uint16, find_positions +from qupulse.hardware.awgs.base import AWG, AWGAmplitudeOffsetHandling +from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing,\ + make_combined_wave - Returns: - An ordered dictionary with the results - """ - name_query_type_list = [("channel", ":INST:SEL?", int), - ("coupling", ":OUTP:COUP?", str), - ("volt_dc", ":SOUR:VOLT:LEV:AMPL:DC?", float), - ("volt_hv", ":VOLT:HV?", float), - ("offset", ":VOLT:OFFS?", float), - ("outp", ":OUTP?", str), - ("mode", ":SOUR:FUNC:MODE?", str), - ("shape", ":SOUR:FUNC:SHAPE?", str), - ("dc_offset", ":SOUR:DC?", float), - ("freq_rast", ":FREQ:RAST?", float), - - ("gated", ":INIT:GATE?", str), - ("continuous", ":INIT:CONT?", str), - ("continuous_enable", ":INIT:CONT:ENAB?", str), - ("continuous_source", ":INIT:CONT:ENAB:SOUR?", str), - ("marker_source", ":SOUR:MARK:SOUR?", str), - ("seq_jump_event", ":SOUR:SEQ:JUMP:EVEN?", str), - ("seq_adv_mode", ":SOUR:SEQ:ADV?", str), - ("aseq_adv_mode", ":SOUR:ASEQ:ADV?", str), - - ("marker", ":SOUR:MARK:SEL?", int), - ("marker_high", ":MARK:VOLT:HIGH?", str), - ("marker_low", ":MARK:VOLT:LOW?", str), - ("marker_width", ":MARK:WIDT?", int), - ("marker_state", ":MARK:STAT?", str)] - data = OrderedDict((name, []) for name, *_ in name_query_type_list) - for ch in (1, 2, 3, 4): - self._parent.channels[ch - 1]._select() - self._parent.marker_channels[(ch - 1) % 2]._select() - for name, query, dtype in name_query_type_list: - data[name].append(dtype(self._parent[SCPI].send_query(query))) - return data +__all__ = ['TaborAWGRepresentation', 'TaborChannelPair'] -# Implementation -class TaborDevice(AWGDevice): - def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, - mirror_addresses=()): +class TaborAWGRepresentation: + def __init__(self, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, mirror_addresses=()): """ - Constructor for a Tabor device - - Args: - device_name (str): Name of the device - instr_addr: Instrument address that is forwarded to teawag - paranoia_level (int): Paranoia level that is forwarded to teawg - external_trigger (bool): Not supported yet - reset (bool): - mirror_addresses: list of devices on which the same things as on the main device are done. - For example you can a simulator and a real Device at once + :param instr_addr: Instrument address that is forwarded to teawg + :param paranoia_level: Paranoia level that is forwarded to teawg + :param external_trigger: Not supported yet + :param reset: + :param mirror_addresses: """ - super().__init__(device_name) self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) self._coupled = None - self._clock_marker = [0, 0, 0, 0] - - self.add_feature(TaborSCPI(self, self.main_instrument._visa_inst)) - self.add_feature(TaborDeviceControl(self)) - self.add_feature(TaborStatusTable(self)) - if reset: - self[SCPI].send_cmd(":RES") - - # Channel - self._channels = [TaborChannel(i + 1, self) for i in range(4)] - - # MarkerChannels - self._marker_channels = [TaborMarkerChannel(i + 1, self) for i in range(4)] - - self._initialize() - - # ChannelTuple - self._channel_tuples = [] - - self.add_feature(TaborChannelSynchronization(self)) - self[TaborChannelSynchronization].synchronize_channels(2) + self._clock_marker = [0, 0, 0, 0] if external_trigger: raise NotImplementedError() # pragma: no cover - def enable(self) -> None: - """ - This method immediately generates the selected output waveform, if the device is in continuous and armed - repetition mode. - """ - self[SCPI].send_cmd(":ENAB") + if reset: + self.send_cmd(':RES') - def abort(self) -> None: - """ - With abort you can terminate the current generation of the output waveform. When the output waveform is - terminated the output starts generating an idle waveform. - """ - self[SCPI].send_cmd(":ABOR") + self.initialize() - def set_coupled(self, coupled: bool) -> None: - """ - Thats the coupling of the device to 'coupled' - """ - if coupled: - self[SCPI].send_cmd("INST:COUP:STAT ON") - else: - self[SCPI].send_cmd("INST:COUP:STAT OFF") + self._channel_pair_AB = TaborChannelPair(self, (1, 2), str(instr_addr) + '_AB') + self._channel_pair_CD = TaborChannelPair(self, (3, 4), str(instr_addr) + '_CD') - def _is_coupled(self) -> bool: - # TODO: comment is missing + def is_coupled(self) -> bool: if self._coupled is None: - return self[SCPI].send_query(":INST:COUP:STAT?") == "ON" + return self.send_query(':INST:COUP:STAT?') == 'ON' else: return self._coupled - def cleanup(self) -> None: - # TODO: split cleanup in to two different methods - for channel_tuple in self.channel_tuples: - channel_tuple.cleanup() - - @property - def channels(self) -> Collection["TaborChannel"]: - """Returns a list of all channels of a Device""" - return self._channels - @property - def marker_channels(self) -> Collection["TaborMarkerChannel"]: - """Returns a list of all marker channels of a device. The collection may be empty""" - return self._marker_channels + def channel_pair_AB(self) -> 'TaborChannelPair': + return self._channel_pair_AB @property - def channel_tuples(self) -> Collection["TaborChannelTuple"]: - """Returns a list of all channel tuples of a list""" - return self._channel_tuples + def channel_pair_CD(self) -> 'TaborChannelPair': + return self._channel_pair_CD @property def main_instrument(self) -> teawg.TEWXAwg: @@ -331,15 +72,11 @@ def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: return self._mirrors @property - def all_devices(self) -> Sequence[teawg.TEWXAwg]: - return (self._instr,) + self._mirrors - - @property - def _paranoia_level(self) -> int: + def paranoia_level(self) -> int: return self._instr.paranoia_level - @_paranoia_level.setter - def _paranoia_level(self, val): + @paranoia_level.setter + def paranoia_level(self, val): for instr in self.all_devices: instr.paranoia_level = val @@ -347,567 +84,235 @@ def _paranoia_level(self, val): def dev_properties(self) -> dict: return self._instr.dev_properties - def _send_binary_data(self, pref, bin_dat, paranoia_level=None): + @property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr, ) + self._mirrors + + def send_cmd(self, cmd_str, paranoia_level=None): + for instr in self.all_devices: + instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) + + def send_query(self, query_str, query_mirrors=False) -> Any: + if query_mirrors: + return tuple(instr.send_query(query_str) for instr in self.all_devices) + else: + return self._instr.send_query(query_str) + + def send_binary_data(self, pref, bin_dat, paranoia_level=None): for instr in self.all_devices: instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) - def _download_segment_lengths(self, seg_len_list, pref=":SEGM:DATA", paranoia_level=None): + def download_segment_lengths(self, seg_len_list, pref=':SEGM:DATA', paranoia_level=None): for instr in self.all_devices: instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) - def _download_sequencer_table(self, seq_table, pref=":SEQ:DATA", paranoia_level=None): + def download_sequencer_table(self, seq_table, pref=':SEQ:DATA', paranoia_level=None): for instr in self.all_devices: instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) - def _download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=None): + def download_adv_seq_table(self, seq_table, pref=':ASEQ:DATA', paranoia_level=None): for instr in self.all_devices: instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) - def _initialize(self) -> None: - # TODO: work on this comment - - # 1. Select channel - # 2. Turn off gated mode - # 3. continous mode - # 4. Armed mode (only generate waveforms after enab command) - # 5. Expect enable signal from (USB / LAN / GPIB) - # 6. Use arbitrary waveforms as marker source - # 7. Expect jump command for sequencing from (USB / LAN / GPIB) - - setup_command = ( - ":INIT:GATE OFF; :INIT:CONT ON; " - ":INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS;" - ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") - self[SCPI].send_cmd(":INST:SEL 1") - self[SCPI].send_cmd(setup_command) - self[SCPI].send_cmd(":INST:SEL 3") - self[SCPI].send_cmd(setup_command) - - def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: - """ - A method to get the first readable device out of all devices. - A readable device is a device which you can read data from like a simulator. - - Returns: - The first readable device out of all devices - - Throws: - TaborException: this exception is thrown if there is no readable device in the list of all devices - """ - for device in self.all_devices: - if device.fw_ver >= 3.0: - if simulator: - if device.is_simulator: - return device - else: - return device - raise TaborException("No device capable of device data read") - - -######################################################################################################################## -# Channel -######################################################################################################################## -# Features -class TaborVoltageRange(VoltageRange): - def __init__(self, channel: "TaborChannel"): - super().__init__() - self._parent = weakref.ref(channel) - - @property - @with_select - def offset(self) -> float: - """Get offset of AWG channel""" - return float( - self._parent().device[SCPI].send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) - - @property - @with_select - def amplitude(self) -> float: - """Get amplitude of AWG channel""" - coupling = self._parent().device[SCPI].send_query(":OUTP:COUP?") - if coupling == "DC": - return float(self._parent().device[SCPI].send_query(":VOLT?")) - elif coupling == "HV": - return float(self._parent().device[SCPI].send_query(":VOLT:HV?")) - else: - raise TaborException("Unknown coupling: {}".format(coupling)) - - @property - def amplitude_offset_handling(self) -> str: - """ - Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and - offset settings are constant or if these should be optimized by the driver - """ - return self._parent()._amplitude_offset_handling - - @amplitude_offset_handling.setter - def amplitude_offset_handling(self, amp_offs_handling: str) -> None: - """ - amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` - """ - if amp_offs_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET or amp_offs_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: - self._parent()._amplitude_offset_handling = amp_offs_handling - else: - raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(amp_offs_handling)) - - def _select(self) -> None: - self._parent()._select() - - -class TaborActivatableChannels(ActivatableChannels): - def __init__(self, channel: "TaborChannel"): - super().__init__() - self._parent = weakref.ref(channel) - - @property - def enabled(self) -> bool: - """ - Returns the the state a channel has at the moment. A channel is either activated or deactivated - """ - pass # TODO: to implement - - @with_select - def enable(self): - """Enables the output of a certain channel""" - command_string = ":OUTP ON".format(ch_id=self._parent().idn) - self._parent().device[SCPI].send_cmd(command_string) - - @with_select - def disable(self): - """Disables the output of a certain channel""" - command_string = ":OUTP OFF".format(ch_id=self._parent().idn) - self._parent().device[SCPI].send_cmd(command_string) - - def _select(self) -> None: - self._parent()._select() - - -# Implementation -class TaborChannel(AWGChannel): - def __init__(self, idn: int, device: TaborDevice): - super().__init__(idn) - - self._device = weakref.ref(device) - self._amplitude_offset_handling = AmplitudeOffsetHandling.IGNORE_OFFSET - - # adding Features - self.add_feature(TaborVoltageRange(self)) - self.add_feature(TaborActivatableChannels(self)) - - @property - def device(self) -> TaborDevice: - """Returns the device that the channel belongs to""" - return self._device() - - @property - def channel_tuple(self) -> "TaborChannelTuple": - """Returns the channel tuple that this channel belongs to""" - return self._channel_tuple() - - def _set_channel_tuple(self, channel_tuple: "TaborChannelTuple") -> None: - """ - The channel tuple "channel_tuple" is assigned to this channel - - Args: - channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to - """ - self._channel_tuple = weakref.ref(channel_tuple) - - def _select(self) -> None: - self.device[SCPI].send_cmd(":INST:SEL {channel}".format(channel=self.idn)) - - -######################################################################################################################## -# ChannelTuple -######################################################################################################################## -# Features -class TaborProgramManagement(ProgramManagement): - def __init__(self, channel_tuple: "TaborChannelTuple"): - super().__init__() - self._programs = {} - self._armed_program = None - self._parent = weakref.ref(channel_tuple) - - self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] - self._trigger_source = 'BUS' - - def get_repetition_mode(self, program_name: str) -> str: - """ - Returns the default repetition mode of a certain program - Args: - program_name (str): name of the program whose repetition mode should be returned - """ - return self._parent()._known_programs[program_name].program._repetition_mode - - def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: - """ - Changes the default repetition mode of a certain program - - Args: - program_name (str): name of the program whose repetition mode should be changed + def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: + """Overwrite send_cmd for paranoia_level > 3""" + if paranoia_level is None: + paranoia_level = self.paranoia_level - Throws: - ValueError: this Exception is thrown when an invalid repetition mode is given - """ - if repetition_mode is "infinite" or repetition_mode is "once": - self._parent()._known_programs[program_name].program._repetition_mode = repetition_mode + if paranoia_level < 3: + super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover else: - raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) - - @with_configuration_guard - @with_select - def upload(self, name: str, - program: Loop, - channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - marker_channels: Tuple[Optional[ChannelID], Optional[ChannelID]], - voltage_transformation: Tuple[Callable, Callable], - repetition_mode: str = None, - force: bool = False) -> None: - """ - Upload a program to the AWG. - - The policy is to prefer amending the unknown waveforms to overwriting old ones. - """ - - if repetition_mode is None: - warnings.warn("TaborWarning - upload() - no repetition mode given!") - repetition_mode = "infinite" - elif repetition_mode not in ("infinite"): - raise TaborException("Invalid Repetionmode: " + repetition_mode) - - if len(channels) != len(self._parent().channels): - raise ValueError("Wrong number of channels") - if len(marker_channels) != len(self._parent().marker_channels): - raise ValueError("Wrong number of marker") - if len(voltage_transformation) != len(self._parent().channels): - raise ValueError("Wrong number of voltage transformations") - - # adjust program to fit criteria - sample_rate = self._parent().device.channel_tuples[0].sample_rate - make_compatible(program, - minimal_waveform_length=192, - waveform_quantum=16, - sample_rate=sample_rate / 10 ** 9) + cmd_str = cmd_str.rstrip() - if name in self._parent()._known_programs: - if force: - self._parent().free_program(name) + if len(cmd_str) > 0: + ask_str = cmd_str + '; *OPC?; :SYST:ERR?' else: - raise ValueError('{} is already known on {}'.format(name, self._parent().idn)) - - # They call the peak to peak range amplitude + ask_str = '*OPC?; :SYST:ERR?' - ranges = tuple(ch[VoltageRange].amplitude for ch in self._parent().channels) - - voltage_amplitudes = tuple(range / 2 for range in ranges) - - voltage_offsets = [] - for channel in self._parent().channels: - if channel._amplitude_offset_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: - voltage_offsets.append(0) - elif channel._amplitude_offset_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET: - voltage_offsets.append(channel[VoltageRange].offset) - else: - raise ValueError( - '{} is invalid as AWGAmplitudeOffsetHandling'.format(channel._amplitude_offset_handling)) - voltage_offsets = tuple(voltage_offsets) + *answers, opc, error_code_msg = self._visa_inst.ask(ask_str).split(';') - # parse to tabor program - tabor_program = TaborProgram(program, - channels=tuple(channels), - markers=marker_channels, - device_properties=self._parent().device.dev_properties, - sample_rate=sample_rate / 10 ** 9, - amplitudes=voltage_amplitudes, - offsets=voltage_offsets, - voltage_transformations=voltage_transformation) - - segments, segment_lengths = tabor_program.get_sampled_segments() - - waveform_to_segment, to_amend, to_insert = self._parent()._find_place_for_segments_in_memory(segments, - segment_lengths) - - self._parent()._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 - - for wf_index in np.flatnonzero(to_insert > 0): - segment_index = to_insert[wf_index] - self._parent()._upload_segment(to_insert[wf_index], segments[wf_index]) - waveform_to_segment[wf_index] = segment_index - - if np.any(to_amend): - segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] - waveform_to_segment[to_amend] = self._parent()._amend_segments(segments_to_amend) - - self._parent()._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, - program=tabor_program) - - # set the default repetionmode for a programm - self.set_repetition_mode(program_name=name, repetition_mode=repetition_mode) - - def remove(self, name: str) -> None: - """ - Remove a program from the AWG. - - Also discards all waveforms referenced only by the program identified by name. - - Args: - name (str): The name of the program to remove. - """ - self._parent().free_program(name) - self._parent().cleanup() - - def clear(self) -> None: - """ - Removes all programs and waveforms from the AWG. - - Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! - """ - - self._parent().device.channels[0]._select() - self._parent().device[SCPI].send_cmd(":TRAC:DEL:ALL") - self._parent().device[SCPI].send_cmd(":SOUR:SEQ:DEL:ALL") - self._parent().device[SCPI].send_cmd(":ASEQ:DEL") - - self._parent().device[SCPI].send_cmd(":TRAC:DEF 1, 192") - self._parent().device[SCPI].send_cmd(":TRAC:SEL 1") - self._parent().device[SCPI].send_cmd(":TRAC:MODE COMB") - self._parent().device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent()._idle_segment.get_as_binary()) + error_code, error_msg = error_code_msg.split(',') + error_code = int(error_code) + if error_code != 0: + _ = self._visa_inst.ask('*CLS; *OPC?') - self._parent()._segment_lengths = 192 * np.ones(1, dtype=np.uint32) - self._parent()._segment_capacity = 192 * np.ones(1, dtype=np.uint32) - self._parent()._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._parent()._idle_segment) - self._parent()._segment_references = np.ones(1, dtype=np.uint32) + if error_code == -450: + # query queue overflow + self.send_cmd(cmd_str) + else: + raise RuntimeError('Cannot execute command: {}\n{}: {}'.format(cmd_str, error_code, error_msg)) - self._parent()._advanced_sequence_table = [] - self._parent()._sequencer_tables = [] + assert len(answers) == 0 - self._parent()._known_programs = dict() - self._change_armed_program(None) + def get_status_table(self) -> Dict[str, Union[str, float, int]]: + """Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame - @with_select - def arm(self, name: Optional[str]) -> None: + Returns: + An ordered dictionary with the results """ - Load the program 'name' and arm the device for running it. + name_query_type_list = [('channel', ':INST:SEL?', int), + ('coupling', ':OUTP:COUP?', str), + ('volt_dc', ':SOUR:VOLT:LEV:AMPL:DC?', float), + ('volt_hv', ':VOLT:HV?', float), + ('offset', ':VOLT:OFFS?', float), + ('outp', ':OUTP?', str), + ('mode', ':SOUR:FUNC:MODE?', str), + ('shape', ':SOUR:FUNC:SHAPE?', str), + ('dc_offset', ':SOUR:DC?', float), + ('freq_rast', ':FREQ:RAST?', float), + + ('gated', ':INIT:GATE?', str), + ('continuous', ':INIT:CONT?', str), + ('continuous_enable', ':INIT:CONT:ENAB?', str), + ('continuous_source', ':INIT:CONT:ENAB:SOUR?', str), + ('marker_source', ':SOUR:MARK:SOUR?', str), + ('seq_jump_event', ':SOUR:SEQ:JUMP:EVEN?', str), + ('seq_adv_mode', ':SOUR:SEQ:ADV?', str), + ('aseq_adv_mode', ':SOUR:ASEQ:ADV?', str), + + ('marker', ':SOUR:MARK:SEL?', int), + ('marker_high', ':MARK:VOLT:HIGH?', str), + ('marker_low', ':MARK:VOLT:LOW?', str), + ('marker_width', ':MARK:WIDT?', int), + ('marker_state', ':MARK:STAT?', str)] - Args: - name (str): the program the device should change to - """ - if self._parent()._current_program == name: - self._parent().device[SCPI].send_cmd("SEQ:SEL 1") - else: - self._change_armed_program(name) + data = OrderedDict((name, []) for name, *_ in name_query_type_list) + for ch in (1, 2, 3, 4): + self.select_channel(ch) + self.select_marker((ch-1) % 2 + 1) + for name, query, dtype in name_query_type_list: + data[name].append(dtype(self.send_query(query))) + return data @property - def programs(self) -> Set[str]: - """The set of program names that can currently be executed on the hardware AWG.""" - return set(program.name for program in self._parent()._known_programs.keys()) - - @with_select - def run_current_program(self) -> None: - """ - This method starts running the active program - - Throws: - RuntimeError: This exception is thrown if there is no active program for this device - """ - if (self._parent().device._is_coupled()): - # channel tuple is the first channel tuple - if (self._parent.device._channel_tuples[0] == self): - if self._parent()._current_program: - repetition_mode = self._parent()._known_programs[ - self._parent()._current_program].program._repetition_mode - if repetition_mode is "infinite": - self._cont_repetition_mode() - self._parent().device[SCPI].send_cmd(':TRIG', - paranoia_level=self._parent().internal_paranoia_level) - else: - raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) - else: - raise RuntimeError("No program active") - else: - warnings.warn( - "TaborWarning - run_current_program() - the device is coupled - runthe program via the first channel tuple") - - else: - if self._parent()._current_program: - repetition_mode = self._parent()._known_programs[ - self._parent()._current_program].program._repetition_mode - if repetition_mode is "infinite": - self._cont_repetition_mode() - self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) - else: - raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) - else: - raise RuntimeError("No program active") - - @with_select - @with_configuration_guard - def _change_armed_program(self, name: Optional[str]) -> None: - """The armed program of the channel tuple is changed to the program with the name 'name'""" - if name is None: - sequencer_tables = [self._idle_sequence_table] - advanced_sequencer_table = [(1, 1, 0)] + def is_open(self) -> bool: + return self._instr.visa_inst is not None # pragma: no cover + + def select_channel(self, channel) -> None: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + + self.send_cmd(':INST:SEL {channel}'.format(channel=channel)) + + def select_marker(self, marker: int) -> None: + """Select marker 1 or 2 of the currently active channel pair.""" + if marker not in (1, 2): + raise TaborException('Invalid marker: {}'.format(marker)) + self.send_cmd(':SOUR:MARK:SEL {marker}'.format(marker=marker)) + + def sample_rate(self, channel) -> int: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + return int(float(self.send_query(':INST:SEL {channel}; :FREQ:RAST?'.format(channel=channel)))) + + def amplitude(self, channel) -> float: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + coupling = self.send_query(':INST:SEL {channel}; :OUTP:COUP?'.format(channel=channel)) + if coupling == 'DC': + return float(self.send_query(':VOLT?')) + elif coupling == 'HV': + return float(self.send_query(':VOLT:HV?')) else: - waveform_to_segment_index, program = self._parent()._known_programs[name] - waveform_to_segment_number = waveform_to_segment_index + 1 - - # translate waveform number to actual segment - sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) - for ((rep_count, wf_index, jump_flag), _) in sequencer_table] - for sequencer_table in program.get_sequencer_tables()] + raise TaborException('Unknown coupling: {}'.format(coupling)) - # insert idle sequence - sequencer_tables = [self._idle_sequence_table] + sequencer_tables + def offset(self, channel: int) -> float: + if channel not in (1, 2, 3, 4): + raise TaborException('Invalid channel: {}'.format(channel)) + return float(self.send_query(':INST:SEL {channel}; :VOLT:OFFS?'.format(channel=channel))) - # adjust advanced sequence table entries by idle sequence table offset - advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) - for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] - - if program.waveform_mode == TaborSequencing.SINGLE: - assert len(advanced_sequencer_table) == 1 - assert len(sequencer_tables) == 2 - - while len(sequencer_tables[1]) < self._parent().device.dev_properties["min_seq_len"]: - assert advanced_sequencer_table[0][0] == 1 - sequencer_tables[1].append((1, 1, 0)) - - # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table - - while len(advanced_sequencer_table) < self._parent().device.dev_properties["min_aseq_len"]: - advanced_sequencer_table.append((1, 1, 0)) - - self._parent().device[SCPI].send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent().internal_paranoia_level) - self._parent()._sequencer_tables = [] - self._parent().device[SCPI].send_cmd("ASEQ:DEL", paranoia_level=self._parent().internal_paranoia_level) - self._parent()._advanced_sequence_table = [] - - # download all sequence tables - for i, sequencer_table in enumerate(sequencer_tables): - self._parent().device[SCPI].send_cmd("SEQ:SEL {}".format(i + 1), - paranoia_level=self._parent().internal_paranoia_level) - self._parent().device._download_sequencer_table(sequencer_table) - self._parent()._sequencer_tables = sequencer_tables - self._parent().device[SCPI].send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) - - self._parent().device._download_adv_seq_table(advanced_sequencer_table) - self._parent()._advanced_sequence_table = advanced_sequencer_table - - self._parent()._current_program = name - - def _select(self): - self._parent().channels[0]._select() - - @property - def _configuration_guard_count(self): - return self._parent()._configuration_guard_count - - @_configuration_guard_count.setter - def _configuration_guard_count(self, configuration_guard_count): - self._parent()._configuration_guard_count = configuration_guard_count - - def _enter_config_mode(self): - self._parent()._enter_config_mode() - - def _exit_config_mode(self): - self._parent()._exit_config_mode() - - @with_select - def _cont_repetition_mode(self): - """Changes the run mode of this channel tuple to continous mode""" - self._parent().device[SCPI].send_cmd(f":TRIG:SOUR:ADV EXT") - self._parent().device[SCPI].send_cmd( - f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + def enable(self) -> None: + self.send_cmd(':ENAB') + def abort(self) -> None: + self.send_cmd(':ABOR') -class TaborVolatileParameters(VolatileParameters): - def __init__(self, channel_tuple: "TaborChannelTuple", ): - super().__init__() - self._parent = weakref.ref(channel_tuple) + def initialize(self) -> None: + # 1. Select channel + # 2. Turn off gated mode + # 3. continous mode + # 4. Armed mode (onlz generate waveforms after enab command) + # 5. Expect enable signal from (USB / LAN / GPIB) + # 6. Use arbitrary waveforms as marker source + # 7. Expect jump command for sequencing from (USB / LAN / GPIB) + setup_command = ( + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR BUS; " + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + self.send_cmd(':INST:SEL 1') + self.send_cmd(setup_command) + self.send_cmd(':INST:SEL 3') + self.send_cmd(setup_command) - def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: - """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters - in program memory and device's (adv.) sequence tables if program is current program. + def reset(self) -> None: + self.send_cmd(':RES') + self._coupled = None + self.initialize() + self.channel_pair_AB.clear() + self.channel_pair_CD.clear() - If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to - enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) - and set device._is_coupled. + def trigger(self) -> None: + self.send_cmd(':TRIG') - Args: - program_name: Name of program which should be changed. - parameters: Names of volatile parameters and respective values to which they should be set. - """ - waveform_to_segment_index, program = self._parent()._known_programs[program_name] - modifications = program.update_volatile_parameters(parameters) + def get_readable_device(self, simulator=True) -> teawg.TEWXAwg: + for device in self.all_devices: + if device.fw_ver >= 3.0: + if simulator: + if device.is_simulator: + return device + else: + return device + raise TaborException('No device capable of device data read') - self._parent().logger.debug("parameter modifications: %r" % modifications) - if not modifications: - self._parent().logger.info( - "There are no volatile parameters to update. Either there are no volatile parameters with " - "these names,\nthe respective repetition counts already have the given values or the " - "volatile parameters were dropped during upload.") - return +TaborProgramMemory = NamedTuple('TaborProgramMemory', [('waveform_to_segment', np.ndarray), + ('program', TaborProgram)]) - if program_name == self._parent()._current_program: - commands = [] - for position, entry in modifications.items(): - if not entry.repetition_count > 0: - raise ValueError("Repetition must be > 0") +def with_configuration_guard(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], + Any]: + """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" + @functools.wraps(function_object) + def guarding_method(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + if channel_pair._configuration_guard_count == 0: + channel_pair._enter_config_mode() + channel_pair._configuration_guard_count += 1 - if isinstance(position, int): - commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, - entry.repetition_count, entry.jump_flag)) - else: - table_num, step_num = position - commands.append(":SEQ:SEL {}".format(table_num + 2)) - commands.append(":SEQ:DEF {},{},{},{}".format(step_num, - waveform_to_segment_index[entry.element_id] + 1, - entry.repetition_count, entry.jump_flag)) - self._parent()._execute_multiple_commands_with_config_guard(commands) + try: + return function_object(channel_pair, *args, **kwargs) + finally: + channel_pair._configuration_guard_count -= 1 + if channel_pair._configuration_guard_count == 0: + channel_pair._exit_config_mode() - # Wait until AWG is finished - _ = self._parent().device.main_instrument._visa_inst.query("*OPC?") + return guarding_method -class TaborReadProgram(ReadProgram): - def __init__(self, channel_tuple: "TaborChannelTuple", ): - super().__init__() - self._parent = weakref.ref(channel_tuple) +def with_select(function_object: Callable[['TaborChannelPair', Any], Any]) -> Callable[['TaborChannelPair'], Any]: + """Asserts the channel pair is selected when the wrapped function is called""" + @functools.wraps(function_object) + def selector(channel_pair: 'TaborChannelPair', *args, **kwargs) -> Any: + channel_pair.select() + return function_object(channel_pair, *args, **kwargs) - def read_complete_program(self): - return PlottableProgram.from_read_data(self._parent().read_waveforms(), - self._parent().read_sequence_tables(), - self._parent().read_advanced_sequencer_table()) + return selector -# Implementation -class TaborChannelTuple(AWGChannelTuple): +class TaborChannelPair(AWG): CONFIG_MODE_PARANOIA_LEVEL = None - def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], - marker_channels: Iterable["TaborMarkerChannel"]): - super().__init__(idn) - self._device = weakref.ref(device) + def __init__(self, tabor_device: TaborAWGRepresentation, channels: Tuple[int, int], identifier: str): + super().__init__(identifier) + self._device = weakref.ref(tabor_device) self._configuration_guard_count = 0 self._is_in_config_mode = False - self._channels = tuple(channels) - self._marker_channels = tuple(marker_channels) - - # the channel and channel marker are assigned to this channel tuple - for channel in self.channels: - channel._set_channel_tuple(self) - for marker_ch in self.marker_channels: - marker_ch._set_channel_tuple(self) - - # adding Features - self.add_feature(TaborProgramManagement(self)) - self.add_feature(TaborVolatileParameters(self)) + if channels not in ((1, 2), (3, 4)): + raise ValueError('Invalid channel pair: {}'.format(channels)) + self._channels = channels self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), output_amplitude=0.5, @@ -916,6 +321,7 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann output_amplitude=0.5, output_offset=0., resolution=14), None, None) + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._known_programs = dict() # type: Dict[str, TaborProgramMemory] self._current_program = None @@ -930,66 +336,46 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann self._internal_paranoia_level = 0 - self[TaborProgramManagement].clear() - - self._channel_tuple_adapter: ChannelTupleAdapter + self.clear() @property def internal_paranoia_level(self) -> Optional[int]: return self._internal_paranoia_level - @property - def logger(self): - return logging.getLogger("qupulse.tabor") - - @property - def channel_tuple_adapter(self) -> ChannelTupleAdapter: - if self._channel_tuple_adapter is None: - self._channel_tuple_adapter = ChannelTupleAdapter(self) - return self._channel_tuple_adapter - - def _select(self) -> None: - """The channel tuple is selected, which means that the first channel of the channel tuple is selected""" - self.channels[0]._select() - - @property - def device(self) -> TaborDevice: - """Returns the device that the channel tuple belongs to""" - return self._device() + @internal_paranoia_level.setter + def internal_paranoia_level(self, paranoia_level: Optional[int]): + """ Sets the paranoia level with which commands from within methods are called """ + assert paranoia_level in (None, 0, 1, 2) + self._internal_paranoia_level = paranoia_level - @property - def channels(self) -> Collection["TaborChannel"]: - """Returns all channels of the channel tuple""" - return self._channels + def select(self) -> None: + self.device.send_cmd(':INST:SEL {}'.format(self._channels[0]), + paranoia_level=self.internal_paranoia_level) @property - def marker_channels(self) -> Collection["TaborMarkerChannel"]: - """Returns all marker channels of the channel tuple""" - return self._marker_channels + def total_capacity(self) -> int: + return int(self.device.dev_properties['max_arb_mem']) // 2 @property - @with_select - def sample_rate(self) -> TimeType: - """Returns the sample rate that the channels of a channel tuple have""" - return TimeType.from_float( - float(self.device[SCPI].send_query(":FREQ:RAST?".format(channel=self.channels[0].idn)))) + def logger(self): + return logging.getLogger("qupulse.tabor") @property - def total_capacity(self) -> int: - return int(self.device.dev_properties["max_arb_mem"]) // 2 + def device(self) -> TaborAWGRepresentation: + return self._device() def free_program(self, name: str) -> TaborProgramMemory: if name is None: - raise TaborException("Removing 'None' program is forbidden.") + raise TaborException('Removing "None" program is forbidden.') program = self._known_programs.pop(name) self._segment_references[program.waveform_to_segment] -= 1 if self._current_program == name: - self[TaborProgramManagement]._change_armed_program(None) + self.change_armed_program(None) return program def _restore_program(self, name: str, program: TaborProgram) -> None: if name in self._known_programs: - raise ValueError("Program cannot be restored as it is already known.") + raise ValueError('Program cannot be restored as it is already known.') self._segment_references[program.waveform_to_segment] += 1 self._known_programs[name] = program @@ -1011,56 +397,148 @@ def _free_points_at_end(self) -> int: @with_select def read_waveforms(self) -> List[np.ndarray]: - device = self.device._get_readable_device(simulator=True) + device = self.device.get_readable_device(simulator=True) - old_segment = device[SCPI].send_query(":TRAC:SEL?") + old_segment = device.send_query(':TRAC:SEL?') waveforms = [] - uploaded_waveform_indices = np.flatnonzero( - self._segment_references) + 1 - + uploaded_waveform_indices = np.flatnonzero(self._segment_references) + 1 for segment in uploaded_waveform_indices: - device[SCPI].send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(':TRAC:SEL {}'.format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) - device[SCPI].send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(':TRAC:SEL {}'.format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms @with_select def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: - device = self.device._get_readable_device(simulator=True) + device = self.device.get_readable_device(simulator=True) - old_sequence = device[SCPI].send_query(":SEQ:SEL?") + old_sequence = device.send_query(':SEQ:SEL?') sequences = [] uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: - device[SCPI].send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(':SEQ:SEL {}'.format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) - device[SCPI].send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(':SEQ:SEL {}'.format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences @with_select def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - return self.device._get_readable_device(simulator=True).read_adv_seq_table() + return self.device.get_readable_device(simulator=True).read_adv_seq_table() def read_complete_program(self) -> PlottableProgram: return PlottableProgram.from_read_data(self.read_waveforms(), self.read_sequence_tables(), self.read_advanced_sequencer_table()) - def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ - Tuple[np.ndarray, np.ndarray, np.ndarray]: - # TODO: comment was not finished + @with_configuration_guard + @with_select + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + markers: Tuple[Optional[ChannelID], Optional[ChannelID]], + voltage_transformation: Tuple[Callable, Callable], + force: bool = False) -> None: + """Upload a program to the AWG. + + The policy is to prefer amending the unknown waveforms to overwriting old ones.""" + + if len(channels) != self.num_channels: + raise ValueError('Channel ID not specified') + if len(markers) != self.num_markers: + raise ValueError('Markers not specified') + if len(voltage_transformation) != self.num_channels: + raise ValueError('Wrong number of voltage transformations') + + # adjust program to fit criteria + sample_rate = self.device.sample_rate(self._channels[0]) + make_compatible(program, + minimal_waveform_length=192, + waveform_quantum=16, + sample_rate=fractions.Fraction(sample_rate, 10**9)) + + if name in self._known_programs: + if force: + self.free_program(name) + else: + raise ValueError('{} is already known on {}'.format(name, self.identifier)) + + # They call the peak to peak range amplitude + ranges = (self.device.amplitude(self._channels[0]), + self.device.amplitude(self._channels[1])) + + voltage_amplitudes = (ranges[0]/2, ranges[1]/2) + + if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets = (0, 0) + elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets = (self.device.offset(self._channels[0]), + self.device.offset(self._channels[1])) + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) + + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=markers, + device_properties=self.device.dev_properties, + sample_rate=sample_rate / 10**9, + amplitudes=voltage_amplitudes, + offsets=voltage_offsets, + voltage_transformations=voltage_transformation) + + segments, segment_lengths = tabor_program.get_sampled_segments() + + waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, + segment_lengths) + + self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 + + for wf_index in np.flatnonzero(to_insert > 0): + segment_index = to_insert[wf_index] + self._upload_segment(to_insert[wf_index], segments[wf_index]) + waveform_to_segment[wf_index] = segment_index + + if np.any(to_amend): + segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] + waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) + + self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, + program=tabor_program) + + @with_configuration_guard + @with_select + def clear(self) -> None: + """Delete all segments and clear memory""" + self.device.select_channel(self._channels[0]) + self.device.send_cmd(':TRAC:DEL:ALL', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':SOUR:SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':ASEQ:DEL', paranoia_level=self.internal_paranoia_level) + + self.device.send_cmd(':TRAC:DEF 1, 192', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:SEL 1', paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=self._idle_segment.get_as_binary()) + + self._segment_lengths = 192*np.ones(1, dtype=np.uint32) + self._segment_capacity = 192*np.ones(1, dtype=np.uint32) + self._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._idle_segment) + self._segment_references = np.ones(1, dtype=np.uint32) + + self._advanced_sequence_table = [] + self._sequencer_tables = [] + + self._known_programs = dict() + self.change_armed_program(None) + + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ 1. Find known segments 2. Find empty spaces with fitting length 3. Find empty spaces with bigger length 4. Amend remaining segments - - Args: - segments (Sequence): - segment_length (Sequence): - - Returns: - + :param segments: + :param segment_lengths: + :return: """ segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) @@ -1072,8 +550,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths known_pos_in_memory = waveform_to_segment[known] - assert len(known_pos_in_memory) == 0 or np.all( - self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + assert len(known_pos_in_memory) == 0 or np.all(self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) new_reference_counter = self._segment_references.copy() new_reference_counter[known_pos_in_memory] += 1 @@ -1081,7 +558,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths to_upload_size = np.sum(segment_lengths[unknown] + 16) free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) if free_points_in_total < to_upload_size: - raise MemoryError("Not enough free memory", + raise MemoryError('Not enough free memory', free_points_in_total, to_upload_size, self._free_points_in_total) @@ -1100,8 +577,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths if free_segment_count == 0: break - pos_of_same_length = np.logical_and(free_segments, - segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + pos_of_same_length = np.logical_and(free_segments, segment_lengths[segment_idx] == self._segment_capacity[:first_free]) idx_same_length = np.argmax(pos_of_same_length) if pos_of_same_length[idx_same_length]: free_segments[idx_same_length] = False @@ -1130,7 +606,7 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: - raise MemoryError("Fragmentation does not allow upload.", + raise MemoryError('Fragmentation does not allow upload.', np.sum(segment_lengths[to_amend] + 16), free_points_at_end, self._free_points_at_end) @@ -1141,22 +617,22 @@ def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths @with_configuration_guard def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: if self._segment_references[segment_index] > 0: - raise ValueError("Reference count not zero") + raise ValueError('Reference count not zero') if segment.num_points > self._segment_capacity[segment_index]: - raise ValueError("Cannot upload segment here.") + raise ValueError('Cannot upload segment here.') segment_no = segment_index + 1 - self.device[SCPI].send_cmd(":TRAC:DEF {}, {}".format(segment_no, segment.num_points), - paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:DEF {}, {}'.format(segment_no, segment.num_points), + paranoia_level=self.internal_paranoia_level) self._segment_lengths[segment_index] = segment.num_points - self.device[SCPI].send_cmd(":TRAC:SEL {}".format(segment_no), paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:SEL {}'.format(segment_no), paranoia_level=self.internal_paranoia_level) - self.device[SCPI].send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:MODE COMB', paranoia_level=self.internal_paranoia_level) wf_data = segment.get_as_binary() - self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) self._segment_references[segment_index] = 1 self._segment_hashes[segment_index] = hash(segment) @@ -1170,14 +646,13 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: segment_index = len(self._segment_capacity) first_segment_number = segment_index + 1 - - self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(first_segment_number, trac_len), - paranoia_level=self.internal_paranoia_level) - self.device[SCPI].send_cmd(":TRAC:SEL {}".format(first_segment_number), - paranoia_level=self.internal_paranoia_level) - self.device[SCPI].send_cmd(":TRAC:MODE COMB", - paranoia_level=self.internal_paranoia_level) - self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) + self.device.send_cmd(':TRAC:DEF {},{}'.format(first_segment_number, trac_len), + paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:SEL {}'.format(first_segment_number), + paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:MODE COMB', + paranoia_level=self.internal_paranoia_level) + self.device.send_binary_data(pref=':TRAC:DATA', bin_dat=wf_data) old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) @@ -1187,15 +662,16 @@ def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: if len(segments) < old_to_update: for i, segment in enumerate(segments): current_segment_number = first_segment_number + i - self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(current_segment_number, segment.num_points), - paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(':TRAC:DEF {},{}'.format(current_segment_number, segment.num_points), + paranoia_level=self.internal_paranoia_level) else: # flush the capacity - self.device._download_segment_lengths(segment_capacity) + self.device.download_segment_lengths(segment_capacity) # update non fitting lengths for i in np.flatnonzero(segment_capacity != segment_lengths): - self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(i + 1, segment_lengths[i])) + self.device.send_cmd(':TRAC:DEF {},{}'.format(i+1, segment_lengths[i]), + paranoia_level=self.internal_paranoia_level) self._segment_capacity = segment_capacity self._segment_lengths = segment_lengths @@ -1210,7 +686,7 @@ def cleanup(self) -> None: """Discard all segments after the last which is still referenced""" reserved_indices = np.flatnonzero(self._segment_references > 0) old_end = len(self._segment_lengths) - new_end = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + new_end = reserved_indices[-1]+1 if len(reserved_indices) else 0 self._segment_lengths = self._segment_lengths[:new_end] self._segment_capacity = self._segment_capacity[:new_end] self._segment_hashes = self._segment_hashes[:new_end] @@ -1220,10 +696,22 @@ def cleanup(self) -> None: # send max 10 commands at once chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): - self.device[SCPI].send_cmd("; ".join("TRAC:DEL {}".format(i + 1) - for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) + self.device.send_cmd('; '.join('TRAC:DEL {}'.format(i+1) + for i in range(chunk_start, min(chunk_start+chunk_size, old_end))), + paranoia_level=self.internal_paranoia_level) except Exception as e: - raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e + raise TaborUndefinedState('Error during cleanup. Device is in undefined state.', device=self) from e + + def remove(self, name: str) -> None: + """Remove a program from the AWG. + + Also discards all waveforms referenced only by the program identified by name. + + Args: + name (str): The name of the program to remove. + """ + self.free_program(name) + self.cleanup() @with_configuration_guard def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: @@ -1233,166 +721,223 @@ def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> N commands: Commands that should be executed. """ cmd_str = ";".join(commands) - self.device[SCPI].send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) + self.device.send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) - def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): - self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table + def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: + """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters + in program memory and device's (adv.) sequence tables if program is current program. - def set_program_sequence_table(self, name, new_sequence_table): - self._known_programs[name][1]._sequencer_tables = new_sequence_table + If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to + enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) + and set device._is_coupled. - def _enter_config_mode(self) -> None: - """ - Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the - sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. - When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip. + Args: + program_name: Name of program which should be changed. + parameters: Names of volatile parameters and respective values to which they should be set. """ - if self._is_in_config_mode is False: - # 1. Selct channel pair - # 2. Select DC as function shape - # 3. Select build-in waveform mode + waveform_to_segment_index, program = self._known_programs[program_name] - if self.device._is_coupled(): - out_cmd = ":OUTP:ALL OFF" - else: - out_cmd = "" - for channel in self.channels: - out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) + modifications = program.update_volatile_parameters(parameters) - marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature - # for marker_ch in self.marker_channels: - # marker_ch[TaborMarkerChannelActivatable].disable() + self.logger.debug("parameter modifications: %r" % modifications) + + if not modifications: + self.logger.info("There are no volatile parameters to update. Either there are no volatile parameters with " + "these names,\nthe respective repetition counts already have the given values or the " + "volatile parameters were dropped during upload.") + return - wf_mode_cmd = ":SOUR:FUNC:MODE FIX" + if program_name == self._current_program: + commands = [] - cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) - cmd = out_cmd + cmd - self.device[SCPI].send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) - self._is_in_config_mode = True + for position, entry in modifications.items(): + if not entry.repetition_count > 0: + raise ValueError('Repetition must be > 0') - @with_select - def _exit_config_mode(self) -> None: - """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" + if isinstance(position, int): + commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, + entry.repetition_count, entry.jump_flag)) + else: + table_num, step_num = position + commands.append(":SEQ:SEL {}".format(table_num + 2)) + commands.append(":SEQ:DEF {},{},{},{}".format(step_num, + waveform_to_segment_index[entry.element_id] + 1, + entry.repetition_count, entry.jump_flag)) + self._execute_multiple_commands_with_config_guard(commands) - # TODO: change implementation for channel synchronisation feature + # Wait until AWG is finished + _ = self.device.main_instrument._visa_inst.query('*OPC?') - if self.device._is_coupled(): - # Coupled -> switch all channels at once - other_channel_tuple: TaborChannelTuple - if self.channels == self.device.channel_tuples[0].channels: - other_channel_tuple = self.device.channel_tuples[1] - else: - other_channel_tuple = self.device.channel_tuples[0] + def set_marker_state(self, marker: int, active: bool) -> None: + """Sets the marker state of this channel pair. + According to the manual one cannot turn them off/on separately.""" + command_string = ':INST:SEL {channel}; :SOUR:MARK:SEL {marker}; :SOUR:MARK:SOUR USER; :SOUR:MARK:STAT {active}' + command_string = command_string.format( + channel=self._channels[0], + marker=(1, 2)[marker], + active='ON' if active else 'OFF') + self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) - if not other_channel_tuple._is_in_config_mode: - self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") - self.device[SCPI].send_cmd(":SEQ:SEL 1") - self.device[SCPI].send_cmd(":OUTP:ALL ON") + def set_channel_state(self, channel, active) -> None: + command_string = ':INST:SEL {}; :OUTP {}'.format(self._channels[channel], 'ON' if active else 'OFF') + self.device.send_cmd(command_string, paranoia_level=self.internal_paranoia_level) + @with_select + def arm(self, name: str) -> None: + if self._current_program == name: + self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) else: - self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") - self.device[SCPI].send_cmd(":SEQ:SEL 1") + self.change_armed_program(name) - for channel in self.channels: - channel[ActivatableChannels].enable() + def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): + self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table - for marker_ch in self.marker_channels: - marker_ch[ActivatableChannels].enable() + def set_program_sequence_table(self, name, new_sequence_table): + self._known_programs[name][1]._sequencer_tables = new_sequence_table - self._is_in_config_mode = False + @with_select + @with_configuration_guard + def change_armed_program(self, name: Optional[str]) -> None: + if name is None: + sequencer_tables = [self._idle_sequence_table] + advanced_sequencer_table = [(1, 1, 0)] + else: + waveform_to_segment_index, program = self._known_programs[name] + waveform_to_segment_number = waveform_to_segment_index + 1 + # translate waveform number to actual segment + sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) + for ((rep_count, wf_index, jump_flag), _) in sequencer_table] + for sequencer_table in program.get_sequencer_tables()] -######################################################################################################################## -# Marker Channel -######################################################################################################################## -# Features + # insert idle sequence + sequencer_tables = [self._idle_sequence_table] + sequencer_tables -class TaborActivatableMarkerChannels(ActivatableChannels): - def __init__(self, marker_channel: "TaborMarkerChannel"): - super().__init__() - self._parent = weakref.ref(marker_channel) + # adjust advanced sequence table entries by idle sequence table offset + advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) + for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] - @property - def enabled(self) -> bool: - pass # TODO: to implement + if program.waveform_mode == TaborSequencing.SINGLE: + assert len(advanced_sequencer_table) == 1 + assert len(sequencer_tables) == 2 - @with_select - def enable(self): - command_string = "SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" - command_string = command_string.format( - channel=self._parent().channel_tuple.channels[0].idn, - marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device[SCPI].send_cmd(command_string) + while len(sequencer_tables[1]) < self.device.dev_properties['min_seq_len']: + assert advanced_sequencer_table[0][0] == 1 + sequencer_tables[1].append((1, 1, 0)) - @with_select - def disable(self): - command_string = ":SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" - command_string = command_string.format( - channel=self._parent().channel_tuple.channels[0].idn, - marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device[SCPI].send_cmd(command_string) + # insert idle sequence in advanced sequence table + advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table + + while len(advanced_sequencer_table) < self.device.dev_properties['min_aseq_len']: + advanced_sequencer_table.append((1, 1, 0)) + + # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs + self.device.send_cmd('SEQ:DEL:ALL', paranoia_level=self.internal_paranoia_level) + self._sequencer_tables = [] + self.device.send_cmd('ASEQ:DEL', paranoia_level=self.internal_paranoia_level) + self._advanced_sequence_table = [] - def _select(self) -> None: - self._parent()._select() + # download all sequence tables + for i, sequencer_table in enumerate(sequencer_tables): + self.device.send_cmd('SEQ:SEL {}'.format(i+1), paranoia_level=self.internal_paranoia_level) + self.device.download_sequencer_table(sequencer_table) + self._sequencer_tables = sequencer_tables + self.device.send_cmd('SEQ:SEL 1', paranoia_level=self.internal_paranoia_level) + self.device.download_adv_seq_table(advanced_sequencer_table) + self._advanced_sequence_table = advanced_sequencer_table -# Implementation -class TaborMarkerChannel(AWGMarkerChannel): - def __init__(self, idn: int, device: TaborDevice): - super().__init__(idn) - self._device = weakref.ref(device) + self._current_program = name - # adding Features - self.add_feature(TaborActivatableMarkerChannels(self)) + @with_select + def run_current_program(self) -> None: + if self._current_program: + self.device.send_cmd(':TRIG', paranoia_level=self.internal_paranoia_level) + else: + raise RuntimeError('No program active') @property - def device(self) -> TaborDevice: - """Returns the device that this marker channel belongs to""" - return self._device() + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program for program in self._known_programs.keys()) @property - def channel_tuple(self) -> TaborChannelTuple: - """Returns the channel tuple that this marker channel belongs to""" - return self._channel_tuple() + def sample_rate(self) -> float: + return self.device.sample_rate(self._channels[0]) - def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: - """ - The channel tuple 'channel_tuple' is assigned to this marker channel + @property + def num_channels(self) -> int: + return 2 - Args: - channel_tuple (TaborChannelTuple): the channel tuple that this marker channel belongs to - """ - self._channel_tuple = weakref.ref(channel_tuple) + @property + def num_markers(self) -> int: + return 2 - def _select(self) -> None: - """ - This marker channel is selected and is now the active channel marker of the device - """ - self.device.channels[int((self.idn - 1) / 2)]._select() - self.device[SCPI].send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) + def _enter_config_mode(self) -> None: + """Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the + sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. + When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip.""" + if self._is_in_config_mode is False: + + # 1. Select channel pair + # 2. Select DC as function shape + # 3. Select build-in waveform mode + + if self.device.is_coupled(): + out_cmd = ':OUTP:ALL OFF' + else: + out_cmd = ':INST:SEL {};:OUTP OFF;:INST:SEL {};:OUTP OFF'.format(*self._channels) + + marker_0_cmd = ':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + marker_1_cmd = ':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF' + + wf_mode_cmd = ':SOUR:FUNC:MODE FIX' + + cmd = ';'.join([out_cmd, marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self._is_in_config_mode = True + + def _exit_config_mode(self) -> None: + """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" + sel_ch = ':INST:SEL {}'.format(self._channels[0]) + aseq_cmd = ':SOUR:FUNC:MODE ASEQ;SEQ:SEL 1' -######################################################################################################################## -class TaborException(Exception): - pass + cmds = [sel_ch, aseq_cmd] + + if self.device.is_coupled(): + # Coupled -> switch all channels at once + if self._channels == (1, 2): + other_channel_pair = self.device.channel_pair_CD + else: + assert self._channels == (3, 4) + other_channel_pair = self.device.channel_pair_AB + + if not other_channel_pair._is_in_config_mode: + cmds.append(':OUTP:ALL ON') + + else: + # ch 0 already selected + cmds.append(':OUTP ON; :INST:SEL {}; :OUTP ON'.format(self._channels[1])) + + cmds.append(':SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') + cmds.append(':SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT ON') + cmd = ';'.join(cmds) + self.device.send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self._is_in_config_mode = False class TaborUndefinedState(TaborException): - """ - If this exception is raised the attached tabor device is in an undefined state. - It is highly recommended to call reset it. - """ + """If this exception is raised the attached tabor device is in an undefined state. + It is highly recommended to call reset it.""" - def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): + def __init__(self, *args, device: Union[TaborAWGRepresentation, TaborChannelPair]): super().__init__(*args) self.device = device def reset_device(self): - if isinstance(self.device, TaborDevice): + if isinstance(self.device, TaborAWGRepresentation): self.device.reset() - elif isinstance(self.device, TaborChannelTuple): - self.device.cleanup() + elif isinstance(self.device, TaborChannelPair): self.device.clear() diff --git a/qupulse/hardware/awgs/tektronix.py b/qupulse/hardware/awgs/tektronix.py index e759fd4ed..ab41e0a4f 100644 --- a/qupulse/hardware/awgs/tektronix.py +++ b/qupulse/hardware/awgs/tektronix.py @@ -14,7 +14,7 @@ "If you wish to use it execute qupulse.hardware.awgs.install_requirements('tektronix')") raise -from qupulse.hardware.awgs.old_base import AWG, AWGAmplitudeOffsetHandling, ProgramOverwriteException +from qupulse.hardware.awgs.base import AWG, AWGAmplitudeOffsetHandling, ProgramOverwriteException from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible from qupulse._program.waveforms import Waveform as QuPulseWaveform diff --git a/qupulse/hardware/awgs/zihdawg.py b/qupulse/hardware/awgs/zihdawg.py index a7b1968f3..2b9c681e4 100644 --- a/qupulse/hardware/awgs/zihdawg.py +++ b/qupulse/hardware/awgs/zihdawg.py @@ -19,7 +19,7 @@ from qupulse.utils.types import ChannelID, TimeType, time_from_float from qupulse._program._loop import Loop, make_compatible from qupulse._program.seqc import HDAWGProgramManager, UserRegister -from qupulse.hardware.awgs.old_base import AWG, ChannelNotFoundException, AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs.base import AWG, ChannelNotFoundException, AWGAmplitudeOffsetHandling from qupulse.pulses.parameters import ConstantParameter diff --git a/qupulse/hardware/awgs_new_driver/base.py b/qupulse/hardware/awgs_new_driver/base.py new file mode 100644 index 000000000..675e4afae --- /dev/null +++ b/qupulse/hardware/awgs_new_driver/base.py @@ -0,0 +1,295 @@ +from abc import ABC, abstractmethod +from typing import Optional, Tuple, Sequence, Callable, List +from collections import OrderedDict + +import numpy + +from qupulse._program._loop import Loop +from qupulse._program.waveforms import Waveform +from qupulse.hardware.awgs_new_driver import channel_tuple_wrapper +from qupulse.hardware.awgs_new_driver.base_features import Feature, FeatureAble +from qupulse.hardware.util import get_sample_times +from qupulse.utils.types import Collection, TimeType, ChannelID + +__all__ = ["AWGDevice", "AWGChannelTuple", "AWGChannel", "AWGMarkerChannel", "AWGDeviceFeature", "AWGChannelFeature", + "AWGChannelTupleFeature"] + + +class AWGDeviceFeature(Feature, ABC): + """Base class for features that are used for `AWGDevice`s""" + def __init__(self): + super().__init__(AWGDevice) + + +class AWGChannelFeature(Feature, ABC): + """Base class for features that are used for `AWGChannel`s""" + def __init__(self): + super().__init__(_BaseAWGChannel) + + +class AWGChannelTupleFeature(Feature, ABC): + """Base class for features that are used for `AWGChannelTuple`s""" + def __init__(self): + super().__init__(AWGChannelTuple) + + +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 + + #def __del__(self): + # self.cleanup() + + @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) -> channel_tuple_wrapper: + pass + + @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() + + # Optional sample_rate-setter + # @sample_rate.setter + # def sample_rate(self, sample_rate: float) -> None: + + @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(Exception): + + 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) + + +class ProgramEntry: + """This is a helper class for implementing awgs drivers. A driver can subclass it to help organizing sampled + waveforms""" + def __init__(self, loop: Loop, + channels: Tuple[Optional[ChannelID], ...], + markers: Tuple[Optional[ChannelID], ...], + amplitudes: Tuple[float, ...], + offsets: Tuple[float, ...], + voltage_transformations: Tuple[Optional[Callable], ...], + sample_rate: TimeType, + waveforms: Sequence[Waveform] = None): + """ + + Args: + loop: + channels: + markers: + amplitudes: + offsets: + voltage_transformations: + sample_rate: + waveforms: These waveforms are sampled and stored in _waveforms. If None the waveforms are extracted from + loop + """ + assert len(channels) == len(amplitudes) == len(offsets) == len(voltage_transformations) + + self._channels = tuple(channels) + self._markers = tuple(markers) + self._amplitudes = tuple(amplitudes) + self._offsets = tuple(offsets) + self._voltage_transformations = tuple(voltage_transformations) + + self._sample_rate = sample_rate + + self._loop = loop + + if waveforms is None: + waveforms = OrderedDict((node.waveform, None) + for node in loop.get_depth_first_iterator() if node.is_leaf()).keys() + if waveforms: + self._waveforms = OrderedDict(zip(waveforms, self._sample_waveforms(waveforms))) + else: + self._waveforms = OrderedDict() + + def _sample_empty_channel(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: + """Override this in derived class to change how """ + return None + + def _sample_empty_marker(self, time: numpy.ndarray) -> Optional[numpy.ndarray]: + return None + + def _sample_waveforms(self, waveforms: Sequence[Waveform]) -> List[Tuple[Tuple[numpy.ndarray, ...], + Tuple[numpy.ndarray, ...]]]: + sampled_waveforms = [] + + time_array, segment_lengths = get_sample_times(waveforms, self._sample_rate) + for waveform, segment_length in zip(waveforms, segment_lengths): + wf_time = time_array[:segment_length] + + sampled_channels = [] + for channel, trafo, amplitude, offset in zip(self._channels, self._voltage_transformations, + self._amplitudes, self._offsets): + if channel is None: + sampled_channels.append(self._sample_empty_channel()) + else: + sampled = waveform.get_sampled(channel, wf_time) + if trafo is not None: + sampled = trafo(sampled) + sampled = sampled - offset + sampled /= amplitude + sampled_channels.append(waveform.get_sampled(channel, wf_time)) + + sampled_markers = [] + for marker in self._markers: + if marker is None: + sampled_markers.append(None) + else: + sampled_markers.append(waveform.get_sampled(marker, wf_time) != 0) + + sampled_waveforms.append((tuple(sampled_channels), tuple(sampled_markers))) + return sampled_waveforms + + +class OutOfWaveformMemoryException(Exception): + + def __str__(self) -> str: + return "Out of memory error adding waveform to waveform memory." + + +class ChannelNotFoundException(Exception): + def __init__(self, channel): + self.channel = channel + + def __str__(self) -> str: + return 'Marker or channel not found: {}'.format(self.channel) diff --git a/qupulse/hardware/awgs/base_features.py b/qupulse/hardware/awgs_new_driver/base_features.py similarity index 100% rename from qupulse/hardware/awgs/base_features.py rename to qupulse/hardware/awgs_new_driver/base_features.py diff --git a/qupulse/hardware/awgs/channel_tuple_wrapper.py b/qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py similarity index 78% rename from qupulse/hardware/awgs/channel_tuple_wrapper.py rename to qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py index b5e0bbd12..ef807ea60 100644 --- a/qupulse/hardware/awgs/channel_tuple_wrapper.py +++ b/qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py @@ -1,8 +1,8 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs.base import AWGChannelTuple -from qupulse.hardware.awgs.old_base import AWG +from qupulse.hardware.awgs_new_driver.base import AWGChannelTuple +from qupulse.hardware.awgs.base import AWG class ChannelTupleAdapter(AWG): @@ -32,24 +32,24 @@ def upload(self, name: str, markers: Tuple[Optional["ChannelID"], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: - from qupulse.hardware.awgs.tabor import ProgramManagement + from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].upload(name, program, channels, markers, voltage_transformation, force) def remove(self, name: str) -> None: - from qupulse.hardware.awgs.tabor import ProgramManagement + from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].remove(name) def clear(self) -> None: - from qupulse.hardware.awgs.tabor import ProgramManagement + from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].clear() def arm(self, name: Optional[str]) -> None: - from qupulse.hardware.awgs.tabor import ProgramManagement + from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].arm(name) def programs(self) -> Set[str]: - from qupulse.hardware.awgs.tabor import ProgramManagement + from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].programs def sample_rate(self) -> float: diff --git a/qupulse/hardware/awgs/features.py b/qupulse/hardware/awgs_new_driver/features.py similarity index 98% rename from qupulse/hardware/awgs/features.py rename to qupulse/hardware/awgs_new_driver/features.py index ceaf8b8e5..64bb5e097 100644 --- a/qupulse/hardware/awgs/features.py +++ b/qupulse/hardware/awgs_new_driver/features.py @@ -2,7 +2,7 @@ from typing import Callable, Optional, Set, Tuple, Dict, Union from qupulse._program._loop import Loop -from qupulse.hardware.awgs.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature +from qupulse.hardware.awgs_new_driver.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature from qupulse.utils.types import ChannelID import pyvisa diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py new file mode 100644 index 000000000..91d3c7a62 --- /dev/null +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -0,0 +1,1398 @@ +import functools +import logging +import numbers +import sys +import weakref +from typing import List, Tuple, Set, Callable, Optional, Any, cast, Union, Dict, Mapping, NamedTuple, Iterable +from collections import OrderedDict +import numpy as np +from qupulse import ChannelID +from qupulse._program._loop import Loop, make_compatible +from qupulse.hardware.awgs_new_driver.channel_tuple_wrapper import ChannelTupleAdapter +from qupulse.hardware.awgs_new_driver.features import ChannelSynchronization, AmplitudeOffsetHandling, VoltageRange, \ + ProgramManagement, ActivatableChannels, DeviceControl, StatusTable, SCPI, VolatileParameters, \ + ReadProgram +from qupulse.hardware.util import voltage_to_uint16, find_positions +from qupulse.utils.types import Collection, TimeType +from qupulse.hardware.awgs_new_driver.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel +from typing import Sequence +from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ + make_combined_wave +import pyvisa +import warnings + +# Provided by Tabor electronics for python 2.7 +# a python 3 version is in a private repository on https://git.rwth-aachen.de/qutech +# Beware of the string encoding change! +import teawg + +assert (sys.byteorder == "little") + +__all__ = ["TaborDevice", "TaborChannelTuple", "TaborChannel"] + +TaborProgramMemory = NamedTuple("TaborProgramMemory", [("waveform_to_segment", np.ndarray), + ("program", TaborProgram)]) + + +def with_configuration_guard(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[ + ["TaborChannelTuple"], Any]: + """This decorator assures that the AWG is in configuration mode while the decorated method runs.""" + + @functools.wraps(function_object) + def guarding_method(channel_pair: "TaborChannelTuple", *args, **kwargs) -> Any: + + if channel_pair._configuration_guard_count == 0: + channel_pair._enter_config_mode() + channel_pair._configuration_guard_count += 1 + + try: + return function_object(channel_pair, *args, **kwargs) + finally: + channel_pair._configuration_guard_count -= 1 + if channel_pair._configuration_guard_count == 0: + channel_pair._exit_config_mode() + + return guarding_method + + +def with_select(function_object: Callable[["TaborChannelTuple", Any], Any]) -> Callable[["TaborChannelTuple"], Any]: + """Asserts the channel pair is selcted when the wrapped function is called""" + + @functools.wraps(function_object) + def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: + channel_tuple._select() + return function_object(channel_tuple, *args, **kwargs) + + return selector + + +######################################################################################################################## +# Device +######################################################################################################################## +# Features +class TaborSCPI(SCPI): + def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedResource): + super().__init__(visa) + + self._parent = weakref.ref(device) + + def send_cmd(self, cmd_str, paranoia_level=None): + print(cmd_str) + for instr in self._parent().all_devices: + instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) + + def send_query(self, query_str, query_mirrors=False) -> Any: + if query_mirrors: + return tuple(instr.send_query(query_str) for instr in self._parent().all_devices) + else: + return self._parent().main_instrument.send_query(query_str) + + def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: + """Overwrite send_cmd for paranoia_level > 3""" + if paranoia_level is None: + paranoia_level = self._parent().paranoia_level + + if paranoia_level < 3: + self._parent().super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover + else: + cmd_str = cmd_str.rstrip() + + if len(cmd_str) > 0: + ask_str = cmd_str + "; *OPC?; :SYST:ERR?" + else: + ask_str = "*OPC?; :SYST:ERR?" + + *answers, opc, error_code_msg = self._parent()._visa_inst.ask(ask_str).split(";") + + error_code, error_msg = error_code_msg.split(",") + error_code = int(error_code) + if error_code != 0: + _ = self._parent()._visa_inst.ask("*CLS; *OPC?") + + if error_code == -450: + # query queue overflow + self.send_cmd(cmd_str) + else: + raise RuntimeError("Cannot execute command: {}\n{}: {}".format(cmd_str, error_code, error_msg)) + + assert len(answers) == 0 + + +class TaborChannelSynchronization(ChannelSynchronization): + """This Feature is used to synchronise a certain ammount of channels""" + + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = weakref.ref(device) + + def synchronize_channels(self, group_size: int) -> None: + """ + Synchronize in groups of `group_size` channels. Groups of synchronized channels will be provided as + AWGChannelTuples. The channel_size must be evenly dividable by the number of channels + + Args: + group_size: Number of channels per channel tuple + """ + if group_size == 2: + self._parent()._channel_tuples = [] + for i in range((int)(len(self._parent().channels) / group_size)): + self._parent()._channel_tuples.append( + TaborChannelTuple((i + 1), + self._parent(), + self._parent().channels[(i * group_size):((i * group_size) + group_size)], + self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) + ) + self._parent()[SCPI].send_cmd(":INST:COUP:STAT OFF") + elif group_size == 4: + self._parent()._channel_tuples = [TaborChannelTuple(1, + self._parent(), + self._parent().channels, + self._parent().marker_channels)] + self._parent()[SCPI].send_cmd(":INST:COUP:STAT ON") + else: + raise TaborException("Invalid group size") + + +class TaborDeviceControl(DeviceControl): + """This feature is used for basic communication with a AWG""" + + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = weakref.ref(device) + + def reset(self) -> None: + """ + Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and + all channel tuples are cleared. + """ + self._parent()[SCPI].send_cmd(":RES") + self._parent()._coupled = None + + self._parent()._initialize() + for channel_tuple in self._parent().channel_tuples: + channel_tuple[TaborProgramManagement].clear() + + def trigger(self) -> None: + """ + This method triggers a device remotely. + """ + self._parent()[SCPI].send_cmd(":TRIG") + + +class TaborStatusTable(StatusTable): + def __init__(self, device: "TaborDevice"): + super().__init__() + self._parent = device + + def get_status_table(self) -> Dict[str, Union[str, float, int]]: + """ + Send a lot of queries to the AWG about its settings. A good way to visualize is using pandas.DataFrame + + Returns: + An ordered dictionary with the results + """ + name_query_type_list = [("channel", ":INST:SEL?", int), + ("coupling", ":OUTP:COUP?", str), + ("volt_dc", ":SOUR:VOLT:LEV:AMPL:DC?", float), + ("volt_hv", ":VOLT:HV?", float), + ("offset", ":VOLT:OFFS?", float), + ("outp", ":OUTP?", str), + ("mode", ":SOUR:FUNC:MODE?", str), + ("shape", ":SOUR:FUNC:SHAPE?", str), + ("dc_offset", ":SOUR:DC?", float), + ("freq_rast", ":FREQ:RAST?", float), + + ("gated", ":INIT:GATE?", str), + ("continuous", ":INIT:CONT?", str), + ("continuous_enable", ":INIT:CONT:ENAB?", str), + ("continuous_source", ":INIT:CONT:ENAB:SOUR?", str), + ("marker_source", ":SOUR:MARK:SOUR?", str), + ("seq_jump_event", ":SOUR:SEQ:JUMP:EVEN?", str), + ("seq_adv_mode", ":SOUR:SEQ:ADV?", str), + ("aseq_adv_mode", ":SOUR:ASEQ:ADV?", str), + + ("marker", ":SOUR:MARK:SEL?", int), + ("marker_high", ":MARK:VOLT:HIGH?", str), + ("marker_low", ":MARK:VOLT:LOW?", str), + ("marker_width", ":MARK:WIDT?", int), + ("marker_state", ":MARK:STAT?", str)] + + data = OrderedDict((name, []) for name, *_ in name_query_type_list) + for ch in (1, 2, 3, 4): + self._parent.channels[ch - 1]._select() + self._parent.marker_channels[(ch - 1) % 2]._select() + for name, query, dtype in name_query_type_list: + data[name].append(dtype(self._parent[SCPI].send_query(query))) + return data + + +# Implementation +class TaborDevice(AWGDevice): + def __init__(self, device_name: str, instr_addr=None, paranoia_level=1, external_trigger=False, reset=False, + mirror_addresses=()): + """ + Constructor for a Tabor device + + Args: + device_name (str): Name of the device + instr_addr: Instrument address that is forwarded to teawag + paranoia_level (int): Paranoia level that is forwarded to teawg + external_trigger (bool): Not supported yet + reset (bool): + mirror_addresses: list of devices on which the same things as on the main device are done. + For example you can a simulator and a real Device at once + """ + super().__init__(device_name) + self._instr = teawg.TEWXAwg(instr_addr, paranoia_level) + self._mirrors = tuple(teawg.TEWXAwg(address, paranoia_level) for address in mirror_addresses) + self._coupled = None + self._clock_marker = [0, 0, 0, 0] + + self.add_feature(TaborSCPI(self, self.main_instrument._visa_inst)) + self.add_feature(TaborDeviceControl(self)) + self.add_feature(TaborStatusTable(self)) + + if reset: + self[SCPI].send_cmd(":RES") + + # Channel + self._channels = [TaborChannel(i + 1, self) for i in range(4)] + + # MarkerChannels + self._marker_channels = [TaborMarkerChannel(i + 1, self) for i in range(4)] + + self._initialize() + + # ChannelTuple + self._channel_tuples = [] + + self.add_feature(TaborChannelSynchronization(self)) + self[TaborChannelSynchronization].synchronize_channels(2) + + if external_trigger: + raise NotImplementedError() # pragma: no cover + + def enable(self) -> None: + """ + This method immediately generates the selected output waveform, if the device is in continuous and armed + repetition mode. + """ + self[SCPI].send_cmd(":ENAB") + + def abort(self) -> None: + """ + With abort you can terminate the current generation of the output waveform. When the output waveform is + terminated the output starts generating an idle waveform. + """ + self[SCPI].send_cmd(":ABOR") + + def set_coupled(self, coupled: bool) -> None: + """ + Thats the coupling of the device to 'coupled' + """ + if coupled: + self[SCPI].send_cmd("INST:COUP:STAT ON") + else: + self[SCPI].send_cmd("INST:COUP:STAT OFF") + + def _is_coupled(self) -> bool: + # TODO: comment is missing + if self._coupled is None: + return self[SCPI].send_query(":INST:COUP:STAT?") == "ON" + else: + return self._coupled + + def cleanup(self) -> None: + # TODO: split cleanup in to two different methods + for channel_tuple in self.channel_tuples: + channel_tuple.cleanup() + + @property + def channels(self) -> Collection["TaborChannel"]: + """Returns a list of all channels of a Device""" + return self._channels + + @property + def marker_channels(self) -> Collection["TaborMarkerChannel"]: + """Returns a list of all marker channels of a device. The collection may be empty""" + return self._marker_channels + + @property + def channel_tuples(self) -> Collection["TaborChannelTuple"]: + """Returns a list of all channel tuples of a list""" + return self._channel_tuples + + @property + def main_instrument(self) -> teawg.TEWXAwg: + return self._instr + + @property + def mirrored_instruments(self) -> Sequence[teawg.TEWXAwg]: + return self._mirrors + + @property + def all_devices(self) -> Sequence[teawg.TEWXAwg]: + return (self._instr,) + self._mirrors + + @property + def _paranoia_level(self) -> int: + return self._instr.paranoia_level + + @_paranoia_level.setter + def _paranoia_level(self, val): + for instr in self.all_devices: + instr.paranoia_level = val + + @property + def dev_properties(self) -> dict: + return self._instr.dev_properties + + def _send_binary_data(self, pref, bin_dat, paranoia_level=None): + for instr in self.all_devices: + instr.send_binary_data(pref, bin_dat=bin_dat, paranoia_level=paranoia_level) + + def _download_segment_lengths(self, seg_len_list, pref=":SEGM:DATA", paranoia_level=None): + for instr in self.all_devices: + instr.download_segment_lengths(seg_len_list, pref=pref, paranoia_level=paranoia_level) + + def _download_sequencer_table(self, seq_table, pref=":SEQ:DATA", paranoia_level=None): + for instr in self.all_devices: + instr.download_sequencer_table(seq_table, pref=pref, paranoia_level=paranoia_level) + + def _download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=None): + for instr in self.all_devices: + instr.download_adv_seq_table(seq_table, pref=pref, paranoia_level=paranoia_level) + + make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) + + def _initialize(self) -> None: + # TODO: work on this comment + + # 1. Select channel + # 2. Turn off gated mode + # 3. continous mode + # 4. Armed mode (only generate waveforms after enab command) + # 5. Expect enable signal from (USB / LAN / GPIB) + # 6. Use arbitrary waveforms as marker source + # 7. Expect jump command for sequencing from (USB / LAN / GPIB) + + setup_command = ( + ":INIT:GATE OFF; :INIT:CONT ON; " + ":INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR BUS;" + ":SOUR:MARK:SOUR USER; :SOUR:SEQ:JUMP:EVEN BUS ") + self[SCPI].send_cmd(":INST:SEL 1") + self[SCPI].send_cmd(setup_command) + self[SCPI].send_cmd(":INST:SEL 3") + self[SCPI].send_cmd(setup_command) + + def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: + """ + A method to get the first readable device out of all devices. + A readable device is a device which you can read data from like a simulator. + + Returns: + The first readable device out of all devices + + Throws: + TaborException: this exception is thrown if there is no readable device in the list of all devices + """ + for device in self.all_devices: + if device.fw_ver >= 3.0: + if simulator: + if device.is_simulator: + return device + else: + return device + raise TaborException("No device capable of device data read") + + +######################################################################################################################## +# Channel +######################################################################################################################## +# Features +class TaborVoltageRange(VoltageRange): + def __init__(self, channel: "TaborChannel"): + super().__init__() + self._parent = weakref.ref(channel) + + @property + @with_select + def offset(self) -> float: + """Get offset of AWG channel""" + return float( + self._parent().device[SCPI].send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) + + @property + @with_select + def amplitude(self) -> float: + """Get amplitude of AWG channel""" + coupling = self._parent().device[SCPI].send_query(":OUTP:COUP?") + if coupling == "DC": + return float(self._parent().device[SCPI].send_query(":VOLT?")) + elif coupling == "HV": + return float(self._parent().device[SCPI].send_query(":VOLT:HV?")) + else: + raise TaborException("Unknown coupling: {}".format(coupling)) + + @property + def amplitude_offset_handling(self) -> str: + """ + Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and + offset settings are constant or if these should be optimized by the driver + """ + return self._parent()._amplitude_offset_handling + + @amplitude_offset_handling.setter + def amplitude_offset_handling(self, amp_offs_handling: str) -> None: + """ + amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` + """ + if amp_offs_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET or amp_offs_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: + self._parent()._amplitude_offset_handling = amp_offs_handling + else: + raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(amp_offs_handling)) + + def _select(self) -> None: + self._parent()._select() + + +class TaborActivatableChannels(ActivatableChannels): + def __init__(self, channel: "TaborChannel"): + super().__init__() + self._parent = weakref.ref(channel) + + @property + def enabled(self) -> bool: + """ + Returns the the state a channel has at the moment. A channel is either activated or deactivated + """ + pass # TODO: to implement + + @with_select + def enable(self): + """Enables the output of a certain channel""" + command_string = ":OUTP ON".format(ch_id=self._parent().idn) + self._parent().device[SCPI].send_cmd(command_string) + + @with_select + def disable(self): + """Disables the output of a certain channel""" + command_string = ":OUTP OFF".format(ch_id=self._parent().idn) + self._parent().device[SCPI].send_cmd(command_string) + + def _select(self) -> None: + self._parent()._select() + + +# Implementation +class TaborChannel(AWGChannel): + def __init__(self, idn: int, device: TaborDevice): + super().__init__(idn) + + self._device = weakref.ref(device) + self._amplitude_offset_handling = AmplitudeOffsetHandling.IGNORE_OFFSET + + # adding Features + self.add_feature(TaborVoltageRange(self)) + self.add_feature(TaborActivatableChannels(self)) + + @property + def device(self) -> TaborDevice: + """Returns the device that the channel belongs to""" + return self._device() + + @property + def channel_tuple(self) -> "TaborChannelTuple": + """Returns the channel tuple that this channel belongs to""" + return self._channel_tuple() + + def _set_channel_tuple(self, channel_tuple: "TaborChannelTuple") -> None: + """ + The channel tuple "channel_tuple" is assigned to this channel + + Args: + channel_tuple (TaborChannelTuple): the channel tuple that this channel belongs to + """ + self._channel_tuple = weakref.ref(channel_tuple) + + def _select(self) -> None: + self.device[SCPI].send_cmd(":INST:SEL {channel}".format(channel=self.idn)) + + +######################################################################################################################## +# ChannelTuple +######################################################################################################################## +# Features +class TaborProgramManagement(ProgramManagement): + def __init__(self, channel_tuple: "TaborChannelTuple"): + super().__init__() + self._programs = {} + self._armed_program = None + self._parent = weakref.ref(channel_tuple) + + self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] + self._trigger_source = 'BUS' + + def get_repetition_mode(self, program_name: str) -> str: + """ + Returns the default repetition mode of a certain program + Args: + program_name (str): name of the program whose repetition mode should be returned + """ + return self._parent()._known_programs[program_name].program._repetition_mode + + def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: + """ + Changes the default repetition mode of a certain program + + Args: + program_name (str): name of the program whose repetition mode should be changed + + Throws: + ValueError: this Exception is thrown when an invalid repetition mode is given + """ + if repetition_mode is "infinite" or repetition_mode is "once": + self._parent()._known_programs[program_name].program._repetition_mode = repetition_mode + else: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + + @with_configuration_guard + @with_select + def upload(self, name: str, + program: Loop, + channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + marker_channels: Tuple[Optional[ChannelID], Optional[ChannelID]], + voltage_transformation: Tuple[Callable, Callable], + repetition_mode: str = None, + force: bool = False) -> None: + """ + Upload a program to the AWG. + + The policy is to prefer amending the unknown waveforms to overwriting old ones. + """ + + if repetition_mode is None: + warnings.warn("TaborWarning - upload() - no repetition mode given!") + repetition_mode = "infinite" + elif repetition_mode not in ("infinite"): + raise TaborException("Invalid Repetionmode: " + repetition_mode) + + if len(channels) != len(self._parent().channels): + raise ValueError("Wrong number of channels") + if len(marker_channels) != len(self._parent().marker_channels): + raise ValueError("Wrong number of marker") + if len(voltage_transformation) != len(self._parent().channels): + raise ValueError("Wrong number of voltage transformations") + + # adjust program to fit criteria + sample_rate = self._parent().device.channel_tuples[0].sample_rate + make_compatible(program, + minimal_waveform_length=192, + waveform_quantum=16, + sample_rate=sample_rate / 10 ** 9) + + if name in self._parent()._known_programs: + if force: + self._parent().free_program(name) + else: + raise ValueError('{} is already known on {}'.format(name, self._parent().idn)) + + # They call the peak to peak range amplitude + + ranges = tuple(ch[VoltageRange].amplitude for ch in self._parent().channels) + + voltage_amplitudes = tuple(range / 2 for range in ranges) + + voltage_offsets = [] + for channel in self._parent().channels: + if channel._amplitude_offset_handling == AmplitudeOffsetHandling.IGNORE_OFFSET: + voltage_offsets.append(0) + elif channel._amplitude_offset_handling == AmplitudeOffsetHandling.CONSIDER_OFFSET: + voltage_offsets.append(channel[VoltageRange].offset) + else: + raise ValueError( + '{} is invalid as AWGAmplitudeOffsetHandling'.format(channel._amplitude_offset_handling)) + voltage_offsets = tuple(voltage_offsets) + + # parse to tabor program + tabor_program = TaborProgram(program, + channels=tuple(channels), + markers=marker_channels, + device_properties=self._parent().device.dev_properties, + sample_rate=sample_rate / 10 ** 9, + amplitudes=voltage_amplitudes, + offsets=voltage_offsets, + voltage_transformations=voltage_transformation) + + segments, segment_lengths = tabor_program.get_sampled_segments() + + waveform_to_segment, to_amend, to_insert = self._parent()._find_place_for_segments_in_memory(segments, + segment_lengths) + + self._parent()._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 + + for wf_index in np.flatnonzero(to_insert > 0): + segment_index = to_insert[wf_index] + self._parent()._upload_segment(to_insert[wf_index], segments[wf_index]) + waveform_to_segment[wf_index] = segment_index + + if np.any(to_amend): + segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] + waveform_to_segment[to_amend] = self._parent()._amend_segments(segments_to_amend) + + self._parent()._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, + program=tabor_program) + + # set the default repetionmode for a programm + self.set_repetition_mode(program_name=name, repetition_mode=repetition_mode) + + def remove(self, name: str) -> None: + """ + Remove a program from the AWG. + + Also discards all waveforms referenced only by the program identified by name. + + Args: + name (str): The name of the program to remove. + """ + self._parent().free_program(name) + self._parent().cleanup() + + def clear(self) -> None: + """ + Removes all programs and waveforms from the AWG. + + Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! + """ + + self._parent().device.channels[0]._select() + self._parent().device[SCPI].send_cmd(":TRAC:DEL:ALL") + self._parent().device[SCPI].send_cmd(":SOUR:SEQ:DEL:ALL") + self._parent().device[SCPI].send_cmd(":ASEQ:DEL") + + self._parent().device[SCPI].send_cmd(":TRAC:DEF 1, 192") + self._parent().device[SCPI].send_cmd(":TRAC:SEL 1") + self._parent().device[SCPI].send_cmd(":TRAC:MODE COMB") + self._parent().device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._parent()._idle_segment.get_as_binary()) + + self._parent()._segment_lengths = 192 * np.ones(1, dtype=np.uint32) + self._parent()._segment_capacity = 192 * np.ones(1, dtype=np.uint32) + self._parent()._segment_hashes = np.ones(1, dtype=np.int64) * hash(self._parent()._idle_segment) + self._parent()._segment_references = np.ones(1, dtype=np.uint32) + + self._parent()._advanced_sequence_table = [] + self._parent()._sequencer_tables = [] + + self._parent()._known_programs = dict() + self._change_armed_program(None) + + @with_select + def arm(self, name: Optional[str]) -> None: + """ + Load the program 'name' and arm the device for running it. + + Args: + name (str): the program the device should change to + """ + if self._parent()._current_program == name: + self._parent().device[SCPI].send_cmd("SEQ:SEL 1") + else: + self._change_armed_program(name) + + @property + def programs(self) -> Set[str]: + """The set of program names that can currently be executed on the hardware AWG.""" + return set(program.name for program in self._parent()._known_programs.keys()) + + @with_select + def run_current_program(self) -> None: + """ + This method starts running the active program + + Throws: + RuntimeError: This exception is thrown if there is no active program for this device + """ + if (self._parent().device._is_coupled()): + # channel tuple is the first channel tuple + if (self._parent.device._channel_tuples[0] == self): + if self._parent()._current_program: + repetition_mode = self._parent()._known_programs[ + self._parent()._current_program].program._repetition_mode + if repetition_mode is "infinite": + self._cont_repetition_mode() + self._parent().device[SCPI].send_cmd(':TRIG', + paranoia_level=self._parent().internal_paranoia_level) + else: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + else: + raise RuntimeError("No program active") + else: + warnings.warn( + "TaborWarning - run_current_program() - the device is coupled - runthe program via the first channel tuple") + + else: + if self._parent()._current_program: + repetition_mode = self._parent()._known_programs[ + self._parent()._current_program].program._repetition_mode + if repetition_mode is "infinite": + self._cont_repetition_mode() + self._parent().device[SCPI].send_cmd(':TRIG', paranoia_level=self._parent().internal_paranoia_level) + else: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + else: + raise RuntimeError("No program active") + + @with_select + @with_configuration_guard + def _change_armed_program(self, name: Optional[str]) -> None: + """The armed program of the channel tuple is changed to the program with the name 'name'""" + if name is None: + sequencer_tables = [self._idle_sequence_table] + advanced_sequencer_table = [(1, 1, 0)] + else: + waveform_to_segment_index, program = self._parent()._known_programs[name] + waveform_to_segment_number = waveform_to_segment_index + 1 + + # translate waveform number to actual segment + sequencer_tables = [[(rep_count, waveform_to_segment_number[wf_index], jump_flag) + for ((rep_count, wf_index, jump_flag), _) in sequencer_table] + for sequencer_table in program.get_sequencer_tables()] + + # insert idle sequence + sequencer_tables = [self._idle_sequence_table] + sequencer_tables + + # adjust advanced sequence table entries by idle sequence table offset + advanced_sequencer_table = [(rep_count, seq_no + 1, jump_flag) + for rep_count, seq_no, jump_flag in program.get_advanced_sequencer_table()] + + if program.waveform_mode == TaborSequencing.SINGLE: + assert len(advanced_sequencer_table) == 1 + assert len(sequencer_tables) == 2 + + while len(sequencer_tables[1]) < self._parent().device.dev_properties["min_seq_len"]: + assert advanced_sequencer_table[0][0] == 1 + sequencer_tables[1].append((1, 1, 0)) + + # insert idle sequence in advanced sequence table + advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table + + while len(advanced_sequencer_table) < self._parent().device.dev_properties["min_aseq_len"]: + advanced_sequencer_table.append((1, 1, 0)) + + self._parent().device[SCPI].send_cmd("SEQ:DEL:ALL", paranoia_level=self._parent().internal_paranoia_level) + self._parent()._sequencer_tables = [] + self._parent().device[SCPI].send_cmd("ASEQ:DEL", paranoia_level=self._parent().internal_paranoia_level) + self._parent()._advanced_sequence_table = [] + + # download all sequence tables + for i, sequencer_table in enumerate(sequencer_tables): + self._parent().device[SCPI].send_cmd("SEQ:SEL {}".format(i + 1), + paranoia_level=self._parent().internal_paranoia_level) + self._parent().device._download_sequencer_table(sequencer_table) + self._parent()._sequencer_tables = sequencer_tables + self._parent().device[SCPI].send_cmd("SEQ:SEL 1", paranoia_level=self._parent().internal_paranoia_level) + + self._parent().device._download_adv_seq_table(advanced_sequencer_table) + self._parent()._advanced_sequence_table = advanced_sequencer_table + + self._parent()._current_program = name + + def _select(self): + self._parent().channels[0]._select() + + @property + def _configuration_guard_count(self): + return self._parent()._configuration_guard_count + + @_configuration_guard_count.setter + def _configuration_guard_count(self, configuration_guard_count): + self._parent()._configuration_guard_count = configuration_guard_count + + def _enter_config_mode(self): + self._parent()._enter_config_mode() + + def _exit_config_mode(self): + self._parent()._exit_config_mode() + + @with_select + def _cont_repetition_mode(self): + """Changes the run mode of this channel tuple to continous mode""" + self._parent().device[SCPI].send_cmd(f":TRIG:SOUR:ADV EXT") + self._parent().device[SCPI].send_cmd( + f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + + +class TaborVolatileParameters(VolatileParameters): + def __init__(self, channel_tuple: "TaborChannelTuple", ): + super().__init__() + self._parent = weakref.ref(channel_tuple) + + def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, numbers.Number]) -> None: + """ Set the values of parameters which were marked as volatile on program creation. Sets volatile parameters + in program memory and device's (adv.) sequence tables if program is current program. + + If set_volatile_parameters needs to run faster, set CONFIG_MODE_PARANOIA_LEVEL to 0 which causes the device to + enter the configuration mode with paranoia level 0 (Note: paranoia level 0 does not work for the simulator) + and set device._is_coupled. + + Args: + program_name: Name of program which should be changed. + parameters: Names of volatile parameters and respective values to which they should be set. + """ + waveform_to_segment_index, program = self._parent()._known_programs[program_name] + modifications = program.update_volatile_parameters(parameters) + + self._parent().logger.debug("parameter modifications: %r" % modifications) + + if not modifications: + self._parent().logger.info( + "There are no volatile parameters to update. Either there are no volatile parameters with " + "these names,\nthe respective repetition counts already have the given values or the " + "volatile parameters were dropped during upload.") + return + + if program_name == self._parent()._current_program: + commands = [] + + for position, entry in modifications.items(): + if not entry.repetition_count > 0: + raise ValueError("Repetition must be > 0") + + if isinstance(position, int): + commands.append(":ASEQ:DEF {},{},{},{}".format(position + 1, entry.element_number + 1, + entry.repetition_count, entry.jump_flag)) + else: + table_num, step_num = position + commands.append(":SEQ:SEL {}".format(table_num + 2)) + commands.append(":SEQ:DEF {},{},{},{}".format(step_num, + waveform_to_segment_index[entry.element_id] + 1, + entry.repetition_count, entry.jump_flag)) + self._parent()._execute_multiple_commands_with_config_guard(commands) + + # Wait until AWG is finished + _ = self._parent().device.main_instrument._visa_inst.query("*OPC?") + + +class TaborReadProgram(ReadProgram): + def __init__(self, channel_tuple: "TaborChannelTuple", ): + super().__init__() + self._parent = weakref.ref(channel_tuple) + + def read_complete_program(self): + return PlottableProgram.from_read_data(self._parent().read_waveforms(), + self._parent().read_sequence_tables(), + self._parent().read_advanced_sequencer_table()) + + +# Implementation +class TaborChannelTuple(AWGChannelTuple): + CONFIG_MODE_PARANOIA_LEVEL = None + + def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], + marker_channels: Iterable["TaborMarkerChannel"]): + super().__init__(idn) + self._device = weakref.ref(device) + + self._configuration_guard_count = 0 + self._is_in_config_mode = False + + self._channels = tuple(channels) + self._marker_channels = tuple(marker_channels) + + # the channel and channel marker are assigned to this channel tuple + for channel in self.channels: + channel._set_channel_tuple(self) + for marker_ch in self.marker_channels: + marker_ch._set_channel_tuple(self) + + # adding Features + self.add_feature(TaborProgramManagement(self)) + self.add_feature(TaborVolatileParameters(self)) + + self._idle_segment = TaborSegment.from_sampled(voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + voltage_to_uint16(voltage=np.zeros(192), + output_amplitude=0.5, + output_offset=0., resolution=14), + None, None) + + self._known_programs = dict() # type: Dict[str, TaborProgramMemory] + self._current_program = None + + self._segment_lengths = None + self._segment_capacity = None + self._segment_hashes = None + self._segment_references = None + + self._sequencer_tables = None + self._advanced_sequence_table = None + + self._internal_paranoia_level = 0 + + self[TaborProgramManagement].clear() + + self._channel_tuple_adapter: ChannelTupleAdapter + + @property + def internal_paranoia_level(self) -> Optional[int]: + return self._internal_paranoia_level + + @property + def logger(self): + return logging.getLogger("qupulse.tabor") + + @property + def channel_tuple_adapter(self) -> ChannelTupleAdapter: + if self._channel_tuple_adapter is None: + self._channel_tuple_adapter = ChannelTupleAdapter(self) + return self._channel_tuple_adapter + + def _select(self) -> None: + """The channel tuple is selected, which means that the first channel of the channel tuple is selected""" + self.channels[0]._select() + + @property + def device(self) -> TaborDevice: + """Returns the device that the channel tuple belongs to""" + return self._device() + + @property + def channels(self) -> Collection["TaborChannel"]: + """Returns all channels of the channel tuple""" + return self._channels + + @property + def marker_channels(self) -> Collection["TaborMarkerChannel"]: + """Returns all marker channels of the channel tuple""" + return self._marker_channels + + @property + @with_select + def sample_rate(self) -> TimeType: + """Returns the sample rate that the channels of a channel tuple have""" + return TimeType.from_float( + float(self.device[SCPI].send_query(":FREQ:RAST?".format(channel=self.channels[0].idn)))) + + @property + def total_capacity(self) -> int: + return int(self.device.dev_properties["max_arb_mem"]) // 2 + + def free_program(self, name: str) -> TaborProgramMemory: + if name is None: + raise TaborException("Removing 'None' program is forbidden.") + program = self._known_programs.pop(name) + self._segment_references[program.waveform_to_segment] -= 1 + if self._current_program == name: + self[TaborProgramManagement]._change_armed_program(None) + return program + + def _restore_program(self, name: str, program: TaborProgram) -> None: + if name in self._known_programs: + raise ValueError("Program cannot be restored as it is already known.") + self._segment_references[program.waveform_to_segment] += 1 + self._known_programs[name] = program + + @property + def _segment_reserved(self) -> np.ndarray: + return self._segment_references > 0 + + @property + def _free_points_in_total(self) -> int: + return self.total_capacity - np.sum(self._segment_capacity[self._segment_reserved]) + + @property + def _free_points_at_end(self) -> int: + reserved_index = np.flatnonzero(self._segment_reserved) + if len(reserved_index): + return self.total_capacity - np.sum(self._segment_capacity[:reserved_index[-1]]) + else: + return self.total_capacity + + @with_select + def read_waveforms(self) -> List[np.ndarray]: + device = self.device._get_readable_device(simulator=True) + + old_segment = device[SCPI].send_query(":TRAC:SEL?") + waveforms = [] + uploaded_waveform_indices = np.flatnonzero( + self._segment_references) + 1 + + for segment in uploaded_waveform_indices: + device[SCPI].send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) + waveforms.append(device.read_act_seg_dat()) + device[SCPI].send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) + return waveforms + + @with_select + def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: + device = self.device._get_readable_device(simulator=True) + + old_sequence = device[SCPI].send_query(":SEQ:SEL?") + sequences = [] + uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 + for sequence in uploaded_sequence_indices: + device[SCPI].send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) + sequences.append(device.read_sequencer_table()) + device[SCPI].send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) + return sequences + + @with_select + def read_advanced_sequencer_table(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + return self.device._get_readable_device(simulator=True).read_adv_seq_table() + + def read_complete_program(self) -> PlottableProgram: + return PlottableProgram.from_read_data(self.read_waveforms(), + self.read_sequence_tables(), + self.read_advanced_sequencer_table()) + + def _find_place_for_segments_in_memory(self, segments: Sequence, segment_lengths: Sequence) -> \ + Tuple[np.ndarray, np.ndarray, np.ndarray]: + # TODO: comment was not finished + """ + 1. Find known segments + 2. Find empty spaces with fitting length + 3. Find empty spaces with bigger length + 4. Amend remaining segments + + Args: + segments (Sequence): + segment_length (Sequence): + + Returns: + + """ + segment_hashes = np.fromiter((hash(segment) for segment in segments), count=len(segments), dtype=np.int64) + + waveform_to_segment = find_positions(self._segment_hashes, segment_hashes) + + # separate into known and unknown + unknown = (waveform_to_segment == -1) + known = ~unknown + + known_pos_in_memory = waveform_to_segment[known] + + assert len(known_pos_in_memory) == 0 or np.all( + self._segment_hashes[known_pos_in_memory] == segment_hashes[known]) + + new_reference_counter = self._segment_references.copy() + new_reference_counter[known_pos_in_memory] += 1 + + to_upload_size = np.sum(segment_lengths[unknown] + 16) + free_points_in_total = self.total_capacity - np.sum(self._segment_capacity[self._segment_references > 0]) + if free_points_in_total < to_upload_size: + raise MemoryError("Not enough free memory", + free_points_in_total, + to_upload_size, + self._free_points_in_total) + + to_amend = cast(np.ndarray, unknown) + to_insert = np.full(len(segments), fill_value=-1, dtype=np.int64) + + reserved_indices = np.flatnonzero(new_reference_counter > 0) + first_free = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + + free_segments = new_reference_counter[:first_free] == 0 + free_segment_count = np.sum(free_segments) + + # look for a free segment place with the same length + for segment_idx in np.flatnonzero(to_amend): + if free_segment_count == 0: + break + + pos_of_same_length = np.logical_and(free_segments, + segment_lengths[segment_idx] == self._segment_capacity[:first_free]) + idx_same_length = np.argmax(pos_of_same_length) + if pos_of_same_length[idx_same_length]: + free_segments[idx_same_length] = False + free_segment_count -= 1 + + to_amend[segment_idx] = False + to_insert[segment_idx] = idx_same_length + + # try to find places that are larger than the segments to fit in starting with the large segments and large + # free spaces + segment_indices = np.flatnonzero(to_amend)[np.argsort(segment_lengths[to_amend])[::-1]] + capacities = self._segment_capacity[:first_free] + for segment_idx in segment_indices: + free_capacities = capacities[free_segments] + free_segments_indices = np.flatnonzero(free_segments)[np.argsort(free_capacities)[::-1]] + + if len(free_segments_indices) == 0: + break + + fitting_segment = np.argmax((free_capacities >= segment_lengths[segment_idx])[::-1]) + fitting_segment = free_segments_indices[fitting_segment] + if self._segment_capacity[fitting_segment] >= segment_lengths[segment_idx]: + free_segments[fitting_segment] = False + to_amend[segment_idx] = False + to_insert[segment_idx] = fitting_segment + + free_points_at_end = self.total_capacity - np.sum(self._segment_capacity[:first_free]) + if np.sum(segment_lengths[to_amend] + 16) > free_points_at_end: + raise MemoryError("Fragmentation does not allow upload.", + np.sum(segment_lengths[to_amend] + 16), + free_points_at_end, + self._free_points_at_end) + + return waveform_to_segment, to_amend, to_insert + + @with_select + @with_configuration_guard + def _upload_segment(self, segment_index: int, segment: TaborSegment) -> None: + if self._segment_references[segment_index] > 0: + raise ValueError("Reference count not zero") + if segment.num_points > self._segment_capacity[segment_index]: + raise ValueError("Cannot upload segment here.") + + segment_no = segment_index + 1 + + self.device[SCPI].send_cmd(":TRAC:DEF {}, {}".format(segment_no, segment.num_points), + paranoia_level=self.internal_paranoia_level) + self._segment_lengths[segment_index] = segment.num_points + + self.device[SCPI].send_cmd(":TRAC:SEL {}".format(segment_no), paranoia_level=self.internal_paranoia_level) + + self.device[SCPI].send_cmd(":TRAC:MODE COMB", paranoia_level=self.internal_paranoia_level) + wf_data = segment.get_as_binary() + + self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) + self._segment_references[segment_index] = 1 + self._segment_hashes[segment_index] = hash(segment) + + @with_select + @with_configuration_guard + def _amend_segments(self, segments: List[TaborSegment]) -> np.ndarray: + new_lengths = np.asarray([s.num_points for s in segments], dtype=np.uint32) + + wf_data = make_combined_wave(segments) + trac_len = len(wf_data) // 2 + + segment_index = len(self._segment_capacity) + first_segment_number = segment_index + 1 + + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(first_segment_number, trac_len), + paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:SEL {}".format(first_segment_number), + paranoia_level=self.internal_paranoia_level) + self.device[SCPI].send_cmd(":TRAC:MODE COMB", + paranoia_level=self.internal_paranoia_level) + self.device._send_binary_data(pref=":TRAC:DATA", bin_dat=wf_data) + + old_to_update = np.count_nonzero(self._segment_capacity != self._segment_lengths) + segment_capacity = np.concatenate((self._segment_capacity, new_lengths)) + segment_lengths = np.concatenate((self._segment_lengths, new_lengths)) + segment_references = np.concatenate((self._segment_references, np.ones(len(segments), dtype=int))) + segment_hashes = np.concatenate((self._segment_hashes, [hash(s) for s in segments])) + if len(segments) < old_to_update: + for i, segment in enumerate(segments): + current_segment_number = first_segment_number + i + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(current_segment_number, segment.num_points), + paranoia_level=self.internal_paranoia_level) + else: + # flush the capacity + self.device._download_segment_lengths(segment_capacity) + + # update non fitting lengths + for i in np.flatnonzero(segment_capacity != segment_lengths): + self.device[SCPI].send_cmd(":TRAC:DEF {},{}".format(i + 1, segment_lengths[i])) + + self._segment_capacity = segment_capacity + self._segment_lengths = segment_lengths + self._segment_hashes = segment_hashes + self._segment_references = segment_references + + return segment_index + np.arange(len(segments), dtype=np.int64) + + @with_select + @with_configuration_guard + def cleanup(self) -> None: + """Discard all segments after the last which is still referenced""" + reserved_indices = np.flatnonzero(self._segment_references > 0) + old_end = len(self._segment_lengths) + new_end = reserved_indices[-1] + 1 if len(reserved_indices) else 0 + self._segment_lengths = self._segment_lengths[:new_end] + self._segment_capacity = self._segment_capacity[:new_end] + self._segment_hashes = self._segment_hashes[:new_end] + self._segment_references = self._segment_references[:new_end] + + try: + # send max 10 commands at once + chunk_size = 10 + for chunk_start in range(new_end, old_end, chunk_size): + self.device[SCPI].send_cmd("; ".join("TRAC:DEL {}".format(i + 1) + for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) + except Exception as e: + raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e + + @with_configuration_guard + def _execute_multiple_commands_with_config_guard(self, commands: List[str]) -> None: + """ Joins the given commands into one and executes it with configuration guard. + + Args: + commands: Commands that should be executed. + """ + cmd_str = ";".join(commands) + self.device[SCPI].send_cmd(cmd_str, paranoia_level=self.internal_paranoia_level) + + def set_program_advanced_sequence_table(self, name, new_advanced_sequence_table): + self._known_programs[name][1]._advanced_sequencer_table = new_advanced_sequence_table + + def set_program_sequence_table(self, name, new_sequence_table): + self._known_programs[name][1]._sequencer_tables = new_sequence_table + + def _enter_config_mode(self) -> None: + """ + Enter the configuration mode if not already in. All outputs are set to the DC offset of the device and the + sequencing is disabled. The manual states this speeds up sequence validation when uploading multiple sequences. + When entering and leaving the configuration mode the AWG outputs a small (~60 mV in 4 V mode) blip. + """ + if self._is_in_config_mode is False: + + # 1. Selct channel pair + # 2. Select DC as function shape + # 3. Select build-in waveform mode + + if self.device._is_coupled(): + out_cmd = ":OUTP:ALL OFF" + else: + out_cmd = "" + for channel in self.channels: + out_cmd = out_cmd + ":INST:SEL {ch_id}; :OUTP OFF;".format(ch_id=channel.idn) + + marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" + # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature + # for marker_ch in self.marker_channels: + # marker_ch[TaborMarkerChannelActivatable].disable() + + wf_mode_cmd = ":SOUR:FUNC:MODE FIX" + + cmd = ";".join([marker_0_cmd, marker_1_cmd, wf_mode_cmd]) + cmd = out_cmd + cmd + self.device[SCPI].send_cmd(cmd, paranoia_level=self.CONFIG_MODE_PARANOIA_LEVEL) + self._is_in_config_mode = True + + @with_select + def _exit_config_mode(self) -> None: + """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" + + # TODO: change implementation for channel synchronisation feature + + if self.device._is_coupled(): + # Coupled -> switch all channels at once + other_channel_tuple: TaborChannelTuple + if self.channels == self.device.channel_tuples[0].channels: + other_channel_tuple = self.device.channel_tuples[1] + else: + other_channel_tuple = self.device.channel_tuples[0] + + if not other_channel_tuple._is_in_config_mode: + self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device[SCPI].send_cmd(":SEQ:SEL 1") + self.device[SCPI].send_cmd(":OUTP:ALL ON") + + else: + self.device[SCPI].send_cmd(":SOUR:FUNC:MODE ASEQ") + self.device[SCPI].send_cmd(":SEQ:SEL 1") + + for channel in self.channels: + channel[ActivatableChannels].enable() + + for marker_ch in self.marker_channels: + marker_ch[ActivatableChannels].enable() + + self._is_in_config_mode = False + + +######################################################################################################################## +# Marker Channel +######################################################################################################################## +# Features + +class TaborActivatableMarkerChannels(ActivatableChannels): + def __init__(self, marker_channel: "TaborMarkerChannel"): + super().__init__() + self._parent = weakref.ref(marker_channel) + + @property + def enabled(self) -> bool: + pass # TODO: to implement + + @with_select + def enable(self): + command_string = "SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" + command_string = command_string.format( + channel=self._parent().channel_tuple.channels[0].idn, + marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) + self._parent().device[SCPI].send_cmd(command_string) + + @with_select + def disable(self): + command_string = ":SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" + command_string = command_string.format( + channel=self._parent().channel_tuple.channels[0].idn, + marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) + self._parent().device[SCPI].send_cmd(command_string) + + def _select(self) -> None: + self._parent()._select() + + +# Implementation +class TaborMarkerChannel(AWGMarkerChannel): + def __init__(self, idn: int, device: TaborDevice): + super().__init__(idn) + self._device = weakref.ref(device) + + # adding Features + self.add_feature(TaborActivatableMarkerChannels(self)) + + @property + def device(self) -> TaborDevice: + """Returns the device that this marker channel belongs to""" + return self._device() + + @property + def channel_tuple(self) -> TaborChannelTuple: + """Returns the channel tuple that this marker channel belongs to""" + return self._channel_tuple() + + def _set_channel_tuple(self, channel_tuple: TaborChannelTuple) -> None: + """ + The channel tuple 'channel_tuple' is assigned to this marker channel + + Args: + channel_tuple (TaborChannelTuple): the channel tuple that this marker channel belongs to + """ + self._channel_tuple = weakref.ref(channel_tuple) + + def _select(self) -> None: + """ + This marker channel is selected and is now the active channel marker of the device + """ + self.device.channels[int((self.idn - 1) / 2)]._select() + self.device[SCPI].send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) + + +######################################################################################################################## +class TaborException(Exception): + pass + + +class TaborUndefinedState(TaborException): + """ + If this exception is raised the attached tabor device is in an undefined state. + It is highly recommended to call reset it. + """ + + def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): + super().__init__(*args) + self.device = device + + def reset_device(self): + if isinstance(self.device, TaborDevice): + self.device.reset() + elif isinstance(self.device, TaborChannelTuple): + self.device.cleanup() + self.device.clear() diff --git a/qupulse/hardware/setup.py b/qupulse/hardware/setup.py index a45a5d849..5ce09d35e 100644 --- a/qupulse/hardware/setup.py +++ b/qupulse/hardware/setup.py @@ -3,7 +3,7 @@ import warnings import numbers -from qupulse.hardware.awgs.old_base import AWG +from qupulse.hardware.awgs.base import AWG from qupulse.hardware.dacs import DAC from qupulse._program._loop import Loop diff --git a/tests/backward_compatibility/tabor_backward_compatibility_tests.py b/tests/backward_compatibility/tabor_backward_compatibility_tests.py index 18f43e5fc..631d697c8 100644 --- a/tests/backward_compatibility/tabor_backward_compatibility_tests.py +++ b/tests/backward_compatibility/tabor_backward_compatibility_tests.py @@ -11,7 +11,7 @@ from qupulse.serialization import Serializer, FilesystemBackend, PulseStorage from qupulse.pulses.pulse_template import PulseTemplate from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel, MeasurementMask -from qupulse.hardware.awgs.old_tabor import PlottableProgram +from qupulse.hardware.awgs.tabor import PlottableProgram diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_base_tests.py index 6e30e8ad3..09047ca9a 100644 --- a/tests/hardware/awg_base_tests.py +++ b/tests/hardware/awg_base_tests.py @@ -1,11 +1,11 @@ -from typing import Callable, Iterable, List, Optional, Set, Tuple +from typing import Callable, Iterable, Optional, Set, Tuple import unittest import warnings from qupulse import ChannelID from qupulse._program._loop import Loop -from qupulse.hardware.awgs.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel -from qupulse.hardware.awgs.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ +from qupulse.hardware.awgs_new_driver.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel +from qupulse.hardware.awgs_new_driver.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ AmplitudeOffsetHandling from qupulse.utils.types import Collection diff --git a/tests/hardware/dummy_devices.py b/tests/hardware/dummy_devices.py index a90b2bd4f..a92ce1282 100644 --- a/tests/hardware/dummy_devices.py +++ b/tests/hardware/dummy_devices.py @@ -2,7 +2,7 @@ from collections import deque -from qupulse.hardware.awgs.old_base import AWG, ProgramOverwriteException +from qupulse.hardware.awgs.base import AWG, ProgramOverwriteException from qupulse.hardware.dacs import DAC class DummyDAC(DAC): diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/old_tabor_simulator_based_tests.py index 8143442c5..124a2914d 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/old_tabor_simulator_based_tests.py @@ -7,7 +7,7 @@ import pytabor import numpy as np -from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation, TaborChannelPair +from qupulse.hardware.awgs.tabor import TaborAWGRepresentation, TaborChannelPair from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry from typing import List, Tuple, Optional, Any diff --git a/tests/hardware/tabor_clock_tests.py b/tests/hardware/tabor_clock_tests.py index 25270284b..98ec19248 100644 --- a/tests/hardware/tabor_clock_tests.py +++ b/tests/hardware/tabor_clock_tests.py @@ -10,7 +10,7 @@ def test_the_thing(self): def get_pulse(): - from qupulse.pulses import TablePulseTemplate as TPT, SequencePulseTemplate as SPT, \ + from qupulse.pulses import SequencePulseTemplate as SPT, \ RepetitionPulseTemplate as RPT, FunctionPulseTemplate as FPT, MultiChannelPulseTemplate as MPT sine = FPT('U*sin(2*pi*t/tau)', 'tau', channel='out') @@ -67,7 +67,7 @@ def get_alazar_config(): def get_operations(): - from atsaverage.operations import Downsample, RepAverage + from atsaverage.operations import RepAverage return [RepAverage(identifier='REP_A', maskID='A')] @@ -90,7 +90,7 @@ def tic(name): t.append(time.time()) names.append(name) - from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborAWGRepresentation + from qupulse.hardware.awgs_new_driver.tabor import TaborChannelPair, TaborAWGRepresentation tawg = TaborAWGRepresentation(r'USB0::0x168C::0x2184::0000216488::INSTR', reset=True) tchannelpair = TaborChannelPair(tawg, (1, 2), 'TABOR_AB') diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py index dbc4d9793..e38411011 100644 --- a/tests/hardware/tabor_dummy_based_tests.py +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -1,7 +1,7 @@ import sys import unittest from unittest import mock -from unittest.mock import patch, MagicMock +from unittest.mock import patch from typing import List, Tuple, Optional, Any from copy import copy, deepcopy @@ -9,8 +9,8 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs.old_base import AWGAmplitudeOffsetHandling -from qupulse.hardware.awgs.tabor import TaborProgram, TaborDevice, TaborProgramMemory +from qupulse.hardware.awgs.base import AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs_new_driver.tabor import TaborProgram, TaborProgramMemory from qupulse.utils.types import TimeType from tests.hardware.dummy_modules import import_package @@ -97,7 +97,7 @@ def tearDownClass(cls): cls.restore_packages() def setUp(self): - from qupulse.hardware.awgs.old_tabor import TaborAWGRepresentation + from qupulse.hardware.awgs.tabor import TaborAWGRepresentation self.instrument = TaborAWGRepresentation('main_instrument', reset=True, paranoia_level=2, @@ -176,7 +176,7 @@ def to_new_advanced_sequencer_table(advanced_sequencer_table: List[Tuple[int, in def setUpClass(cls): super().setUpClass() - from qupulse.hardware.awgs.old_tabor import TaborChannelPair, TaborProgramMemory, TaborSegment, TaborSequencing + from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborProgramMemory, TaborSegment, TaborSequencing from qupulse.pulses.table_pulse_template import TableWaveform from qupulse.pulses.interpolation import HoldInterpolationStrategy from qupulse._program._loop import Loop diff --git a/tests/hardware/tabor_exex_test.py b/tests/hardware/tabor_exex_test.py index b3d6fc2dd..acc496c02 100644 --- a/tests/hardware/tabor_exex_test.py +++ b/tests/hardware/tabor_exex_test.py @@ -72,7 +72,7 @@ def get_window(card): class TaborTests(unittest.TestCase): @unittest.skip def test_all(self): - from qupulse.hardware.awgs.old_tabor import TaborChannelTuple, TaborDevice + from qupulse.hardware.awgs.tabor import TaborChannelTuple, TaborDevice #import warnings tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index e85f6d717..79f14f240 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -9,9 +9,8 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters -from qupulse.hardware.awgs.tabor import TaborDevice, TaborException, TaborSegment, TaborChannelTuple, \ - TaborVoltageRange, TaborDeviceControl, TaborProgramManagement +from qupulse.hardware.awgs_new_driver.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters +from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborSegment class TaborSimulatorManager: diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_tests.py index d960a2abb..b68943e2e 100644 --- a/tests/hardware/tabor_tests.py +++ b/tests/hardware/tabor_tests.py @@ -1,19 +1,6 @@ import unittest -import itertools -import numpy as np - -from teawg import model_properties_dict - -from qupulse.hardware.awgs.tabor import TaborException, TaborProgram, \ - TaborSegment, TaborSequencing, with_configuration_guard, PlottableProgram -from qupulse._program._loop import Loop -from qupulse.hardware.util import voltage_to_uint16 - -from tests.pulses.sequencing_dummies import DummyWaveform -from tests._program.loop_tests import LoopTests, WaveformGenerator - - +from qupulse.hardware.awgs_new_driver.tabor import with_configuration_guard class ConfigurationGuardTest(unittest.TestCase): From c21b4b06da0df37a58af7ee2d19cfebeb11ee481 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 17 Dec 2020 16:06:25 +0100 Subject: [PATCH 084/107] added an __init__.py for awgs_new_driver --- qupulse/hardware/awgs_new_driver/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qupulse/hardware/awgs_new_driver/__init__.py diff --git a/qupulse/hardware/awgs_new_driver/__init__.py b/qupulse/hardware/awgs_new_driver/__init__.py new file mode 100644 index 000000000..e69de29bb From bbfd7c53871176d320c542da6d98845fe2f544db Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 14 Jan 2021 11:02:38 +0100 Subject: [PATCH 085/107] renamed all the tests to tabor_new_... so they don't overwrite the test for the old tabor in the rebase --- qupulse/hardware/awgs_new_driver/tabor.py | 1 - .../tabor_backward_compatibility_tests.py | 2 +- ..._tests.py => awg_new_driver_base_tests.py} | 0 ...sts.py => tabor_new_driver_clock_tests.py} | 0 ... => tabor_new_driver_dummy_based_tests.py} | 0 ..._test.py => tabor_new_driver_exex_test.py} | 2 +- ...tabor_new_driver_simulator_based_tests.py} | 97 +++++++++++-------- ...bor_tests.py => tabor_new_driver_tests.py} | 0 tests/hardware/tabor_simulator_based_tests.py | 97 ++++++++----------- 9 files changed, 99 insertions(+), 100 deletions(-) rename tests/hardware/{awg_base_tests.py => awg_new_driver_base_tests.py} (100%) rename tests/hardware/{tabor_clock_tests.py => tabor_new_driver_clock_tests.py} (100%) rename tests/hardware/{tabor_dummy_based_tests.py => tabor_new_driver_dummy_based_tests.py} (100%) rename tests/hardware/{tabor_exex_test.py => tabor_new_driver_exex_test.py} (98%) rename tests/hardware/{old_tabor_simulator_based_tests.py => tabor_new_driver_simulator_based_tests.py} (73%) rename tests/hardware/{tabor_tests.py => tabor_new_driver_tests.py} (100%) diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py index 91d3c7a62..070a34822 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -77,7 +77,6 @@ def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedRes self._parent = weakref.ref(device) def send_cmd(self, cmd_str, paranoia_level=None): - print(cmd_str) for instr in self._parent().all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) diff --git a/tests/backward_compatibility/tabor_backward_compatibility_tests.py b/tests/backward_compatibility/tabor_backward_compatibility_tests.py index 631d697c8..97d567232 100644 --- a/tests/backward_compatibility/tabor_backward_compatibility_tests.py +++ b/tests/backward_compatibility/tabor_backward_compatibility_tests.py @@ -5,7 +5,7 @@ import importlib.util import sys -from tests.hardware.old_tabor_simulator_based_tests import TaborSimulatorManager +from tests.hardware.tabor_simulator_based_tests import TaborSimulatorManager from tests.hardware.dummy_devices import DummyDAC from qupulse.serialization import Serializer, FilesystemBackend, PulseStorage diff --git a/tests/hardware/awg_base_tests.py b/tests/hardware/awg_new_driver_base_tests.py similarity index 100% rename from tests/hardware/awg_base_tests.py rename to tests/hardware/awg_new_driver_base_tests.py diff --git a/tests/hardware/tabor_clock_tests.py b/tests/hardware/tabor_new_driver_clock_tests.py similarity index 100% rename from tests/hardware/tabor_clock_tests.py rename to tests/hardware/tabor_new_driver_clock_tests.py diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_new_driver_dummy_based_tests.py similarity index 100% rename from tests/hardware/tabor_dummy_based_tests.py rename to tests/hardware/tabor_new_driver_dummy_based_tests.py diff --git a/tests/hardware/tabor_exex_test.py b/tests/hardware/tabor_new_driver_exex_test.py similarity index 98% rename from tests/hardware/tabor_exex_test.py rename to tests/hardware/tabor_new_driver_exex_test.py index acc496c02..50da9ad82 100644 --- a/tests/hardware/tabor_exex_test.py +++ b/tests/hardware/tabor_new_driver_exex_test.py @@ -72,7 +72,7 @@ def get_window(card): class TaborTests(unittest.TestCase): @unittest.skip def test_all(self): - from qupulse.hardware.awgs.tabor import TaborChannelTuple, TaborDevice + from qupulse.hardware.awgs_new_driver.tabor import TaborChannelTuple, TaborDevice #import warnings tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') diff --git a/tests/hardware/old_tabor_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py similarity index 73% rename from tests/hardware/old_tabor_simulator_based_tests.py rename to tests/hardware/tabor_new_driver_simulator_based_tests.py index 124a2914d..79f14f240 100644 --- a/tests/hardware/old_tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -3,13 +3,15 @@ import time import platform import os +from typing import List, Tuple, Optional, Any import pytabor import numpy as np -from qupulse.hardware.awgs.tabor import TaborAWGRepresentation, TaborChannelPair -from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry -from typing import List, Tuple, Optional, Any +from qupulse._program.tabor import TableDescription, TableEntry +from qupulse.hardware.awgs_new_driver.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters +from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborSegment + class TaborSimulatorManager: def __init__(self, @@ -21,7 +23,7 @@ def __init__(self, self.started_simulator = False self.simulator_process = None - self.instrument = None + self.instrument: TaborDevice = None def kill_running_simulators(self): command = 'Taskkill', '/IM {simulator_executable}'.format(simulator_executable=self.simulator_executable) @@ -55,10 +57,11 @@ def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_ti raise RuntimeError('Could not connect to simulator') time.sleep(0.1) - def connect(self): - self.instrument = TaborAWGRepresentation('127.0.0.1', - reset=True, - paranoia_level=2) + def connect(self) -> TaborDevice: + self.instrument = TaborDevice("testDevice", + "127.0.0.1", + reset=True, + paranoia_level=2) if self.instrument.main_instrument.visa_inst is None: raise RuntimeError('Could not connect to simulator') @@ -79,7 +82,7 @@ class TaborSimulatorBasedTest(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.instrument = None + self.instrument: TaborDevice @classmethod def setUpClass(cls): @@ -97,7 +100,7 @@ def setUp(self): self.instrument = self.simulator_manager.connect() def tearDown(self): - self.instrument.reset() + self.instrument[DeviceControl].reset() self.simulator_manager.disconnect() @staticmethod @@ -116,60 +119,59 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_sample_rate(self): - for ch in (1, 2, 3, 4): - self.assertIsInstance(self.instrument.sample_rate(ch), int) + # for ch in (1, 2, 3, 4): + # self.assertIsInstance(self.instrument.sample_rate(ch), int) + # for ch_tuple in self.instrument.channel_tuples: + # self.assertIsInstance(ch_tuple.sample_rate,int) - with self.assertRaises(TaborException): - self.instrument.sample_rate(0) + # with self.assertRaises(TaborException): + # self.instrument.sample_rate(0) self.instrument.send_cmd(':INST:SEL 1') self.instrument.send_cmd(':FREQ:RAST 2.3e9') - self.assertEqual(2300000000, self.instrument.sample_rate(1)) + # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) def test_amplitude(self): - for ch in (1, 2, 3, 4): - self.assertIsInstance(self.instrument.amplitude(ch), float) - - with self.assertRaises(TaborException): - self.instrument.amplitude(0) + for channel in self.instrument.channels: + self.assertIsInstance(channel[VoltageRange].amplitude, float) self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') self.instrument.send_cmd(':VOLT 0.7') - self.assertAlmostEqual(.7, self.instrument.amplitude(1)) + self.assertAlmostEqual(.7, self.instrument.channels[0][VoltageRange].amplitude) def test_select_marker(self): - with self.assertRaises(TaborException): - self.instrument.select_marker(6) + with self.assertRaises(IndexError): + self.instrument.marker_channels[6]._select() - self.instrument.select_marker(2) - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.instrument.marker_channels[1]._select() + selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') - self.instrument.select_marker(1) - selected = self.instrument.send_query(':SOUR:MARK:SEL?') + self.instrument.marker_channels[0]._select() + selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): - with self.assertRaises(TaborException): - self.instrument.select_channel(6) + with self.assertRaises(IndexError): + self.instrument.channels[6]._select() - self.instrument.select_channel(1) - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') + self.instrument.channels[0]._select() + self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '1') - self.instrument.select_channel(4) - self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') + self.instrument.channels[3]._select() + self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): def setUp(self): super().setUp() - ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) + ramp_up = np.linspace(0, 2 ** 14 - 1, num=192, dtype=np.uint16) ramp_down = ramp_up[::-1] - zero = np.ones(192, dtype=np.uint16) * 2**13 - sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) + zero = np.ones(192, dtype=np.uint16) * 2 ** 13 + sine = ((np.sin(np.linspace(0, 2 * np.pi, 192 + 64)) + 1) / 2 * (2 ** 14 - 1)).astype(np.uint16) self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), TaborSegment.from_sampled(ramp_down, zero, None, None), @@ -185,7 +187,11 @@ def setUp(self): self.sequence_tables = self.to_new_sequencer_tables(self.sequence_tables_raw) self.advanced_sequence_table = self.to_new_advanced_sequencer_table(self.advanced_sequence_table) - self.channel_pair = TaborChannelPair(self.instrument, (1, 2), 'tabor_unit_test') + # TODO: darf man das so ersetzen + # self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') + + self.channel_pair = self.instrument.channel_tuples[0] + def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: @@ -209,11 +215,12 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair.change_armed_program('dummy_program') + self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) + #waveforms sind schon nicht gleich zum alten Treiber waveforms = self.channel_pair.read_waveforms() segments = [TaborSegment.from_binary_segment(waveform) @@ -235,7 +242,7 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) + actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] @@ -249,12 +256,15 @@ def test_read_advanced_sequencer_table(self): self.channel_pair._amend_segments(self.segments) self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) - actual_advanced_table = [(1, 1, 1)] + [(rep, idx+1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + #TODO: test here + #actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) advanced_table = self.channel_pair.read_advanced_sequencer_table() + np.testing.assert_equal(advanced_table, expected) def test_set_volatile_parameter(self): @@ -262,13 +272,14 @@ def test_set_volatile_parameter(self): self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) para = {'a': 5} - actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index + 2, jump) + actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] - actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] - self.channel_pair.set_volatile_parameters('dummy_program', parameters=para) + actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + + self.channel_pair[VolatileParameters].set_volatile_parameters('dummy_program', parameters=para) actual_sequence_tables[1][1] = (50, 3, 0) actual_advanced_table[2] = (5, 3, 0) diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_new_driver_tests.py similarity index 100% rename from tests/hardware/tabor_tests.py rename to tests/hardware/tabor_new_driver_tests.py diff --git a/tests/hardware/tabor_simulator_based_tests.py b/tests/hardware/tabor_simulator_based_tests.py index 79f14f240..124a2914d 100644 --- a/tests/hardware/tabor_simulator_based_tests.py +++ b/tests/hardware/tabor_simulator_based_tests.py @@ -3,15 +3,13 @@ import time import platform import os -from typing import List, Tuple, Optional, Any import pytabor import numpy as np -from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs_new_driver.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters -from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborSegment - +from qupulse.hardware.awgs.tabor import TaborAWGRepresentation, TaborChannelPair +from qupulse._program.tabor import TaborSegment, PlottableProgram, TaborException, TableDescription, TableEntry +from typing import List, Tuple, Optional, Any class TaborSimulatorManager: def __init__(self, @@ -23,7 +21,7 @@ def __init__(self, self.started_simulator = False self.simulator_process = None - self.instrument: TaborDevice = None + self.instrument = None def kill_running_simulators(self): command = 'Taskkill', '/IM {simulator_executable}'.format(simulator_executable=self.simulator_executable) @@ -57,11 +55,10 @@ def start_simulator(self, try_connecting_to_existing_simulator=True, max_wait_ti raise RuntimeError('Could not connect to simulator') time.sleep(0.1) - def connect(self) -> TaborDevice: - self.instrument = TaborDevice("testDevice", - "127.0.0.1", - reset=True, - paranoia_level=2) + def connect(self): + self.instrument = TaborAWGRepresentation('127.0.0.1', + reset=True, + paranoia_level=2) if self.instrument.main_instrument.visa_inst is None: raise RuntimeError('Could not connect to simulator') @@ -82,7 +79,7 @@ class TaborSimulatorBasedTest(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.instrument: TaborDevice + self.instrument = None @classmethod def setUpClass(cls): @@ -100,7 +97,7 @@ def setUp(self): self.instrument = self.simulator_manager.connect() def tearDown(self): - self.instrument[DeviceControl].reset() + self.instrument.reset() self.simulator_manager.disconnect() @staticmethod @@ -119,59 +116,60 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_sample_rate(self): - # for ch in (1, 2, 3, 4): - # self.assertIsInstance(self.instrument.sample_rate(ch), int) - # for ch_tuple in self.instrument.channel_tuples: - # self.assertIsInstance(ch_tuple.sample_rate,int) + for ch in (1, 2, 3, 4): + self.assertIsInstance(self.instrument.sample_rate(ch), int) - # with self.assertRaises(TaborException): - # self.instrument.sample_rate(0) + with self.assertRaises(TaborException): + self.instrument.sample_rate(0) self.instrument.send_cmd(':INST:SEL 1') self.instrument.send_cmd(':FREQ:RAST 2.3e9') - # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) + self.assertEqual(2300000000, self.instrument.sample_rate(1)) def test_amplitude(self): - for channel in self.instrument.channels: - self.assertIsInstance(channel[VoltageRange].amplitude, float) + for ch in (1, 2, 3, 4): + self.assertIsInstance(self.instrument.amplitude(ch), float) + + with self.assertRaises(TaborException): + self.instrument.amplitude(0) self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') self.instrument.send_cmd(':VOLT 0.7') - self.assertAlmostEqual(.7, self.instrument.channels[0][VoltageRange].amplitude) + self.assertAlmostEqual(.7, self.instrument.amplitude(1)) def test_select_marker(self): - with self.assertRaises(IndexError): - self.instrument.marker_channels[6]._select() + with self.assertRaises(TaborException): + self.instrument.select_marker(6) - self.instrument.marker_channels[1]._select() - selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') + self.instrument.select_marker(2) + selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '2') - self.instrument.marker_channels[0]._select() - selected = self.instrument[SCPI].send_query(':SOUR:MARK:SEL?') + self.instrument.select_marker(1) + selected = self.instrument.send_query(':SOUR:MARK:SEL?') self.assertEqual(selected, '1') def test_select_channel(self): - with self.assertRaises(IndexError): - self.instrument.channels[6]._select() + with self.assertRaises(TaborException): + self.instrument.select_channel(6) - self.instrument.channels[0]._select() - self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '1') + self.instrument.select_channel(1) + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '1') - self.instrument.channels[3]._select() - self.assertEqual(self.instrument[SCPI].send_query(':INST:SEL?'), '4') + self.instrument.select_channel(4) + self.assertEqual(self.instrument.send_query(':INST:SEL?'), '4') class TaborMemoryReadTests(TaborSimulatorBasedTest): def setUp(self): super().setUp() - ramp_up = np.linspace(0, 2 ** 14 - 1, num=192, dtype=np.uint16) + ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) ramp_down = ramp_up[::-1] - zero = np.ones(192, dtype=np.uint16) * 2 ** 13 - sine = ((np.sin(np.linspace(0, 2 * np.pi, 192 + 64)) + 1) / 2 * (2 ** 14 - 1)).astype(np.uint16) + zero = np.ones(192, dtype=np.uint16) * 2**13 + sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), TaborSegment.from_sampled(ramp_down, zero, None, None), @@ -187,11 +185,7 @@ def setUp(self): self.sequence_tables = self.to_new_sequencer_tables(self.sequence_tables_raw) self.advanced_sequence_table = self.to_new_advanced_sequencer_table(self.advanced_sequence_table) - # TODO: darf man das so ersetzen - # self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') - - self.channel_pair = self.instrument.channel_tuples[0] - + self.channel_pair = TaborChannelPair(self.instrument, (1, 2), 'tabor_unit_test') def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: @@ -215,12 +209,11 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet + self.channel_pair.change_armed_program('dummy_program') def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) - #waveforms sind schon nicht gleich zum alten Treiber waveforms = self.channel_pair.read_waveforms() segments = [TaborSegment.from_binary_segment(waveform) @@ -242,7 +235,7 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) + actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index+2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] @@ -256,15 +249,12 @@ def test_read_advanced_sequencer_table(self): self.channel_pair._amend_segments(self.segments) self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) - #TODO: test here - #actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] - actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] + actual_advanced_table = [(1, 1, 1)] + [(rep, idx+1, jmp) for rep, idx, jmp in self.advanced_sequence_table] expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) advanced_table = self.channel_pair.read_advanced_sequencer_table() - np.testing.assert_equal(advanced_table, expected) def test_set_volatile_parameter(self): @@ -272,14 +262,13 @@ def test_set_volatile_parameter(self): self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) para = {'a': 5} - actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) + actual_sequence_tables = [self.channel_pair._idle_sequence_table] + [[(rep, index + 2, jump) for rep, index, jump in table] for table in self.sequence_tables_raw] + actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] - actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] - - self.channel_pair[VolatileParameters].set_volatile_parameters('dummy_program', parameters=para) + self.channel_pair.set_volatile_parameters('dummy_program', parameters=para) actual_sequence_tables[1][1] = (50, 3, 0) actual_advanced_table[2] = (5, 3, 0) From 37a49acd02d42a0e8f8470554a9520d48bfe79d2 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 14 Jan 2021 11:23:26 +0100 Subject: [PATCH 086/107] fixed the tabor drivers because the read_waveform method isn't subscribable --- qupulse/hardware/awgs_new_driver/tabor.py | 12 ++++++------ .../tabor_new_driver_simulator_based_tests.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py index 070a34822..ecfd061e1 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -1012,28 +1012,28 @@ def _free_points_at_end(self) -> int: def read_waveforms(self) -> List[np.ndarray]: device = self.device._get_readable_device(simulator=True) - old_segment = device[SCPI].send_query(":TRAC:SEL?") + old_segment = device.send_query(":TRAC:SEL?") waveforms = [] uploaded_waveform_indices = np.flatnonzero( self._segment_references) + 1 for segment in uploaded_waveform_indices: - device[SCPI].send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":TRAC:SEL {}".format(segment), paranoia_level=self.internal_paranoia_level) waveforms.append(device.read_act_seg_dat()) - device[SCPI].send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":TRAC:SEL {}".format(old_segment), paranoia_level=self.internal_paranoia_level) return waveforms @with_select def read_sequence_tables(self) -> List[Tuple[np.ndarray, np.ndarray, np.ndarray]]: device = self.device._get_readable_device(simulator=True) - old_sequence = device[SCPI].send_query(":SEQ:SEL?") + old_sequence = device.send_query(":SEQ:SEL?") sequences = [] uploaded_sequence_indices = np.arange(len(self._sequencer_tables)) + 1 for sequence in uploaded_sequence_indices: - device[SCPI].send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":SEQ:SEL {}".format(sequence), paranoia_level=self.internal_paranoia_level) sequences.append(device.read_sequencer_table()) - device[SCPI].send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) + device.send_cmd(":SEQ:SEL {}".format(old_sequence), paranoia_level=self.internal_paranoia_level) return sequences @with_select diff --git a/tests/hardware/tabor_new_driver_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py index 79f14f240..0dc0f571d 100644 --- a/tests/hardware/tabor_new_driver_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -127,8 +127,8 @@ def test_sample_rate(self): # with self.assertRaises(TaborException): # self.instrument.sample_rate(0) - self.instrument.send_cmd(':INST:SEL 1') - self.instrument.send_cmd(':FREQ:RAST 2.3e9') + self.instrument[SCPI].send_cmd(':INST:SEL 1') + self.instrument[SCPI].send_cmd(':FREQ:RAST 2.3e9') # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) @@ -136,8 +136,8 @@ def test_amplitude(self): for channel in self.instrument.channels: self.assertIsInstance(channel[VoltageRange].amplitude, float) - self.instrument.send_cmd(':INST:SEL 1; :OUTP:COUP DC') - self.instrument.send_cmd(':VOLT 0.7') + self.instrument[SCPI].send_cmd(':INST:SEL 1; :OUTP:COUP DC') + self.instrument[SCPI].send_cmd(':VOLT 0.7') self.assertAlmostEqual(.7, self.instrument.channels[0][VoltageRange].amplitude) From 1a1f041dcc5175cda7f6c9f943fc1c213214fd44 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 1 Feb 2021 14:43:58 +0100 Subject: [PATCH 087/107] added tests for the new_tabor_driver --- tests/hardware/tabor_clock_tests.py | 165 ++++ tests/hardware/tabor_dummy_based_tests.py | 808 ++++++++++++++++++ tests/hardware/tabor_exex_test.py | 136 +++ .../hardware/tabor_new_driver_clock_tests.py | 160 ++-- .../tabor_new_driver_dummy_based_tests.py | 4 +- .../tabor_new_driver_simulator_based_tests.py | 31 +- .../tabor_simulator_based_test_debug.py | 42 + tests/hardware/tabor_tests.py | 52 ++ 8 files changed, 1279 insertions(+), 119 deletions(-) create mode 100644 tests/hardware/tabor_clock_tests.py create mode 100644 tests/hardware/tabor_dummy_based_tests.py create mode 100644 tests/hardware/tabor_exex_test.py create mode 100644 tests/hardware/tabor_simulator_based_test_debug.py create mode 100644 tests/hardware/tabor_tests.py diff --git a/tests/hardware/tabor_clock_tests.py b/tests/hardware/tabor_clock_tests.py new file mode 100644 index 000000000..25270284b --- /dev/null +++ b/tests/hardware/tabor_clock_tests.py @@ -0,0 +1,165 @@ +import unittest + + +class MyTest(unittest.TestCase): + @unittest.skip + def test_the_thing(self): + exec_test() + +with_alazar = True + + +def get_pulse(): + from qupulse.pulses import TablePulseTemplate as TPT, SequencePulseTemplate as SPT, \ + RepetitionPulseTemplate as RPT, FunctionPulseTemplate as FPT, MultiChannelPulseTemplate as MPT + + sine = FPT('U*sin(2*pi*t/tau)', 'tau', channel='out') + marker_on = FPT('1', 'tau', channel='trigger') + + multi = MPT([sine, marker_on], {'tau', 'U'}) + multi.atomicity = True + + assert sine.defined_channels == {'out'} + assert multi.defined_channels == {'out', 'trigger'} + + sine.add_measurement_declaration('meas', 0, 'tau') + + base = SPT([(multi, dict(tau='tau', U='U'), dict(meas='A')), + (multi, dict(tau='tau', U='U'), dict(meas='A')), + (multi, dict(tau='tau', U='U'), dict(meas='A'))], {'tau', 'U'}) + + repeated = RPT(base, 'n') + + root = SPT([repeated, repeated, repeated], {'tau', 'n', 'U'}) + + assert root.defined_channels == {'out', 'trigger'} + + return root + + +def get_alazar_config(): + from atsaverage import alazar + from atsaverage.config import ScanlineConfiguration, CaptureClockConfiguration, EngineTriggerConfiguration,\ + TRIGInputConfiguration, InputConfiguration + + trig_level = int((5 + 0.4) / 10. * 255) + assert 0 <= trig_level < 256 + + config = ScanlineConfiguration() + config.triggerInputConfiguration = TRIGInputConfiguration(triggerRange=alazar.TriggerRangeID.etr_5V) + config.triggerConfiguration = EngineTriggerConfiguration(triggerOperation=alazar.TriggerOperation.J, + triggerEngine1=alazar.TriggerEngine.J, + triggerSource1=alazar.TriggerSource.external, + triggerSlope1=alazar.TriggerSlope.positive, + triggerLevel1=trig_level, + triggerEngine2=alazar.TriggerEngine.K, + triggerSource2=alazar.TriggerSource.disable, + triggerSlope2=alazar.TriggerSlope.positive, + triggerLevel2=trig_level) + config.captureClockConfiguration = CaptureClockConfiguration(source=alazar.CaptureClockType.internal_clock, + samplerate=alazar.SampleRateID.rate_100MSPS) + config.inputConfiguration = 4*[InputConfiguration(input_range=alazar.InputRangeID.range_1_V)] + config.totalRecordSize = 0 + + assert config.totalRecordSize == 0 + + return config + + +def get_operations(): + from atsaverage.operations import Downsample, RepAverage + + return [RepAverage(identifier='REP_A', maskID='A')] + + +def get_window(card): + from atsaverage.gui import ThreadedStatusWindow + window = ThreadedStatusWindow(card) + window.start() + return window + + +def exec_test(): + import time + import numpy as np + + t = [] + names = [] + + def tic(name): + t.append(time.time()) + names.append(name) + + from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborAWGRepresentation + tawg = TaborAWGRepresentation(r'USB0::0x168C::0x2184::0000216488::INSTR', reset=True) + + tchannelpair = TaborChannelPair(tawg, (1, 2), 'TABOR_AB') + tawg.paranoia_level = 2 + + # warnings.simplefilter('error', Warning) + + from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel + hardware_setup = HardwareSetup() + + hardware_setup.set_channel('TABOR_A', PlaybackChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B', PlaybackChannel(tchannelpair, 1)) + hardware_setup.set_channel('TABOR_A_MARKER', MarkerChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B_MARKER', MarkerChannel(tchannelpair, 1)) + + if with_alazar: + from qupulse.hardware.dacs.alazar import AlazarCard + import atsaverage.server + + if not atsaverage.server.Server.default_instance.running: + atsaverage.server.Server.default_instance.start(key=b'guest') + + import atsaverage.core + + alazar = AlazarCard(atsaverage.core.getLocalCard(1, 1)) + alazar.register_mask_for_channel('A', 0) + alazar.config = get_alazar_config() + + alazar.register_operations('test', get_operations()) + + window = get_window(atsaverage.core.getLocalCard(1, 1)) + + hardware_setup.register_dac(alazar) + + repeated = get_pulse() + + from qupulse.pulses.sequencing import Sequencer + + tic('init') + sequencer = Sequencer() + sequencer.push(repeated, + parameters=dict(n=10000, tau=1920, U=0.5), + channel_mapping={'out': 'TABOR_A', 'trigger': 'TABOR_A_MARKER'}, + window_mapping=dict(A='A')) + instruction_block = sequencer.build() + + tic('sequence') + + hardware_setup.register_program('test', instruction_block) + + tic('register') + + if with_alazar: + from atsaverage.masks import PeriodicMask + m = PeriodicMask() + m.identifier = 'D' + m.begin = 0 + m.end = 1 + m.period = 1 + m.channel = 0 + alazar._registered_programs['test'].masks.append(m) + + tic('per_mask') + + hardware_setup.arm_program('test') + + tic('arm') + + for d, name in zip(np.diff(np.asarray(t)), names[1:]): + print(name, d) + + d = 1 diff --git a/tests/hardware/tabor_dummy_based_tests.py b/tests/hardware/tabor_dummy_based_tests.py new file mode 100644 index 000000000..554d489ac --- /dev/null +++ b/tests/hardware/tabor_dummy_based_tests.py @@ -0,0 +1,808 @@ +import sys +import unittest +from unittest import mock +from unittest.mock import patch, MagicMock + +from typing import List, Tuple, Optional, Any +from copy import copy, deepcopy + +import numpy as np + +from qupulse.hardware.awgs.base import AWGAmplitudeOffsetHandling +from qupulse.hardware.awgs.tabor import TaborProgram, TaborAWGRepresentation, TaborProgramMemory +from qupulse._program.tabor import TableDescription, TimeType, TableEntry +from tests.hardware.dummy_modules import import_package + + +class DummyTaborProgramClass: + def __init__(self, segments=None, segment_lengths=None, + sequencer_tables=None, advanced_sequencer_table=None, waveform_mode=None): + self.program = None + self.device_properties = None + self.channels = None + self.markers = None + + self.segment_lengths = segment_lengths + self.segments = segments + + self.sequencer_tables = sequencer_tables + self.advanced_sequencer_table = advanced_sequencer_table + self.waveform_mode = waveform_mode + + self.created = [] + + def __call__(self, program, device_properties, channels, markers): + self.program = program + self.device_properties = device_properties + self.channels = channels + self.markers = markers + + class DummyTaborProgram: + def __init__(self, class_obj: DummyTaborProgramClass): + self.sampled_segments_calls = [] + self.class_obj = class_obj + self.waveform_mode = class_obj.waveform_mode + def sampled_segments(self, sample_rate, voltage_amplitude, voltage_offset, voltage_transformation): + self.sampled_segments_calls.append((sample_rate, voltage_amplitude, voltage_offset, voltage_transformation)) + return self.class_obj.segments, self.class_obj.segment_lengths + def get_sequencer_tables(self): + return self.class_obj.sequencer_tables + def get_advanced_sequencer_table(self): + return self.class_obj.advanced_sequencer_table + self.created.append(DummyTaborProgram(self)) + return self.created[-1] + + +class TaborDummyBasedTest(unittest.TestCase): + to_unload = ['pytabor', 'pyvisa', 'visa', 'teawg', 'qupulse', 'tests.pulses.sequencing_dummies'] + backup_modules = dict() + + @classmethod + def unload_package(cls, package_name): + modules_to_delete = [module_name for module_name in sys.modules if module_name.startswith(package_name)] + + for module_name in modules_to_delete: + del sys.modules[module_name] + + @classmethod + def backup_package(cls, package_name): + cls.backup_modules[package_name] = [(module_name, module) + for module_name, module in sys.modules.items() + if module_name.startswith(package_name)] + + @classmethod + def restore_packages(cls): + for package, module_list in cls.backup_modules.items(): + for module_name, module in module_list: + sys.modules[module_name] = module + + @classmethod + def setUpClass(cls): + for u in cls.to_unload: + cls.backup_package(u) + + for u in cls.to_unload: + cls.unload_package(u) + + import_package('pytabor') + import_package('pyvisa') + import_package('teawg') + + @classmethod + def tearDownClass(cls): + for u in cls.to_unload: + cls.unload_package(u) + + cls.restore_packages() + + def setUp(self): + from qupulse.hardware.awgs.tabor import TaborAWGRepresentation + self.instrument = TaborAWGRepresentation('main_instrument', + reset=True, + paranoia_level=2, + mirror_addresses=['mirror_instrument']) + self.instrument.main_instrument.visa_inst.answers[':OUTP:COUP'] = 'DC' + self.instrument.main_instrument.visa_inst.answers[':VOLT'] = '1.0' + self.instrument.main_instrument.visa_inst.answers[':FREQ:RAST'] = '1e9' + self.instrument.main_instrument.visa_inst.answers[':VOLT:HV'] = '0.7' + + @property + def awg_representation(self): + return self.instrument + + @property + def channel_pair(self): + return self.awg_representation.channel_pair_AB + + def reset_instrument_logs(self): + for device in self.instrument.all_devices: + device.logged_commands = [] + device._send_binary_data_calls = [] + device._download_adv_seq_table_calls = [] + device._download_sequencer_table_calls = [] + + def assertAllCommandLogsEqual(self, expected_log: List): + for device in self.instrument.all_devices: + self.assertEqual(device.logged_commands, expected_log) + + +class TaborAWGRepresentationDummyBasedTests(TaborDummyBasedTest): + def test_send_cmd(self): + self.reset_instrument_logs() + + self.instrument.send_cmd('bleh', paranoia_level=3) + + self.assertAllCommandLogsEqual([((), dict(paranoia_level=3, cmd_str='bleh'))]) + + self.instrument.send_cmd('bleho') + self.assertAllCommandLogsEqual([((), dict(paranoia_level=3, cmd_str='bleh')), + ((), dict(cmd_str='bleho', paranoia_level=None))]) + + def test_trigger(self): + self.reset_instrument_logs() + self.instrument.trigger() + + self.assertAllCommandLogsEqual([((), dict(cmd_str=':TRIG', paranoia_level=None))]) + + def test_paranoia_level(self): + self.assertEqual(self.instrument.paranoia_level, self.instrument.main_instrument.paranoia_level) + self.instrument.paranoia_level = 30 + for device in self.instrument.all_devices: + self.assertEqual(device.paranoia_level, 30) + + def test_enable(self): + self.reset_instrument_logs() + self.instrument.enable() + + expected_commands = [':ENAB'] + expected_log = [((), dict(cmd_str=cmd, paranoia_level=None)) + for cmd in expected_commands] + self.assertAllCommandLogsEqual(expected_log) + + +class TaborChannelPairTests(TaborDummyBasedTest): + @staticmethod + def to_new_sequencer_tables(sequencer_tables: List[List[Tuple[int, int, int]]] + ) -> List[List[Tuple[TableDescription, Optional[Any]]]]: + return [[(TableDescription(*entry), None) for entry in sequencer_table] + for sequencer_table in sequencer_tables] + + @staticmethod + def to_new_advanced_sequencer_table(advanced_sequencer_table: List[Tuple[int, int, int]]) -> List[TableDescription]: + return [TableDescription(*entry) for entry in advanced_sequencer_table] + + @classmethod + def setUpClass(cls): + super().setUpClass() + + from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborProgramMemory, TaborSegment, TaborSequencing + from qupulse.pulses.table_pulse_template import TableWaveform + from qupulse.pulses.interpolation import HoldInterpolationStrategy + from qupulse._program._loop import Loop + + from tests.pulses.sequencing_dummies import DummyWaveform + + from qupulse._program.tabor import make_combined_wave + + cls.DummyWaveform = DummyWaveform + cls.TaborChannelPair = TaborChannelPair + cls.TaborProgramMemory = TaborProgramMemory + cls.TableWaveform = TableWaveform + cls.HoldInterpolationStrategy = HoldInterpolationStrategy + cls.Loop = Loop + cls.TaborSegment = TaborSegment + cls.make_combined_wave = staticmethod(make_combined_wave) + cls.TaborSequencing = TaborSequencing + + def setUp(self): + super().setUp() + + def test__execute_multiple_commands_with_config_guard(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + given_commands = [':ASEQ:DEF 2,2,5,0', ':SEQ:SEL 2', ':SEQ:DEF 1,2,10,0'] + expected_command = ':ASEQ:DEF 2,2,5,0;:SEQ:SEL 2;:SEQ:DEF 1,2,10,0' + with mock.patch.object(channel_pair.device, 'send_cmd') as send_cmd: + channel_pair._execute_multiple_commands_with_config_guard(given_commands) + send_cmd.assert_called_once_with(expected_command, paranoia_level=channel_pair.internal_paranoia_level) + + def test_set_volatile_parameters(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + parameters = {'var': 2} + modifications = {1: TableEntry(repetition_count=5, element_number=1, jump_flag=0), + (0, 1): TableDescription(repetition_count=10, element_id=0, jump_flag=0)} + invalid_modification = {1: TableEntry(repetition_count=0, element_number=1, jump_flag=0)} + no_modifications = {} + + program_mock = mock.Mock(TaborProgram) + program_memory = TaborProgramMemory(waveform_to_segment=np.array([1, 4]), program=program_mock) + + expected_commands = {':ASEQ:DEF 2,2,5,0', ':SEQ:SEL 2', ':SEQ:DEF 1,2,10,0'} + + channel_pair._known_programs['active_program'] = program_memory + channel_pair._known_programs['other_program'] = program_memory + channel_pair._current_program = 'active_program' + + with mock.patch.object(program_mock, 'update_volatile_parameters', return_value=modifications) as update_prog: + with mock.patch.object(channel_pair, '_execute_multiple_commands_with_config_guard') as ex_com: + with mock.patch.object(channel_pair.device.main_instrument._visa_inst, 'query'): + channel_pair.set_volatile_parameters('other_program', parameters) + ex_com.assert_not_called() + update_prog.assert_called_once_with(parameters) + + channel_pair.set_volatile_parameters('active_program', parameters) + self.assertEqual(1, ex_com.call_count) + actual_commands, = ex_com.call_args[0] + self.assertEqual(expected_commands, set(actual_commands)) + self.assertEqual(len(expected_commands), len(actual_commands)) + + assert update_prog.call_count == 2 + update_prog.assert_called_with(parameters) + + with mock.patch.object(program_mock, 'update_volatile_parameters', return_value=no_modifications) as update_prog: + with mock.patch.object(channel_pair, '_execute_multiple_commands_with_config_guard') as ex_com: + channel_pair.set_volatile_parameters('active_program', parameters) + + ex_com.assert_not_called() + update_prog.assert_called_once_with(parameters) + + with mock.patch.object(program_mock, 'update_volatile_parameters', return_value=invalid_modification) as update_prog: + with mock.patch.object(channel_pair, '_execute_multiple_commands_with_config_guard') as ex_com: + with self.assertRaises(ValueError): + channel_pair.set_volatile_parameters('active_program', parameters) + + ex_com.assert_not_called() + update_prog.assert_called_once_with(parameters) + + def test_copy(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + with self.assertRaises(NotImplementedError): + copy(channel_pair) + with self.assertRaises(NotImplementedError): + deepcopy(channel_pair) + + def test_init(self): + with self.assertRaises(ValueError): + self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 3)) + + def test_free_program(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + with self.assertRaises(KeyError): + channel_pair.free_program('test') + + program = self.TaborProgramMemory(np.array([1, 2], dtype=np.int64), None) + + channel_pair._segment_references = np.array([1, 3, 1, 0]) + channel_pair._known_programs['test'] = program + self.assertIs(channel_pair.free_program('test'), program) + + np.testing.assert_equal(channel_pair._segment_references, np.array([1, 2, 0, 0])) + + def test_upload_exceptions(self): + + wv = self.TableWaveform(1, [(0, 0.1, self.HoldInterpolationStrategy()), + (192, 0.1, self.HoldInterpolationStrategy())]) + + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + program = self.Loop(waveform=wv) + with self.assertRaises(ValueError): + channel_pair.upload('test', program, (1, 2, 3), (5, 6), (lambda x: x, lambda x: x)) + with self.assertRaises(ValueError): + channel_pair.upload('test', program, (1, 2), (5, 6, 'a'), (lambda x: x, lambda x: x)) + with self.assertRaises(ValueError): + channel_pair.upload('test', program, (1, 2), (3, 4), (lambda x: x,)) + + old = channel_pair._amplitude_offset_handling + with self.assertRaises(ValueError): + channel_pair._amplitude_offset_handling = 'invalid' + channel_pair.upload('test', program, (1, None), (None, None), (lambda x: x, lambda x: x)) + channel_pair._amplitude_offset_handling = old + + channel_pair._known_programs['test'] = self.TaborProgramMemory(np.array([0]), None) + with self.assertRaises(ValueError): + channel_pair.upload('test', program, (1, 2), (3, 4), (lambda x: x, lambda x: x)) + + def test_upload(self): + segments = np.array([1, 2, 3, 4, 5]) + segment_lengths = np.array([0, 16, 0, 16, 0], dtype=np.uint16).tolist() + + segment_references = np.array([1, 1, 2, 0, 1], dtype=np.uint32) + + w2s = np.array([-1, -1, 1, 2, -1], dtype=np.int64) + ta = np.array([True, False, False, False, True]) + ti = np.array([-1, 3, -1, -1, -1]) + + channels = (1, None) + markers = (None, None) + voltage_transformations = (lambda x: x, lambda x: x) + sample_rate = TimeType.from_fraction(1, 1) + + with mock.patch('qupulse.hardware.awgs.tabor.TaborProgram', specs=TaborProgram) as DummyTaborProgram: + tabor_program = DummyTaborProgram.return_value + tabor_program.get_sampled_segments.return_value = (segments, segment_lengths) + + program = self.Loop(waveform=self.DummyWaveform(duration=192)) + + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + channel_pair._segment_references = segment_references + + def dummy_find_place(segments_, segement_lengths_): + self.assertIs(segments_, segments) + self.assertIs(segment_lengths, segement_lengths_) + return w2s, ta, ti + + def dummy_upload_segment(segment_index, segment): + self.assertEqual(segment_index, 3) + self.assertEqual(segment, 2) + + def dummy_amend_segments(segments_): + np.testing.assert_equal(segments_, np.array([1, 5])) + return np.array([5, 6], dtype=np.int64) + + channel_pair._find_place_for_segments_in_memory = dummy_find_place + channel_pair._upload_segment = dummy_upload_segment + channel_pair._amend_segments = dummy_amend_segments + + channel_pair.upload('test', program, channels, markers, voltage_transformations) + + DummyTaborProgram.assert_called_once_with( + program, + channels=tuple(channels), + markers=markers, + device_properties=channel_pair.device.dev_properties, + sample_rate=sample_rate, + amplitudes=(.5, .5), + offsets=(0., 0.), + voltage_transformations=voltage_transformations + ) + + # the other references are increased in amend and upload segment method + np.testing.assert_equal(channel_pair._segment_references, np.array([1, 2, 3, 0, 1])) + + self.assertEqual(len(channel_pair._known_programs), 1) + np.testing.assert_equal(channel_pair._known_programs['test'].waveform_to_segment, + np.array([5, 3, 1, 2, 6], dtype=np.int64)) + + def test_upload_offset_handling(self): + + program = self.Loop(waveform=self.TableWaveform(1, [(0, 0.1, self.HoldInterpolationStrategy()), + (192, 0.1, self.HoldInterpolationStrategy())])) + + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + channels = (1, None) + markers = (None, None) + + tabor_program_kwargs = dict( + channels=channels, + markers=markers, + device_properties=channel_pair.device.dev_properties) + + test_sample_rate = TimeType.from_fraction(1, 1) + test_amplitudes = (channel_pair.device.amplitude(channel_pair._channels[0]) / 2, + channel_pair.device.amplitude(channel_pair._channels[1]) / 2) + test_offset = 0.1 + test_transform = (lambda x: x, lambda x: x) + + with patch('qupulse.hardware.awgs.tabor.TaborProgram', wraps=TaborProgram) as tabor_program_mock: + with patch.object(self.instrument, 'offset', return_value=test_offset) as offset_mock: + tabor_program_mock.get_sampled_segments = mock.Mock(wraps=tabor_program_mock.get_sampled_segments) + + channel_pair.amplitude_offset_handling = AWGAmplitudeOffsetHandling.CONSIDER_OFFSET + channel_pair.upload('test1', program, channels, markers, test_transform) + + tabor_program_mock.assert_called_once_with(program, **tabor_program_kwargs, + sample_rate=test_sample_rate, + amplitudes=test_amplitudes, + offsets=(test_offset, test_offset), + voltage_transformations=test_transform) + self.assertEqual([mock.call(1), mock.call(2)], offset_mock.call_args_list) + offset_mock.reset_mock() + tabor_program_mock.reset_mock() + + channel_pair.amplitude_offset_handling = AWGAmplitudeOffsetHandling.IGNORE_OFFSET + channel_pair.upload('test2', program, (1, None), (None, None), test_transform) + + tabor_program_mock.assert_called_once_with(program, **tabor_program_kwargs, + sample_rate=test_sample_rate, + amplitudes=test_amplitudes, + offsets=(0., 0.), + voltage_transformations=test_transform) + self.assertEqual([], offset_mock.call_args_list) + + def test_find_place_for_segments_in_memory(self): + def hash_based_on_dir(ch): + hash_list = [] + for d in dir(ch): + o = getattr(ch, d) + if isinstance(o, np.ndarray): + hash_list.append(hash(o.tobytes())) + else: + try: + hash_list.append(hash(o)) + except TypeError: + pass + return hash(tuple(hash_list)) + + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + # empty + segments = np.asarray([-5, -6, -7, -8, -9]) + segment_lengths = 192 + np.asarray([32, 16, 64, 32, 16]) + + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(ta.tolist(), [True, True, True, True, True]) + self.assertEqual(ti.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + # all new segments + channel_pair._segment_capacity = 192 + np.asarray([0, 16, 32, 16, 0], dtype=np.uint32) + channel_pair._segment_hashes = np.asarray([1, 2, 3, 4, 5], dtype=np.int64) + channel_pair._segment_references = np.asarray([1, 1, 1, 2, 1], dtype=np.int32) + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(ta.tolist(), [True, True, True, True, True]) + self.assertEqual(ti.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + # some known segments + channel_pair._segment_capacity = 192 + np.asarray([0, 16, 32, 64, 0, 16], dtype=np.uint32) + channel_pair._segment_hashes = np.asarray([1, 2, 3, -7, 5, -9], dtype=np.int64) + channel_pair._segment_references = np.asarray([1, 1, 1, 2, 1, 3], dtype=np.int32) + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, 3, -1, 5]) + self.assertEqual(ta.tolist(), [True, True, False, True, False]) + self.assertEqual(ti.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + # insert some segments with same length + channel_pair._segment_capacity = 192 + np.asarray([0, 16, 32, 64, 0, 16], dtype=np.uint32) + channel_pair._segment_hashes = np.asarray([1, 2, 3, 4, 5, 6], dtype=np.int64) + channel_pair._segment_references = np.asarray([1, 0, 1, 0, 1, 3], dtype=np.int32) + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(ta.tolist(), [True, False, False, True, True]) + self.assertEqual(ti.tolist(), [-1, 1, 3, -1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + # insert some segments with smaller length + channel_pair._segment_capacity = 192 + np.asarray([0, 80, 32, 64, 96, 16], dtype=np.uint32) + channel_pair._segment_hashes = np.asarray([1, 2, 3, 4, 5, 6], dtype=np.int64) + channel_pair._segment_references = np.asarray([1, 0, 1, 1, 0, 3], dtype=np.int32) + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, -1, -1, -1]) + self.assertEqual(ta.tolist(), [True, True, False, False, True]) + self.assertEqual(ti.tolist(), [-1, -1, 4, 1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + # mix everything + segments = np.asarray([-5, -6, -7, -8, -9, -10, -11]) + segment_lengths = 192 + np.asarray([32, 16, 64, 32, 16, 0, 0]) + + channel_pair._segment_capacity = 192 + np.asarray([0, 80, 32, 64, 32, 16], dtype=np.uint32) + channel_pair._segment_hashes = np.asarray([1, 2, 3, 4, -8, 6], dtype=np.int64) + channel_pair._segment_references = np.asarray([1, 0, 1, 0, 1, 0], dtype=np.int32) + hash_before = hash_based_on_dir(channel_pair) + + w2s, ta, ti = channel_pair._find_place_for_segments_in_memory(segments, segment_lengths) + self.assertEqual(w2s.tolist(), [-1, -1, -1, 4, -1, -1, -1]) + self.assertEqual(ta.tolist(), [False, True, False, False, True, True, True]) + self.assertEqual(ti.tolist(), [1, -1, 3, -1, -1, -1, -1]) + self.assertEqual(hash_before, hash_based_on_dir(channel_pair)) + + def test_upload_segment(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + self.reset_instrument_logs() + + channel_pair._segment_references = np.array([1, 2, 0, 1], dtype=np.uint32) + channel_pair._segment_capacity = 192 + np.array([0, 16, 32, 32], dtype=np.uint32) + channel_pair._segment_lengths = channel_pair._segment_capacity.copy() + + channel_pair._segment_hashes = np.array([1, 2, 3, 4], dtype=np.int64) + + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + segment = self.TaborSegment.from_sampled(np.ones(192+16, dtype=np.uint16), np.zeros(192+16, dtype=np.uint16), None, None) + segment_binary = segment.get_as_binary() + with self.assertRaises(ValueError): + channel_pair._upload_segment(3, segment) + + with self.assertRaises(ValueError): + channel_pair._upload_segment(0, segment) + + channel_pair._upload_segment(2, segment) + np.testing.assert_equal(channel_pair._segment_capacity, 192 + np.array([0, 16, 32, 32], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_lengths, 192 + np.array([0, 16, 16, 32], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_hashes, np.array([1, 2, hash(segment), 4], dtype=np.int64)) + + expected_commands = [':INST:SEL 1', ':INST:SEL 1', ':INST:SEL 1', + ':TRAC:DEF 3, 208', + ':TRAC:SEL 3', + ':TRAC:MODE COMB'] + expected_log = [((), dict(cmd_str=cmd, paranoia_level=channel_pair.internal_paranoia_level)) + for cmd in expected_commands] + self.assertAllCommandLogsEqual(expected_log) + + expected_send_binary_data_log = [(':TRAC:DATA', segment_binary, None)] + for device in self.instrument.all_devices: + np.testing.assert_equal(device._send_binary_data_calls, expected_send_binary_data_log) + + def test_amend_segments_flush(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + self.instrument.main_instrument.paranoia_level = 0 + self.instrument.main_instrument.logged_commands = [] + self.instrument.main_instrument.logged_queries = [] + self.instrument.main_instrument._send_binary_data_calls = [] + self.reset_instrument_logs() + + channel_pair._segment_references = np.array([1, 2, 0, 1], dtype=np.uint32) + channel_pair._segment_capacity = 192 + np.array([0, 16, 32, 32], dtype=np.uint32) + channel_pair._segment_lengths = 192 + np.array([0, 16, 16, 32], dtype=np.uint32) + + channel_pair._segment_hashes = np.array([1, 2, 3, 4], dtype=np.int64) + + data = np.ones(192, dtype=np.uint16) + segments = [self.TaborSegment.from_sampled(0*data, 1*data, None, None), + self.TaborSegment.from_sampled(1*data, 2*data, None, None)] + + channel_pair._amend_segments(segments) + + expected_references = np.array([1, 2, 0, 1, 1, 1], dtype=np.uint32) + expected_capacities = 192 + np.array([0, 16, 32, 32, 0, 0], dtype=np.uint32) + expected_lengths = 192 + np.array([0, 16, 16, 32, 0, 0], dtype=np.uint32) + expected_hashes = np.array([1, 2, 3, 4, hash(segments[0]), hash(segments[1])], dtype=np.int64) + + np.testing.assert_equal(channel_pair._segment_references, expected_references) + np.testing.assert_equal(channel_pair._segment_capacity, expected_capacities) + np.testing.assert_equal(channel_pair._segment_lengths, expected_lengths) + np.testing.assert_equal(channel_pair._segment_hashes, expected_hashes) + + expected_commands = [':INST:SEL 1', + ':TRAC:DEF 5,{}'.format(2 * 192 + 16), + ':TRAC:SEL 5', + ':TRAC:MODE COMB', + ':TRAC:DEF 3,208'] + expected_log = [((), dict(cmd_str=cmd, paranoia_level=channel_pair.internal_paranoia_level)) + for cmd in expected_commands] + self.assertAllCommandLogsEqual(expected_log) + #self.assertEqual(expected_log, instrument.main_instrument.logged_commands) + + expected_download_segment_calls = [(expected_capacities, ':SEGM:DATA', None)] + np.testing.assert_equal(self.instrument.main_instrument._download_segment_lengths_calls, expected_download_segment_calls) + + expected_bin_blob = self.make_combined_wave(segments) + expected_send_binary_data_log = [(':TRAC:DATA', expected_bin_blob, None)] + np.testing.assert_equal(self.instrument.main_instrument._send_binary_data_calls, expected_send_binary_data_log) + + def test_amend_segments_iter(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + self.instrument.paranoia_level = 0 + self.reset_instrument_logs() + + channel_pair._segment_references = np.array([1, 2, 0, 1], dtype=np.uint32) + channel_pair._segment_capacity = 192 + np.array([0, 16, 32, 32], dtype=np.uint32) + channel_pair._segment_lengths = 192 + np.array([0, 0, 16, 16], dtype=np.uint32) + + channel_pair._segment_hashes = np.array([1, 2, 3, 4], dtype=np.int64) + + data = np.ones(192, dtype=np.uint16) + segments = [self.TaborSegment.from_sampled(0*data, 1*data, None, None), + self.TaborSegment.from_sampled(1*data, 2*data, None, None)] + + indices = channel_pair._amend_segments(segments) + + expected_references = np.array([1, 2, 0, 1, 1, 1], dtype=np.uint32) + expected_capacities = 192 + np.array([0, 16, 32, 32, 0, 0], dtype=np.uint32) + expected_lengths = 192 + np.array([0, 0, 16, 16, 0, 0], dtype=np.uint32) + expected_hashes = np.array([1, 2, 3, 4, hash(segments[0]), hash(segments[1])], dtype=np.int64) + + np.testing.assert_equal(channel_pair._segment_references, expected_references) + np.testing.assert_equal(channel_pair._segment_capacity, expected_capacities) + np.testing.assert_equal(channel_pair._segment_lengths, expected_lengths) + np.testing.assert_equal(channel_pair._segment_hashes, expected_hashes) + + np.testing.assert_equal(indices, np.array([4, 5], dtype=np.int64)) + + expected_commands = [':INST:SEL 1', + ':TRAC:DEF 5,{}'.format(2 * 192 + 16), + ':TRAC:SEL 5', + ':TRAC:MODE COMB', + ':TRAC:DEF 5,192', + ':TRAC:DEF 6,192'] + expected_log = [((), dict(cmd_str=cmd, paranoia_level=channel_pair.internal_paranoia_level)) + for cmd in expected_commands] + self.assertAllCommandLogsEqual(expected_log) + + expected_download_segment_calls = [] + for device in self.instrument.all_devices: + self.assertEqual(device._download_segment_lengths_calls, expected_download_segment_calls) + + expected_bin_blob = self.make_combined_wave(segments) + expected_send_binary_data_log = [(':TRAC:DATA', expected_bin_blob, None)] + for device in self.instrument.all_devices: + np.testing.assert_equal(device._send_binary_data_calls, expected_send_binary_data_log) + + def test_cleanup(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + self.instrument.paranoia_level = 0 + self.instrument.logged_commands = [] + self.instrument.logged_queries = [] + self.instrument._send_binary_data_calls = [] + + channel_pair._segment_references = np.array([1, 2, 0, 1], dtype=np.uint32) + channel_pair._segment_capacity = 192 + np.array([0, 16, 32, 32], dtype=np.uint32) + channel_pair._segment_lengths = 192 + np.array([0, 0, 16, 16], dtype=np.uint32) + channel_pair._segment_hashes = np.array([1, 2, 3, 4], dtype=np.int64) + + channel_pair.cleanup() + np.testing.assert_equal(channel_pair._segment_references, np.array([1, 2, 0, 1], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_capacity, 192 + np.array([0, 16, 32, 32], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_lengths, 192 + np.array([0, 0, 16, 16], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_hashes, np.array([1, 2, 3, 4], dtype=np.int64)) + + channel_pair._segment_references = np.array([1, 2, 0, 1, 0], dtype=np.uint32) + channel_pair._segment_capacity = 192 + np.array([0, 16, 32, 32, 32], dtype=np.uint32) + channel_pair._segment_lengths = 192 + np.array([0, 0, 16, 16, 0], dtype=np.uint32) + channel_pair._segment_hashes = np.array([1, 2, 3, 4, 5], dtype=np.int64) + + channel_pair.cleanup() + np.testing.assert_equal(channel_pair._segment_references, np.array([1, 2, 0, 1], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_capacity, 192 + np.array([0, 16, 32, 32], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_lengths, 192 + np.array([0, 0, 16, 16], dtype=np.uint32)) + np.testing.assert_equal(channel_pair._segment_hashes, np.array([1, 2, 3, 4], dtype=np.int64)) + + def test_remove(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + + calls = [] + + program_name = 'test' + def dummy_free_program(name): + self.assertIs(name, program_name) + calls.append('free_program') + + def dummy_cleanup(): + calls.append('cleanup') + + channel_pair.cleanup = dummy_cleanup + channel_pair.free_program = dummy_free_program + + channel_pair.remove(program_name) + self.assertEqual(calls, ['free_program', 'cleanup']) + + def test_change_armed_program_single_sequence(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + self.instrument.paranoia_level = 0 + self.instrument.logged_commands = [] + self.instrument.logged_queries = [] + self.reset_instrument_logs() + + advanced_sequencer_table = [(2, 1, 0)] + sequencer_tables = [[(3, 0, 0), (2, 1, 0), (1, 0, 0), (1, 2, 0), (1, 3, 0)]] + w2s = np.array([2, 5, 3, 1]) + + sequencer_tables = self.to_new_sequencer_tables(sequencer_tables) + advanced_sequencer_table = self.to_new_advanced_sequencer_table(advanced_sequencer_table) + + expected_sequencer_table = [(3, 3, 0), (2, 6, 0), (1, 3, 0), (1, 4, 0), (1, 2, 0)] + + program = DummyTaborProgramClass(advanced_sequencer_table=advanced_sequencer_table, + sequencer_tables=sequencer_tables, + waveform_mode=self.TaborSequencing.SINGLE)(None, None, None, None) + + channel_pair._known_programs['test'] = self.TaborProgramMemory(w2s, program) + + channel_pair.change_armed_program('test') + + expected_adv_seq_table_log = [([(1, 1, 1), (2, 2, 0), (1, 1, 0)], ':ASEQ:DATA', None)] + expected_sequencer_table_log = [((sequencer_table,), dict(pref=':SEQ:DATA', paranoia_level=None)) + for sequencer_table in [channel_pair._idle_sequence_table, + expected_sequencer_table]] + + for device in self.instrument.all_devices: + self.assertEqual(device._download_adv_seq_table_calls, expected_adv_seq_table_log) + self.assertEqual(device._download_sequencer_table_calls, expected_sequencer_table_log) + + def test_change_armed_program_single_waveform(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + self.instrument.paranoia_level = 0 + self.instrument.logged_commands = [] + self.instrument.logged_queries = [] + self.reset_instrument_logs() + + advanced_sequencer_table = [(1, 1, 0)] + sequencer_tables = [[(10, 0, 0)]] + w2s = np.array([4]) + + sequencer_tables = self.to_new_sequencer_tables(sequencer_tables) + advanced_sequencer_table = self.to_new_advanced_sequencer_table(advanced_sequencer_table) + + expected_sequencer_table = [(10, 5, 0), (1, 1, 0), (1, 1, 0)] + + program = DummyTaborProgramClass(advanced_sequencer_table=advanced_sequencer_table, + sequencer_tables=sequencer_tables, + waveform_mode=self.TaborSequencing.SINGLE)(None, None, None, None) + + channel_pair._known_programs['test'] = self.TaborProgramMemory(w2s, program) + + channel_pair.change_armed_program('test') + + expected_adv_seq_table_log = [([(1, 1, 1), (1, 2, 0), (1, 1, 0)], ':ASEQ:DATA', None)] + expected_sequencer_table_log = [((sequencer_table,), dict(pref=':SEQ:DATA', paranoia_level=None)) + for sequencer_table in [channel_pair._idle_sequence_table, + expected_sequencer_table]] + + for device in self.instrument.all_devices: + self.assertEqual(device._download_adv_seq_table_calls, expected_adv_seq_table_log) + self.assertEqual(device._download_sequencer_table_calls, expected_sequencer_table_log) + + def test_change_armed_program_advanced_sequence(self): + channel_pair = self.TaborChannelPair(self.instrument, identifier='asd', channels=(1, 2)) + # prevent entering and exiting configuration mode + channel_pair._configuration_guard_count = 2 + + self.instrument.paranoia_level = 0 + self.instrument.logged_commands = [] + self.instrument.logged_queries = [] + self.instrument._send_binary_data_calls = [] + + self.reset_instrument_logs() + + advanced_sequencer_table = [(2, 1, 0), (3, 2, 0)] + sequencer_tables = [[(3, 0, 0), (2, 1, 0), (1, 0, 0), (1, 2, 0), (1, 3, 0)], + [(4, 1, 0), (2, 1, 0), (1, 0, 0), (1, 2, 0), (1, 3, 0)]] + wf_idx2seg_idx = np.array([2, 5, 3, 1]) + + sequencer_tables = self.to_new_sequencer_tables(sequencer_tables) + advanced_sequencer_table = self.to_new_advanced_sequencer_table(advanced_sequencer_table) + + expected_sequencer_tables = [[(3, 3, 0), (2, 6, 0), (1, 3, 0), (1, 4, 0), (1, 2, 0)], + [(4, 6, 0), (2, 6, 0), (1, 3, 0), (1, 4, 0), (1, 2, 0)]] + + program = DummyTaborProgramClass(advanced_sequencer_table=advanced_sequencer_table, + sequencer_tables=sequencer_tables, + waveform_mode=self.TaborSequencing.ADVANCED)(None, None, None, None) + + channel_pair._known_programs['test'] = self.TaborProgramMemory(wf_idx2seg_idx, program) + + channel_pair.change_armed_program('test') + + expected_adv_seq_table_log = [([(1, 1, 1), (2, 2, 0), (3, 3, 0)], ':ASEQ:DATA', None)] + expected_sequencer_table_log = [((sequencer_table,), dict(pref=':SEQ:DATA', paranoia_level=None)) + for sequencer_table in [channel_pair._idle_sequence_table] + + expected_sequencer_tables] + + for device in self.instrument.all_devices: + self.assertEqual(device._download_adv_seq_table_calls, expected_adv_seq_table_log) + self.assertEqual(device._download_sequencer_table_calls, expected_sequencer_table_log) diff --git a/tests/hardware/tabor_exex_test.py b/tests/hardware/tabor_exex_test.py new file mode 100644 index 000000000..0e9e98537 --- /dev/null +++ b/tests/hardware/tabor_exex_test.py @@ -0,0 +1,136 @@ +import unittest + + +with_alazar = True + +def get_pulse(): + from qupulse.pulses import TablePulseTemplate as TPT, SequencePulseTemplate as SPT, RepetitionPulseTemplate as RPT + + ramp = TPT(identifier='ramp', channels={'out', 'trigger'}) + ramp.add_entry(0, 'start', channel='out') + ramp.add_entry('duration', 'stop', 'linear', channel='out') + + ramp.add_entry(0, 1, channel='trigger') + ramp.add_entry('duration', 1, 'hold', channel='trigger') + + ramp.add_measurement_declaration('meas', 0, 'duration') + + base = SPT([(ramp, dict(start='min', stop='max', duration='tau/3'), dict(meas='A')), + (ramp, dict(start='max', stop='max', duration='tau/3'), dict(meas='B')), + (ramp, dict(start='max', stop='min', duration='tau/3'), dict(meas='C'))], {'min', 'max', 'tau'}) + + repeated = RPT(base, 'n') + + root = SPT([repeated, repeated, repeated], {'min', 'max', 'tau', 'n'}) + + return root + + +def get_alazar_config(): + from atsaverage import alazar + from atsaverage.config import ScanlineConfiguration, CaptureClockConfiguration, EngineTriggerConfiguration,\ + TRIGInputConfiguration, InputConfiguration + + trig_level = int((5 + 0.4) / 10. * 255) + assert 0 <= trig_level < 256 + + config = ScanlineConfiguration() + config.triggerInputConfiguration = TRIGInputConfiguration(triggerRange=alazar.TriggerRangeID.etr_5V) + config.triggerConfiguration = EngineTriggerConfiguration(triggerOperation=alazar.TriggerOperation.J, + triggerEngine1=alazar.TriggerEngine.J, + triggerSource1=alazar.TriggerSource.external, + triggerSlope1=alazar.TriggerSlope.positive, + triggerLevel1=trig_level, + triggerEngine2=alazar.TriggerEngine.K, + triggerSource2=alazar.TriggerSource.disable, + triggerSlope2=alazar.TriggerSlope.positive, + triggerLevel2=trig_level) + config.captureClockConfiguration = CaptureClockConfiguration(source=alazar.CaptureClockType.internal_clock, + samplerate=alazar.SampleRateID.rate_100MSPS) + config.inputConfiguration = 4*[InputConfiguration(input_range=alazar.InputRangeID.range_1_V)] + config.totalRecordSize = 0 + + assert config.totalRecordSize == 0 + + return config + +def get_operations(): + from atsaverage.operations import Downsample + + return [Downsample(identifier='DS_A', maskID='A'), + Downsample(identifier='DS_B', maskID='B'), + Downsample(identifier='DS_C', maskID='C'), + Downsample(identifier='DS_D', maskID='D')] + +def get_window(card): + from atsaverage.gui import ThreadedStatusWindow + window = ThreadedStatusWindow(card) + window.start() + return window + + +class TaborTests(unittest.TestCase): + @unittest.skip + def test_all(self): + from qupulse.hardware.awgs.tabor import TaborChannelPair, TaborAWGRepresentation + #import warnings + tawg = TaborAWGRepresentation(r'USB0::0x168C::0x2184::0000216488::INSTR') + tchannelpair = TaborChannelPair(tawg, (1, 2), 'TABOR_AB') + tawg.paranoia_level = 2 + + #warnings.simplefilter('error', Warning) + + from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel + hardware_setup = HardwareSetup() + + hardware_setup.set_channel('TABOR_A', PlaybackChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B', PlaybackChannel(tchannelpair, 1)) + hardware_setup.set_channel('TABOR_A_MARKER', MarkerChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B_MARKER', MarkerChannel(tchannelpair, 1)) + + if with_alazar: + from qupulse.hardware.dacs.alazar import AlazarCard + import atsaverage.server + + if not atsaverage.server.Server.default_instance.running: + atsaverage.server.Server.default_instance.start(key=b'guest') + + import atsaverage.core + + alazar = AlazarCard(atsaverage.core.getLocalCard(1, 1)) + alazar.register_mask_for_channel('A', 0) + alazar.register_mask_for_channel('B', 0) + alazar.register_mask_for_channel('C', 0) + alazar.config = get_alazar_config() + + alazar.register_operations('test', get_operations()) + window = get_window(atsaverage.core.getLocalCard(1, 1)) + hardware_setup.register_dac(alazar) + + repeated = get_pulse() + + from qupulse.pulses.sequencing import Sequencer + + sequencer = Sequencer() + sequencer.push(repeated, + parameters=dict(n=1000, min=-0.5, max=0.5, tau=192*3), + channel_mapping={'out': 'TABOR_A', 'trigger': 'TABOR_A_MARKER'}, + window_mapping=dict(A='A', B='B', C='C')) + instruction_block = sequencer.build() + + hardware_setup.register_program('test', instruction_block) + + if with_alazar: + from atsaverage.masks import PeriodicMask + m = PeriodicMask() + m.identifier = 'D' + m.begin = 0 + m.end = 1 + m.period = 1 + m.channel = 0 + alazar._registered_programs['test'].masks.append(m) + + hardware_setup.arm_program('test') + + d = 1 + diff --git a/tests/hardware/tabor_new_driver_clock_tests.py b/tests/hardware/tabor_new_driver_clock_tests.py index 98ec19248..1375c8ec0 100644 --- a/tests/hardware/tabor_new_driver_clock_tests.py +++ b/tests/hardware/tabor_new_driver_clock_tests.py @@ -1,38 +1,27 @@ import unittest -class MyTest(unittest.TestCase): - @unittest.skip - def test_the_thing(self): - exec_test() - with_alazar = True - def get_pulse(): - from qupulse.pulses import SequencePulseTemplate as SPT, \ - RepetitionPulseTemplate as RPT, FunctionPulseTemplate as FPT, MultiChannelPulseTemplate as MPT + from qupulse.pulses import TablePulseTemplate as TPT, SequencePulseTemplate as SPT, RepetitionPulseTemplate as RPT - sine = FPT('U*sin(2*pi*t/tau)', 'tau', channel='out') - marker_on = FPT('1', 'tau', channel='trigger') + ramp = TPT(identifier='ramp', channels={'out', 'trigger'}) + ramp.add_entry(0, 'start', channel='out') + ramp.add_entry('duration', 'stop', 'linear', channel='out') - multi = MPT([sine, marker_on], {'tau', 'U'}) - multi.atomicity = True + ramp.add_entry(0, 1, channel='trigger') + ramp.add_entry('duration', 1, 'hold', channel='trigger') - assert sine.defined_channels == {'out'} - assert multi.defined_channels == {'out', 'trigger'} + ramp.add_measurement_declaration('meas', 0, 'duration') - sine.add_measurement_declaration('meas', 0, 'tau') - - base = SPT([(multi, dict(tau='tau', U='U'), dict(meas='A')), - (multi, dict(tau='tau', U='U'), dict(meas='A')), - (multi, dict(tau='tau', U='U'), dict(meas='A'))], {'tau', 'U'}) + base = SPT([(ramp, dict(start='min', stop='max', duration='tau/3'), dict(meas='A')), + (ramp, dict(start='max', stop='max', duration='tau/3'), dict(meas='B')), + (ramp, dict(start='max', stop='min', duration='tau/3'), dict(meas='C'))], {'min', 'max', 'tau'}) repeated = RPT(base, 'n') - root = SPT([repeated, repeated, repeated], {'tau', 'n', 'U'}) - - assert root.defined_channels == {'out', 'trigger'} + root = SPT([repeated, repeated, repeated], {'min', 'max', 'tau', 'n'}) return root @@ -65,12 +54,13 @@ def get_alazar_config(): return config - def get_operations(): - from atsaverage.operations import RepAverage - - return [RepAverage(identifier='REP_A', maskID='A')] + from atsaverage.operations import Downsample + return [Downsample(identifier='DS_A', maskID='A'), + Downsample(identifier='DS_B', maskID='B'), + Downsample(identifier='DS_C', maskID='C'), + Downsample(identifier='DS_D', maskID='D')] def get_window(card): from atsaverage.gui import ThreadedStatusWindow @@ -79,87 +69,67 @@ def get_window(card): return window -def exec_test(): - import time - import numpy as np - - t = [] - names = [] - - def tic(name): - t.append(time.time()) - names.append(name) - - from qupulse.hardware.awgs_new_driver.tabor import TaborChannelPair, TaborAWGRepresentation - tawg = TaborAWGRepresentation(r'USB0::0x168C::0x2184::0000216488::INSTR', reset=True) - - tchannelpair = TaborChannelPair(tawg, (1, 2), 'TABOR_AB') - tawg.paranoia_level = 2 - - # warnings.simplefilter('error', Warning) - - from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel - hardware_setup = HardwareSetup() - - hardware_setup.set_channel('TABOR_A', PlaybackChannel(tchannelpair, 0)) - hardware_setup.set_channel('TABOR_B', PlaybackChannel(tchannelpair, 1)) - hardware_setup.set_channel('TABOR_A_MARKER', MarkerChannel(tchannelpair, 0)) - hardware_setup.set_channel('TABOR_B_MARKER', MarkerChannel(tchannelpair, 1)) - - if with_alazar: - from qupulse.hardware.dacs.alazar import AlazarCard - import atsaverage.server - - if not atsaverage.server.Server.default_instance.running: - atsaverage.server.Server.default_instance.start(key=b'guest') - - import atsaverage.core - - alazar = AlazarCard(atsaverage.core.getLocalCard(1, 1)) - alazar.register_mask_for_channel('A', 0) - alazar.config = get_alazar_config() +class TaborTests(unittest.TestCase): + @unittest.skip + def test_all(self): + from qupulse.hardware.awgs_new_driver.tabor import TaborChannelTuple, TaborDevice + #import warnings + tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') + tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') + tawg.paranoia_level = 2 - alazar.register_operations('test', get_operations()) + #warnings.simplefilter('error', Warning) - window = get_window(atsaverage.core.getLocalCard(1, 1)) + from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel + hardware_setup = HardwareSetup() - hardware_setup.register_dac(alazar) + hardware_setup.set_channel('TABOR_A', PlaybackChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B', PlaybackChannel(tchannelpair, 1)) + hardware_setup.set_channel('TABOR_A_MARKER', MarkerChannel(tchannelpair, 0)) + hardware_setup.set_channel('TABOR_B_MARKER', MarkerChannel(tchannelpair, 1)) - repeated = get_pulse() + if with_alazar: + from qupulse.hardware.dacs.alazar import AlazarCard + import atsaverage.server - from qupulse.pulses.sequencing import Sequencer + if not atsaverage.server.Server.default_instance.running: + atsaverage.server.Server.default_instance.start(key=b'guest') - tic('init') - sequencer = Sequencer() - sequencer.push(repeated, - parameters=dict(n=10000, tau=1920, U=0.5), - channel_mapping={'out': 'TABOR_A', 'trigger': 'TABOR_A_MARKER'}, - window_mapping=dict(A='A')) - instruction_block = sequencer.build() + import atsaverage.core - tic('sequence') + alazar = AlazarCard(atsaverage.core.getLocalCard(1, 1)) + alazar.register_mask_for_channel('A', 0) + alazar.register_mask_for_channel('B', 0) + alazar.register_mask_for_channel('C', 0) + alazar.config = get_alazar_config() - hardware_setup.register_program('test', instruction_block) + alazar.register_operations('test', get_operations()) + window = get_window(atsaverage.core.getLocalCard(1, 1)) + hardware_setup.register_dac(alazar) - tic('register') + repeated = get_pulse() - if with_alazar: - from atsaverage.masks import PeriodicMask - m = PeriodicMask() - m.identifier = 'D' - m.begin = 0 - m.end = 1 - m.period = 1 - m.channel = 0 - alazar._registered_programs['test'].masks.append(m) + from qupulse.pulses.sequencing import Sequencer - tic('per_mask') + sequencer = Sequencer() + sequencer.push(repeated, + parameters=dict(n=1000, min=-0.5, max=0.5, tau=192*3), + channel_mapping={'out': 'TABOR_A', 'trigger': 'TABOR_A_MARKER'}, + window_mapping=dict(A='A', B='B', C='C')) + instruction_block = sequencer.build() - hardware_setup.arm_program('test') + hardware_setup.register_program('test', instruction_block) - tic('arm') + if with_alazar: + from atsaverage.masks import PeriodicMask + m = PeriodicMask() + m.identifier = 'D' + m.begin = 0 + m.end = 1 + m.period = 1 + m.channel = 0 + alazar._registered_programs['test'].masks.append(m) - for d, name in zip(np.diff(np.asarray(t)), names[1:]): - print(name, d) + hardware_setup.arm_program('test') - d = 1 + d = 1 diff --git a/tests/hardware/tabor_new_driver_dummy_based_tests.py b/tests/hardware/tabor_new_driver_dummy_based_tests.py index e38411011..f52371cb3 100644 --- a/tests/hardware/tabor_new_driver_dummy_based_tests.py +++ b/tests/hardware/tabor_new_driver_dummy_based_tests.py @@ -1,17 +1,17 @@ import sys import unittest from unittest import mock -from unittest.mock import patch +from unittest.mock import patch, MagicMock from typing import List, Tuple, Optional, Any from copy import copy, deepcopy import numpy as np -from qupulse._program.tabor import TableDescription, TableEntry from qupulse.hardware.awgs.base import AWGAmplitudeOffsetHandling from qupulse.hardware.awgs_new_driver.tabor import TaborProgram, TaborProgramMemory from qupulse.utils.types import TimeType +from qupulse._program.tabor import TableDescription, TimeType, TableEntry from tests.hardware.dummy_modules import import_package diff --git a/tests/hardware/tabor_new_driver_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py index 0dc0f571d..75ac635b1 100644 --- a/tests/hardware/tabor_new_driver_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -11,7 +11,7 @@ from qupulse._program.tabor import TableDescription, TableEntry from qupulse.hardware.awgs_new_driver.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborSegment - +from qupulse.utils.types import TimeType class TaborSimulatorManager: def __init__(self, @@ -82,7 +82,7 @@ class TaborSimulatorBasedTest(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.instrument: TaborDevice + self.instrument: TaborDevice = None @classmethod def setUpClass(cls): @@ -119,18 +119,13 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_sample_rate(self): - # for ch in (1, 2, 3, 4): - # self.assertIsInstance(self.instrument.sample_rate(ch), int) - # for ch_tuple in self.instrument.channel_tuples: - # self.assertIsInstance(ch_tuple.sample_rate,int) - - # with self.assertRaises(TaborException): - # self.instrument.sample_rate(0) + for ch_tuple in self.instrument.channel_tuples: + self.assertIsInstance(ch_tuple.sample_rate,TimeType) self.instrument[SCPI].send_cmd(':INST:SEL 1') self.instrument[SCPI].send_cmd(':FREQ:RAST 2.3e9') - # TODO: int or float self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) + self.assertEqual(2300000000, self.instrument.channel_tuples[0].sample_rate) def test_amplitude(self): for channel in self.instrument.channels: @@ -168,10 +163,10 @@ class TaborMemoryReadTests(TaborSimulatorBasedTest): def setUp(self): super().setUp() - ramp_up = np.linspace(0, 2 ** 14 - 1, num=192, dtype=np.uint16) + ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) ramp_down = ramp_up[::-1] - zero = np.ones(192, dtype=np.uint16) * 2 ** 13 - sine = ((np.sin(np.linspace(0, 2 * np.pi, 192 + 64)) + 1) / 2 * (2 ** 14 - 1)).astype(np.uint16) + zero = np.ones(192, dtype=np.uint16) * 2**13 + sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), TaborSegment.from_sampled(ramp_down, zero, None, None), @@ -187,12 +182,8 @@ def setUp(self): self.sequence_tables = self.to_new_sequencer_tables(self.sequence_tables_raw) self.advanced_sequence_table = self.to_new_advanced_sequencer_table(self.advanced_sequence_table) - # TODO: darf man das so ersetzen - # self.channel_pair = TaborChannelTuple(self.instrument, (1, 2), 'tabor_unit_test') - self.channel_pair = self.instrument.channel_tuples[0] - def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: @staticmethod @@ -215,12 +206,11 @@ def update_volatile_parameters(parameters): waveform_mode = mode self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) - self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') #TODO: change - change_armed_program in the feature doesnt work yet + self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') def test_read_waveforms(self): self.channel_pair._amend_segments(self.segments) - #waveforms sind schon nicht gleich zum alten Treiber waveforms = self.channel_pair.read_waveforms() segments = [TaborSegment.from_binary_segment(waveform) @@ -256,15 +246,12 @@ def test_read_advanced_sequencer_table(self): self.channel_pair._amend_segments(self.segments) self.arm_program(self.sequence_tables, self.advanced_sequence_table, None, np.asarray([1, 2])) - #TODO: test here - #actual_advanced_table = [(1, 1, 1)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] expected = list(np.asarray(d) for d in zip(*actual_advanced_table)) advanced_table = self.channel_pair.read_advanced_sequencer_table() - np.testing.assert_equal(advanced_table, expected) def test_set_volatile_parameter(self): diff --git a/tests/hardware/tabor_simulator_based_test_debug.py b/tests/hardware/tabor_simulator_based_test_debug.py new file mode 100644 index 000000000..420adea8a --- /dev/null +++ b/tests/hardware/tabor_simulator_based_test_debug.py @@ -0,0 +1,42 @@ +from qupulse.hardware.awgs_new_driver.features import SCPI, ProgramManagement, StatusTable +from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborVoltageRange + +testDevice = TaborDevice("testDevice", + "127.0.0.1", + reset=True, + paranoia_level=2) + +# print(testDevice.dev_properties) + +# testDevice.send_cmd(':INST:SEL 1; :OUTP:COUP DC') +# testDevice.send_cmd(':VOLT 0.7') + +# print(testDevice._is_coupled()) + + +print(testDevice._is_coupled()) + +testDevice.channel_tuples[0][ProgramManagement]._cont_repetition_mode() +testDevice.channel_tuples[1][ProgramManagement]._cont_repetition_mode() +print(testDevice[StatusTable].get_status_table()) +#testDevice.channel_tuples[1][ProgramManagement]._trig_repetition_mode() +print(testDevice[StatusTable].get_status_table()) + +testDevice[SCPI].send_cmd(':INST:SEL 1') +testDevice[SCPI].send_cmd(':TRIG') + + + +#testDevice[SCPI].send_cmd(':INST:COUP:STAT ON') + +print(testDevice._is_coupled()) + +print(testDevice.channels[0][TaborVoltageRange].amplitude) +print(testDevice.channels[0][TaborVoltageRange].offset) + +#print(testDevice.channel_tuples[0].read_sequence_tables) +#print(testDevice.channel_tuples[0].read_waveforms) +#testDevice[SCPI].send_cmd('init:cont 0') +#testDevice.cleanup() + + diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_tests.py new file mode 100644 index 000000000..535746797 --- /dev/null +++ b/tests/hardware/tabor_tests.py @@ -0,0 +1,52 @@ +import unittest +import itertools +import numpy as np + +from teawg import model_properties_dict + +from qupulse.hardware.awgs.tabor import TaborException, TaborProgram, \ + TaborSegment, TaborSequencing, with_configuration_guard, PlottableProgram +from qupulse._program._loop import Loop +from qupulse.hardware.util import voltage_to_uint16 + +from tests.pulses.sequencing_dummies import DummyWaveform +from tests._program.loop_tests import LoopTests, WaveformGenerator + + + +class ConfigurationGuardTest(unittest.TestCase): + class DummyChannelPair: + def __init__(self, test_obj: unittest.TestCase): + self.test_obj = test_obj + self._configuration_guard_count = 0 + self.is_in_config_mode = False + + def _enter_config_mode(self): + self.test_obj.assertFalse(self.is_in_config_mode) + self.test_obj.assertEqual(self._configuration_guard_count, 0) + self.is_in_config_mode = True + + def _exit_config_mode(self): + self.test_obj.assertTrue(self.is_in_config_mode) + self.test_obj.assertEqual(self._configuration_guard_count, 0) + self.is_in_config_mode = False + + @with_configuration_guard + def guarded_method(self, counter=5, throw=False): + self.test_obj.assertTrue(self.is_in_config_mode) + if counter > 0: + return self.guarded_method(counter - 1, throw) + 1 + if throw: + raise RuntimeError() + return 0 + + def test_config_guard(self): + channel_pair = ConfigurationGuardTest.DummyChannelPair(self) + + for i in range(5): + self.assertEqual(channel_pair.guarded_method(i), i) + + with self.assertRaises(RuntimeError): + channel_pair.guarded_method(1, True) + + self.assertFalse(channel_pair.is_in_config_mode) \ No newline at end of file From 1ac3038f5c386104a3bf41c6c460d32d54976a9f Mon Sep 17 00:00:00 2001 From: bpapajewski <56872093+bpapajewski@users.noreply.github.com> Date: Wed, 3 Feb 2021 09:27:56 +0100 Subject: [PATCH 088/107] Delete tabor_simulator_based_test_debug.py removed tabor_similator_based_test_debug.py because it was only for debugging --- .../tabor_simulator_based_test_debug.py | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 tests/hardware/tabor_simulator_based_test_debug.py diff --git a/tests/hardware/tabor_simulator_based_test_debug.py b/tests/hardware/tabor_simulator_based_test_debug.py deleted file mode 100644 index 420adea8a..000000000 --- a/tests/hardware/tabor_simulator_based_test_debug.py +++ /dev/null @@ -1,42 +0,0 @@ -from qupulse.hardware.awgs_new_driver.features import SCPI, ProgramManagement, StatusTable -from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborVoltageRange - -testDevice = TaborDevice("testDevice", - "127.0.0.1", - reset=True, - paranoia_level=2) - -# print(testDevice.dev_properties) - -# testDevice.send_cmd(':INST:SEL 1; :OUTP:COUP DC') -# testDevice.send_cmd(':VOLT 0.7') - -# print(testDevice._is_coupled()) - - -print(testDevice._is_coupled()) - -testDevice.channel_tuples[0][ProgramManagement]._cont_repetition_mode() -testDevice.channel_tuples[1][ProgramManagement]._cont_repetition_mode() -print(testDevice[StatusTable].get_status_table()) -#testDevice.channel_tuples[1][ProgramManagement]._trig_repetition_mode() -print(testDevice[StatusTable].get_status_table()) - -testDevice[SCPI].send_cmd(':INST:SEL 1') -testDevice[SCPI].send_cmd(':TRIG') - - - -#testDevice[SCPI].send_cmd(':INST:COUP:STAT ON') - -print(testDevice._is_coupled()) - -print(testDevice.channels[0][TaborVoltageRange].amplitude) -print(testDevice.channels[0][TaborVoltageRange].offset) - -#print(testDevice.channel_tuples[0].read_sequence_tables) -#print(testDevice.channel_tuples[0].read_waveforms) -#testDevice[SCPI].send_cmd('init:cont 0') -#testDevice.cleanup() - - From 2aeffb6655103531c89a22ee5a2b171bd589811e Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 3 Feb 2021 13:24:25 +0100 Subject: [PATCH 089/107] -added a comment for the method _is_coupled of a device -changed the comment of the _initialize of a device --- qupulse/hardware/awgs_new_driver/tabor.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py index ecfd061e1..619dd2673 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -295,14 +295,15 @@ def set_coupled(self, coupled: bool) -> None: self[SCPI].send_cmd("INST:COUP:STAT OFF") def _is_coupled(self) -> bool: - # TODO: comment is missing + """ + Returns true if the coupling of the device is 'coupled' otherwise false + """ if self._coupled is None: return self[SCPI].send_query(":INST:COUP:STAT?") == "ON" else: return self._coupled def cleanup(self) -> None: - # TODO: split cleanup in to two different methods for channel_tuple in self.channel_tuples: channel_tuple.cleanup() @@ -365,11 +366,9 @@ def _download_adv_seq_table(self, seq_table, pref=":ASEQ:DATA", paranoia_level=N make_combined_wave = staticmethod(teawg.TEWXAwg.make_combined_wave) def _initialize(self) -> None: - # TODO: work on this comment - # 1. Select channel # 2. Turn off gated mode - # 3. continous mode + # 3. Turn on continous mode # 4. Armed mode (only generate waveforms after enab command) # 5. Expect enable signal from (USB / LAN / GPIB) # 6. Use arbitrary waveforms as marker source @@ -482,7 +481,6 @@ def disable(self): def _select(self) -> None: self._parent()._select() - # Implementation class TaborChannel(AWGChannel): def __init__(self, idn: int, device: TaborDevice): From 931ee763cc165f3f88c2523a77349a5e5890cba5 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 3 Feb 2021 13:25:59 +0100 Subject: [PATCH 090/107] removed old comment from the method _enter_config_mode of a channel tuple --- qupulse/hardware/awgs_new_driver/tabor.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py index 619dd2673..b72961768 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -1259,9 +1259,6 @@ def _enter_config_mode(self) -> None: marker_0_cmd = ":SOUR:MARK:SEL 1;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" marker_1_cmd = ":SOUR:MARK:SEL 2;:SOUR:MARK:SOUR USER;:SOUR:MARK:STAT OFF" - # TODO: würde diese ersetzung reichen? - maybe change for synchronisationsfeature - # for marker_ch in self.marker_channels: - # marker_ch[TaborMarkerChannelActivatable].disable() wf_mode_cmd = ":SOUR:FUNC:MODE FIX" @@ -1274,8 +1271,6 @@ def _enter_config_mode(self) -> None: def _exit_config_mode(self) -> None: """Leave the configuration mode. Enter advanced sequence mode and turn on all outputs""" - # TODO: change implementation for channel synchronisation feature - if self.device._is_coupled(): # Coupled -> switch all channels at once other_channel_tuple: TaborChannelTuple From dff82b4b975497b8b5d090baf2a3355494e5395f Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Wed, 3 Feb 2021 13:58:02 +0100 Subject: [PATCH 091/107] added the implenetation for the method enabled for a channel and a marker channel --- qupulse/hardware/awgs_new_driver/tabor.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/awgs_new_driver/tabor.py index b72961768..0e309a666 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/awgs_new_driver/tabor.py @@ -463,8 +463,9 @@ def __init__(self, channel: "TaborChannel"): def enabled(self) -> bool: """ Returns the the state a channel has at the moment. A channel is either activated or deactivated + True stands for activated and false for deactivated """ - pass # TODO: to implement + return self._parent().device[SCPI].send_query(":OUTP ?") == "ON" @with_select def enable(self): @@ -1309,10 +1310,15 @@ def __init__(self, marker_channel: "TaborMarkerChannel"): @property def enabled(self) -> bool: - pass # TODO: to implement + """ + Returns the the state a marker channel has at the moment. A channel is either activated or deactivated + True stands for activated and false for deactivated + """ + return self._parent().device[SCPI].send_query(":MARK:STAT ?") == "ON" @with_select def enable(self): + """Enables the output of a certain marker channel""" command_string = "SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, @@ -1321,6 +1327,7 @@ def enable(self): @with_select def disable(self): + """Disable the output of a certain marker channel""" command_string = ":SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" command_string = command_string.format( channel=self._parent().channel_tuple.channels[0].idn, From fc56b9042fde6651058752fae4e12f7454015731 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 18 Feb 2021 08:02:02 +0100 Subject: [PATCH 092/107] added MATLAB/+qc/personalPaths.mat and /doc/source/_autosummary/* to the gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d8ce04dd9..ea2cdec63 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ dist/* doc/source/examples/.ipynb_checkpoints/* **.asv *.orig +MATLAB/+qc/personalPaths.mat +/doc/source/_autosummary/* .idea/ .mypy_cache/* .gitignore From ed078729ae270577506b85abd716172a3571ef65 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 22 Feb 2021 08:22:54 +0100 Subject: [PATCH 093/107] fixed the gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ea2cdec63..7124aaa3e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,4 @@ MATLAB/+qc/personalPaths.mat /doc/source/_autosummary/* .idea/ .mypy_cache/* -.gitignore -tests/hardware/WX2184C.exe \ No newline at end of file +tests/hardware/WX2184C.exe From 3944901deb9223dc396f7250617e3682500a9a9b Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 22 Feb 2021 14:51:12 +0100 Subject: [PATCH 094/107] renamed to tabor_new_driver package to feature_awg --- doc/source/examples/hardware/tabor.py | 2 +- qupulse/_program/tabor.py | 2 +- qupulse/examples/VolatileParameters.py | 2 +- qupulse/hardware/awgs/__init__.py | 2 +- .../{awgs_new_driver => feature_awg}/__init__.py | 0 .../{awgs_new_driver => feature_awg}/base.py | 4 ++-- .../base_features.py | 0 .../channel_tuple_wrapper.py | 13 ++++++------- .../{awgs_new_driver => feature_awg}/features.py | 2 +- .../{awgs_new_driver => feature_awg}/tabor.py | 6 +++--- tests/hardware/awg_new_driver_base_tests.py | 4 ++-- tests/hardware/tabor_new_driver_clock_tests.py | 2 +- .../hardware/tabor_new_driver_dummy_based_tests.py | 2 +- tests/hardware/tabor_new_driver_exex_test.py | 2 +- .../tabor_new_driver_simulator_based_tests.py | 8 ++++---- tests/hardware/tabor_new_driver_tests.py | 2 +- 16 files changed, 26 insertions(+), 27 deletions(-) rename qupulse/hardware/{awgs_new_driver => feature_awg}/__init__.py (100%) rename qupulse/hardware/{awgs_new_driver => feature_awg}/base.py (98%) rename qupulse/hardware/{awgs_new_driver => feature_awg}/base_features.py (100%) rename qupulse/hardware/{awgs_new_driver => feature_awg}/channel_tuple_wrapper.py (77%) rename qupulse/hardware/{awgs_new_driver => feature_awg}/features.py (98%) rename qupulse/hardware/{awgs_new_driver => feature_awg}/tabor.py (99%) diff --git a/doc/source/examples/hardware/tabor.py b/doc/source/examples/hardware/tabor.py index a579e89b4..1926a854c 100644 --- a/doc/source/examples/hardware/tabor.py +++ b/doc/source/examples/hardware/tabor.py @@ -1,5 +1,5 @@ from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel -from qupulse.hardware.awgs_new_driver.tabor import TaborAWGRepresentation +from qupulse.hardware.awgs.tabor import TaborAWGRepresentation import pyvisa diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 047b015d3..131bdb482 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -9,7 +9,7 @@ import numpy as np from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.awgs_new_driver.base import ProgramEntry +from qupulse.hardware.feature_awg.base import ProgramEntry from qupulse.hardware.util import get_sample_times, voltage_to_uint16 from qupulse._program.waveforms import Waveform from qupulse._program._loop import Loop diff --git a/qupulse/examples/VolatileParameters.py b/qupulse/examples/VolatileParameters.py index e8ce07a12..53dcc3dab 100644 --- a/qupulse/examples/VolatileParameters.py +++ b/qupulse/examples/VolatileParameters.py @@ -29,7 +29,7 @@ awg_channel = awg.channel_pair_AB elif awg_name == 'TABOR': - from qupulse.hardware.awgs_new_driver.tabor import TaborAWGRepresentation + from qupulse.hardware.awgs.tabor import TaborAWGRepresentation awg = TaborAWGRepresentation(awg_address, reset=True) channel_pairs = [] diff --git a/qupulse/hardware/awgs/__init__.py b/qupulse/hardware/awgs/__init__.py index 5dd609b1a..31cdb6cc5 100644 --- a/qupulse/hardware/awgs/__init__.py +++ b/qupulse/hardware/awgs/__init__.py @@ -4,7 +4,7 @@ __all__ = ["install_requirements"] try: - from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborChannelTuple + from qupulse.hardware.feature_awg.tabor import TaborDevice, TaborChannelTuple __all__.extend(["TaborAWGRepresentation", "TaborChannelPair"]) except ImportError: pass diff --git a/qupulse/hardware/awgs_new_driver/__init__.py b/qupulse/hardware/feature_awg/__init__.py similarity index 100% rename from qupulse/hardware/awgs_new_driver/__init__.py rename to qupulse/hardware/feature_awg/__init__.py diff --git a/qupulse/hardware/awgs_new_driver/base.py b/qupulse/hardware/feature_awg/base.py similarity index 98% rename from qupulse/hardware/awgs_new_driver/base.py rename to qupulse/hardware/feature_awg/base.py index 675e4afae..30e60e874 100644 --- a/qupulse/hardware/awgs_new_driver/base.py +++ b/qupulse/hardware/feature_awg/base.py @@ -6,8 +6,8 @@ from qupulse._program._loop import Loop from qupulse._program.waveforms import Waveform -from qupulse.hardware.awgs_new_driver import channel_tuple_wrapper -from qupulse.hardware.awgs_new_driver.base_features import Feature, FeatureAble +from qupulse.hardware.feature_awg import channel_tuple_wrapper +from qupulse.hardware.feature_awg.base_features import Feature, FeatureAble from qupulse.hardware.util import get_sample_times from qupulse.utils.types import Collection, TimeType, ChannelID diff --git a/qupulse/hardware/awgs_new_driver/base_features.py b/qupulse/hardware/feature_awg/base_features.py similarity index 100% rename from qupulse/hardware/awgs_new_driver/base_features.py rename to qupulse/hardware/feature_awg/base_features.py diff --git a/qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py b/qupulse/hardware/feature_awg/channel_tuple_wrapper.py similarity index 77% rename from qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py rename to qupulse/hardware/feature_awg/channel_tuple_wrapper.py index ef807ea60..ad8d6597c 100644 --- a/qupulse/hardware/awgs_new_driver/channel_tuple_wrapper.py +++ b/qupulse/hardware/feature_awg/channel_tuple_wrapper.py @@ -1,12 +1,11 @@ from typing import Tuple, Optional, Callable, Set from qupulse._program._loop import Loop -from qupulse.hardware.awgs_new_driver.base import AWGChannelTuple +from qupulse.hardware.feature_awg.base import AWGChannelTuple from qupulse.hardware.awgs.base import AWG class ChannelTupleAdapter(AWG): - # TODO (toCheck): is this DocString okay like this? """ 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. @@ -32,24 +31,24 @@ def upload(self, name: str, markers: Tuple[Optional["ChannelID"], ...], voltage_transformation: Tuple[Optional[Callable], ...], force: bool = False) -> None: - from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement + from qupulse.hardware.feature_awg.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].upload(name, program, channels, markers, voltage_transformation, force) def remove(self, name: str) -> None: - from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement + from qupulse.hardware.feature_awg.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].remove(name) def clear(self) -> None: - from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement + from qupulse.hardware.feature_awg.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].clear() def arm(self, name: Optional[str]) -> None: - from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement + from qupulse.hardware.feature_awg.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].arm(name) def programs(self) -> Set[str]: - from qupulse.hardware.awgs_new_driver.tabor import ProgramManagement + from qupulse.hardware.feature_awg.tabor import ProgramManagement return self._channel_tuple[ProgramManagement].programs def sample_rate(self) -> float: diff --git a/qupulse/hardware/awgs_new_driver/features.py b/qupulse/hardware/feature_awg/features.py similarity index 98% rename from qupulse/hardware/awgs_new_driver/features.py rename to qupulse/hardware/feature_awg/features.py index 64bb5e097..b90a4d497 100644 --- a/qupulse/hardware/awgs_new_driver/features.py +++ b/qupulse/hardware/feature_awg/features.py @@ -2,7 +2,7 @@ from typing import Callable, Optional, Set, Tuple, Dict, Union from qupulse._program._loop import Loop -from qupulse.hardware.awgs_new_driver.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature +from qupulse.hardware.feature_awg.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature from qupulse.utils.types import ChannelID import pyvisa diff --git a/qupulse/hardware/awgs_new_driver/tabor.py b/qupulse/hardware/feature_awg/tabor.py similarity index 99% rename from qupulse/hardware/awgs_new_driver/tabor.py rename to qupulse/hardware/feature_awg/tabor.py index 0e309a666..03cd55dbd 100644 --- a/qupulse/hardware/awgs_new_driver/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -8,13 +8,13 @@ import numpy as np from qupulse import ChannelID from qupulse._program._loop import Loop, make_compatible -from qupulse.hardware.awgs_new_driver.channel_tuple_wrapper import ChannelTupleAdapter -from qupulse.hardware.awgs_new_driver.features import ChannelSynchronization, AmplitudeOffsetHandling, VoltageRange, \ +from qupulse.hardware.feature_awg.channel_tuple_wrapper import ChannelTupleAdapter +from qupulse.hardware.feature_awg.features import ChannelSynchronization, AmplitudeOffsetHandling, VoltageRange, \ ProgramManagement, ActivatableChannels, DeviceControl, StatusTable, SCPI, VolatileParameters, \ ReadProgram from qupulse.hardware.util import voltage_to_uint16, find_positions from qupulse.utils.types import Collection, TimeType -from qupulse.hardware.awgs_new_driver.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel +from qupulse.hardware.feature_awg.base import AWGChannelTuple, AWGChannel, AWGDevice, AWGMarkerChannel from typing import Sequence from qupulse._program.tabor import TaborSegment, TaborException, TaborProgram, PlottableProgram, TaborSequencing, \ make_combined_wave diff --git a/tests/hardware/awg_new_driver_base_tests.py b/tests/hardware/awg_new_driver_base_tests.py index 09047ca9a..f83eff68c 100644 --- a/tests/hardware/awg_new_driver_base_tests.py +++ b/tests/hardware/awg_new_driver_base_tests.py @@ -4,8 +4,8 @@ from qupulse import ChannelID from qupulse._program._loop import Loop -from qupulse.hardware.awgs_new_driver.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel -from qupulse.hardware.awgs_new_driver.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ +from qupulse.hardware.feature_awg.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel +from qupulse.hardware.feature_awg.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ AmplitudeOffsetHandling from qupulse.utils.types import Collection diff --git a/tests/hardware/tabor_new_driver_clock_tests.py b/tests/hardware/tabor_new_driver_clock_tests.py index 1375c8ec0..fe3ae7a3c 100644 --- a/tests/hardware/tabor_new_driver_clock_tests.py +++ b/tests/hardware/tabor_new_driver_clock_tests.py @@ -72,7 +72,7 @@ def get_window(card): class TaborTests(unittest.TestCase): @unittest.skip def test_all(self): - from qupulse.hardware.awgs_new_driver.tabor import TaborChannelTuple, TaborDevice + from qupulse.hardware.feature_awg.tabor import TaborChannelTuple, TaborDevice #import warnings tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') diff --git a/tests/hardware/tabor_new_driver_dummy_based_tests.py b/tests/hardware/tabor_new_driver_dummy_based_tests.py index f52371cb3..2ea52ab36 100644 --- a/tests/hardware/tabor_new_driver_dummy_based_tests.py +++ b/tests/hardware/tabor_new_driver_dummy_based_tests.py @@ -9,7 +9,7 @@ import numpy as np from qupulse.hardware.awgs.base import AWGAmplitudeOffsetHandling -from qupulse.hardware.awgs_new_driver.tabor import TaborProgram, TaborProgramMemory +from qupulse.hardware.feature_awg.tabor import TaborProgram, TaborProgramMemory from qupulse.utils.types import TimeType from qupulse._program.tabor import TableDescription, TimeType, TableEntry from tests.hardware.dummy_modules import import_package diff --git a/tests/hardware/tabor_new_driver_exex_test.py b/tests/hardware/tabor_new_driver_exex_test.py index 50da9ad82..c4cf4489e 100644 --- a/tests/hardware/tabor_new_driver_exex_test.py +++ b/tests/hardware/tabor_new_driver_exex_test.py @@ -72,7 +72,7 @@ def get_window(card): class TaborTests(unittest.TestCase): @unittest.skip def test_all(self): - from qupulse.hardware.awgs_new_driver.tabor import TaborChannelTuple, TaborDevice + from qupulse.hardware.feature_awg.tabor import TaborChannelTuple, TaborDevice #import warnings tawg = TaborDevice(r'USB0::0x168C::0x2184::0000216488::INSTR') tchannelpair = TaborChannelTuple(tawg, (1, 2), 'TABOR_AB') diff --git a/tests/hardware/tabor_new_driver_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py index 75ac635b1..3f2200245 100644 --- a/tests/hardware/tabor_new_driver_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -9,10 +9,11 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.awgs_new_driver.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters -from qupulse.hardware.awgs_new_driver.tabor import TaborDevice, TaborSegment +from qupulse.hardware.feature_awg.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters +from qupulse.hardware.feature_awg.tabor import TaborDevice, TaborSegment from qupulse.utils.types import TimeType + class TaborSimulatorManager: def __init__(self, simulator_executable='WX2184C.exe', @@ -120,7 +121,7 @@ def __init__(self, *args, **kwargs): def test_sample_rate(self): for ch_tuple in self.instrument.channel_tuples: - self.assertIsInstance(ch_tuple.sample_rate,TimeType) + self.assertIsInstance(ch_tuple.sample_rate, TimeType) self.instrument[SCPI].send_cmd(':INST:SEL 1') self.instrument[SCPI].send_cmd(':FREQ:RAST 2.3e9') @@ -263,7 +264,6 @@ def test_set_volatile_parameter(self): for rep, index, jump in table] for table in self.sequence_tables_raw] - actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] self.channel_pair[VolatileParameters].set_volatile_parameters('dummy_program', parameters=para) diff --git a/tests/hardware/tabor_new_driver_tests.py b/tests/hardware/tabor_new_driver_tests.py index b68943e2e..85421b042 100644 --- a/tests/hardware/tabor_new_driver_tests.py +++ b/tests/hardware/tabor_new_driver_tests.py @@ -1,6 +1,6 @@ import unittest -from qupulse.hardware.awgs_new_driver.tabor import with_configuration_guard +from qupulse.hardware.feature_awg.tabor import with_configuration_guard class ConfigurationGuardTest(unittest.TestCase): From 4de30b3163f28875c1afd3ce7f08e9b5fc9fac57 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 22 Feb 2021 14:58:28 +0100 Subject: [PATCH 095/107] removed silencing of all warnings in awg_new_driver_base_tests.py --- tests/hardware/awg_new_driver_base_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/hardware/awg_new_driver_base_tests.py b/tests/hardware/awg_new_driver_base_tests.py index f83eff68c..4641d76f9 100644 --- a/tests/hardware/awg_new_driver_base_tests.py +++ b/tests/hardware/awg_new_driver_base_tests.py @@ -9,9 +9,6 @@ AmplitudeOffsetHandling from qupulse.utils.types import Collection -warnings.simplefilter("ignore", UserWarning) - - ######################################################################################################################## # Example Features From 7107da8bb7f486d429e3a3c9f116968afd1db75d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Thu, 25 Feb 2021 13:30:03 +0100 Subject: [PATCH 096/107] reformated tabor_test.py to minimize merge conflicts --- tests/hardware/tabor_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_tests.py index 535746797..976c333b2 100644 --- a/tests/hardware/tabor_tests.py +++ b/tests/hardware/tabor_tests.py @@ -14,6 +14,8 @@ + + class ConfigurationGuardTest(unittest.TestCase): class DummyChannelPair: def __init__(self, test_obj: unittest.TestCase): @@ -49,4 +51,4 @@ def test_config_guard(self): with self.assertRaises(RuntimeError): channel_pair.guarded_method(1, True) - self.assertFalse(channel_pair.is_in_config_mode) \ No newline at end of file + self.assertFalse(channel_pair.is_in_config_mode) From 1b06544371fd7b7f8fa58a3bf60ce13b6efc139c Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 1 Mar 2021 11:29:52 +0100 Subject: [PATCH 097/107] reformated tabor_test.py to minimize merge conflicts --- tests/hardware/tabor_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_tests.py index 976c333b2..d960a2abb 100644 --- a/tests/hardware/tabor_tests.py +++ b/tests/hardware/tabor_tests.py @@ -52,3 +52,5 @@ def test_config_guard(self): channel_pair.guarded_method(1, True) self.assertFalse(channel_pair.is_in_config_mode) + + From a3d40a38c398b1b7fe51c42eef6bd35f0fc9eafb Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 2 Mar 2021 07:49:56 +0100 Subject: [PATCH 098/107] implemented missing methods in awg_new_driver_base_tests.py --- tests/hardware/awg_new_driver_base_tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/hardware/awg_new_driver_base_tests.py b/tests/hardware/awg_new_driver_base_tests.py index 4641d76f9..24bc5162e 100644 --- a/tests/hardware/awg_new_driver_base_tests.py +++ b/tests/hardware/awg_new_driver_base_tests.py @@ -4,6 +4,7 @@ from qupulse import ChannelID from qupulse._program._loop import Loop +from qupulse.hardware.feature_awg import channel_tuple_wrapper from qupulse.hardware.feature_awg.base import AWGDevice, AWGChannel, AWGChannelTuple, AWGMarkerChannel from qupulse.hardware.feature_awg.features import ChannelSynchronization, ProgramManagement, VoltageRange, \ AmplitudeOffsetHandling @@ -92,6 +93,13 @@ def arm(self, name: Optional[str]) -> None: def programs(self) -> Set[str]: return set(self._programs.keys()) + def run_current_program(self) -> None: + if self._armed_program: + print("Run Program:", self._armed_program) + print(self.programs[self._armed_program]) + else: + print("No program armed") + ######################################################################################################################## # Device & Channels @@ -161,6 +169,10 @@ def __init__(self, idn: int, device: TestAWGDevice, channels: Iterable["TestAWGC self._channels = tuple(channels) self._sample_rate = 12.456 # default value + @property + def channel_tuple_adapter(self) -> channel_tuple_wrapper: + pass + @property def sample_rate(self) -> float: return self._sample_rate From 09396a10c022ee6c058fcba7898728f76d213f16 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 2 Mar 2021 09:05:29 +0100 Subject: [PATCH 099/107] fixed refactoring mistake in setup.py --- qupulse/hardware/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qupulse/hardware/setup.py b/qupulse/hardware/setup.py index 9adf2d63c..bce719032 100644 --- a/qupulse/hardware/setup.py +++ b/qupulse/hardware/setup.py @@ -152,7 +152,7 @@ def get_default_info(awg): awg.upload(name, program=program, channels=tuple(playback_ids), - marker_channels=tuple(marker_ids), + markers=tuple(marker_ids), force=update, voltage_transformation=tuple(voltage_trafos)) From 1df3c4a1fb03c91f282a4579b24571dbd62bc352 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 16 Apr 2021 09:46:57 +0200 Subject: [PATCH 100/107] fixed SyntaxWarning from issue #561 --- qupulse/hardware/feature_awg/tabor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 721c8e3a9..6785ec0c6 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -549,7 +549,7 @@ def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: Throws: ValueError: this Exception is thrown when an invalid repetition mode is given """ - if repetition_mode is "infinite" or repetition_mode is "once": + if repetition_mode == "infinite" or repetition_mode == "once": self._channel_tuple._known_programs[program_name].program._repetition_mode = repetition_mode else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) @@ -721,7 +721,7 @@ def run_current_program(self) -> None: if self._channel_tuple._current_program: repetition_mode = self._channel_tuple._known_programs[ self._channel_tuple._current_program].program._repetition_mode - if repetition_mode is "infinite": + if repetition_mode == "infinite": self._cont_repetition_mode() self._channel_tuple.device[SCPI].send_cmd(':TRIG', paranoia_level=self._channel_tuple.internal_paranoia_level) @@ -737,7 +737,7 @@ def run_current_program(self) -> None: if self._channel_tuple._current_program: repetition_mode = self._channel_tuple._known_programs[ self._channel_tuple._current_program].program._repetition_mode - if repetition_mode is "infinite": + if repetition_mode == "infinite": self._cont_repetition_mode() self._channel_tuple.device[SCPI].send_cmd(':TRIG', paranoia_level=self._channel_tuple.internal_paranoia_level) else: From c684f9c488f6baabebc0e878b6e25150ed2f8e6f Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Mon, 26 Apr 2021 08:47:46 +0200 Subject: [PATCH 101/107] first changes for the runmode once --- qupulse/hardware/feature_awg/tabor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 6785ec0c6..077a96173 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -554,6 +554,7 @@ def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + #TODO: erweitern @property def supported_repetition_modes(self) -> Set[RepetitionMode]: return {RepetitionMode.INFINITE} @@ -1366,11 +1367,12 @@ def _select(self) -> None: self.device.channels[int((self.idn - 1) / 2)]._select() self.device[SCPI].send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) +######################################################################################################################## class TaborUndefinedState(TaborException): """ If this exception is raised the attached tabor device is in an undefined state. - It is highly recommended to call reset it.f + It is highly recommended to call reset it. """ def __init__(self, *args, device: Union[TaborDevice, TaborChannelTuple]): From 203a34f6e7056142d283b10979fbe4508919776d Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 1 Jun 2021 13:49:51 +0200 Subject: [PATCH 102/107] -first draft of the runmode auto rearm -moved the parent reference to the feature --- qupulse/_program/tabor.py | 7 +- qupulse/hardware/feature_awg/base.py | 8 +- qupulse/hardware/feature_awg/features.py | 8 +- qupulse/hardware/feature_awg/tabor.py | 220 ++++++++++++++--------- 4 files changed, 144 insertions(+), 99 deletions(-) diff --git a/qupulse/_program/tabor.py b/qupulse/_program/tabor.py index 131bdb482..2b498a450 100644 --- a/qupulse/_program/tabor.py +++ b/qupulse/_program/tabor.py @@ -8,8 +8,9 @@ import numpy as np +from qupulse.hardware.feature_awg.features import RepetitionMode from qupulse.utils.types import ChannelID, TimeType -from qupulse.hardware.feature_awg.base import ProgramEntry +from qupulse.hardware.awgs.base import ProgramEntry from qupulse.hardware.util import get_sample_times, voltage_to_uint16 from qupulse._program.waveforms import Waveform from qupulse._program._loop import Loop @@ -389,7 +390,7 @@ def __init__(self, voltage_transformations: Tuple[Optional[callable], Optional[callable]], sample_rate: TimeType, mode: TaborSequencing = None, - repetition_mode: str = "infinite", + repetition_mode: Union[str, RepetitionMode] = "infinite", ): if len(channels) != device_properties['chan_per_part']: raise TaborException('TaborProgram only supports {} channels'.format(device_properties['chan_per_part'])) @@ -420,7 +421,7 @@ def __init__(self, self._parsed_program = None # type: Optional[ParsedProgram] self._mode = None self._device_properties = device_properties - self._repetition_mode = repetition_mode + self._repetition_mode = RepetitionMode(repetition_mode) assert mode in (TaborSequencing.ADVANCED, TaborSequencing.SINGLE), "Invalid mode" if mode == TaborSequencing.SINGLE: diff --git a/qupulse/hardware/feature_awg/base.py b/qupulse/hardware/feature_awg/base.py index d8a85d16b..b45559b9e 100644 --- a/qupulse/hardware/feature_awg/base.py +++ b/qupulse/hardware/feature_awg/base.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Optional, Collection +from typing import Optional, Collection, Union import weakref from qupulse.hardware.awgs.base import AWG @@ -11,14 +11,16 @@ class AWGDeviceFeature(Feature): """Base class for features that are used for `AWGDevice`s""" - def __init__(self): + def __init__(self, device: 'AWGDevice'): super().__init__(AWGDevice) + self._device = weakref.proxy(device) class AWGChannelFeature(Feature): """Base class for features that are used for `AWGChannel`s""" - def __init__(self): + def __init__(self, channel: Union['AWGChannel', 'AWGMarkerChannel']): super().__init__(_BaseAWGChannel) + self._channel = weakref.proxy(channel) class AWGChannelTupleFeature(Feature): diff --git a/qupulse/hardware/feature_awg/features.py b/qupulse/hardware/feature_awg/features.py index 5a88fba40..97318ba39 100644 --- a/qupulse/hardware/feature_awg/features.py +++ b/qupulse/hardware/feature_awg/features.py @@ -4,8 +4,8 @@ from enum import Enum from qupulse._program._loop import Loop -from qupulse.hardware.feature_awg.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature,\ - AWGChannelTuple +from qupulse.hardware.feature_awg.base import AWGDeviceFeature, AWGChannelFeature, AWGChannelTupleFeature, \ + AWGChannelTuple, AWGDevice from qupulse.utils.types import ChannelID import pyvisa @@ -20,8 +20,8 @@ class SCPI(AWGDeviceFeature, ABC): https://en.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instruments """ - def __init__(self, visa: pyvisa.resources.MessageBasedResource): - super().__init__() + def __init__(self, device: 'AWGDevice', visa: pyvisa.resources.MessageBasedResource): + super().__init__(device) self._socket = visa def send_cmd(self, cmd_str): diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 077a96173..6c6c90a79 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -3,7 +3,7 @@ import numbers import sys import weakref -from typing import List, Tuple, Set, Callable, Optional, Any, cast, Union, Dict, Mapping, NamedTuple, Iterable,\ +from typing import List, Tuple, Set, Callable, Optional, Any, cast, Union, Dict, Mapping, NamedTuple, Iterable, \ Collection from collections import OrderedDict import numpy as np @@ -75,27 +75,25 @@ def selector(channel_tuple: "TaborChannelTuple", *args, **kwargs) -> Any: # Features class TaborSCPI(SCPI): def __init__(self, device: "TaborDevice", visa: pyvisa.resources.MessageBasedResource): - super().__init__(visa) - - self._parent = weakref.ref(device) + super().__init__(device=device, visa=visa) def send_cmd(self, cmd_str, paranoia_level=None): - for instr in self._parent().all_devices: + for instr in self._device.all_devices: instr.send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) def send_query(self, query_str, query_mirrors=False) -> Any: if query_mirrors: - return tuple(instr.send_query(query_str) for instr in self._parent().all_devices) + return tuple(instr.send_query(query_str) for instr in self._device.all_devices) else: - return self._parent().main_instrument.send_query(query_str) + return self._device.main_instrument.send_query(query_str) def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: """Overwrite send_cmd for paranoia_level > 3""" if paranoia_level is None: - paranoia_level = self._parent().paranoia_level + paranoia_level = self._device.paranoia_level if paranoia_level < 3: - self._parent().super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover + self._device.super().send_cmd(cmd_str=cmd_str, paranoia_level=paranoia_level) # pragma: no cover else: cmd_str = cmd_str.rstrip() @@ -104,12 +102,12 @@ def _send_cmd(self, cmd_str, paranoia_level=None) -> Any: else: ask_str = "*OPC?; :SYST:ERR?" - *answers, opc, error_code_msg = self._parent()._visa_inst.ask(ask_str).split(";") + *answers, opc, error_code_msg = self._device._visa_inst.ask(ask_str).split(";") error_code, error_msg = error_code_msg.split(",") error_code = int(error_code) if error_code != 0: - _ = self._parent()._visa_inst.ask("*CLS; *OPC?") + _ = self._device._visa_inst.ask("*CLS; *OPC?") if error_code == -450: # query queue overflow @@ -124,8 +122,7 @@ class TaborChannelSynchronization(ChannelSynchronization): """This Feature is used to synchronise a certain ammount of channels""" def __init__(self, device: "TaborDevice"): - super().__init__() - self._parent = weakref.ref(device) + super().__init__(device=device) def synchronize_channels(self, group_size: int) -> None: """ @@ -136,21 +133,21 @@ def synchronize_channels(self, group_size: int) -> None: group_size: Number of channels per channel tuple """ if group_size == 2: - self._parent()._channel_tuples = [] - for i in range((int)(len(self._parent().channels) / group_size)): - self._parent()._channel_tuples.append( + self._device._channel_tuples = [] + for i in range(int(len(self._device.channels) / group_size)): + self._device._channel_tuples.append( TaborChannelTuple((i + 1), - self._parent(), - self._parent().channels[(i * group_size):((i * group_size) + group_size)], - self._parent().marker_channels[(i * group_size):((i * group_size) + group_size)]) + self._device, + self._device.channels[(i * group_size):((i * group_size) + group_size)], + self._device.marker_channels[(i * group_size):((i * group_size) + group_size)]) ) - self._parent()[SCPI].send_cmd(":INST:COUP:STAT OFF") + self._device[SCPI].send_cmd(":INST:COUP:STAT OFF") elif group_size == 4: - self._parent()._channel_tuples = [TaborChannelTuple(1, - self._parent(), - self._parent().channels, - self._parent().marker_channels)] - self._parent()[SCPI].send_cmd(":INST:COUP:STAT ON") + self._device._channel_tuples = [TaborChannelTuple(1, + self._device, + self._device.channels, + self._device.marker_channels)] + self._device[SCPI].send_cmd(":INST:COUP:STAT ON") else: raise TaborException("Invalid group size") @@ -159,31 +156,30 @@ class TaborDeviceControl(DeviceControl): """This feature is used for basic communication with a AWG""" def __init__(self, device: "TaborDevice"): - super().__init__() - self._parent = weakref.ref(device) + super().__init__(device=device) def reset(self) -> None: """ Resetting the whole device. A command for resetting is send to the Device, the device is initialized again and all channel tuples are cleared. """ - self._parent()[SCPI].send_cmd(":RES") - self._parent()._coupled = None + self._device[SCPI].send_cmd(":RES") + self._device._coupled = None - self._parent()._initialize() - for channel_tuple in self._parent().channel_tuples: + self._device._initialize() + for channel_tuple in self._device.channel_tuples: channel_tuple[TaborProgramManagement].clear() def trigger(self) -> None: """ This method triggers a device remotely. """ - self._parent()[SCPI].send_cmd(":TRIG") + self._device[SCPI].send_cmd(":TRIG") class TaborStatusTable(StatusTable): def __init__(self, device: "TaborDevice"): - super().__init__() + super().__init__(device=device) self._parent = device def get_status_table(self) -> Dict[str, Union[str, float, int]]: @@ -221,8 +217,8 @@ def get_status_table(self) -> Dict[str, Union[str, float, int]]: data = OrderedDict((name, []) for name, *_ in name_query_type_list) for ch in (1, 2, 3, 4): - self._parent.channels[ch - 1]._select() - self._parent.marker_channels[(ch - 1) % 2]._select() + self._device.channels[ch - 1]._select() + self._device.marker_channels[(ch - 1) % 2]._select() for name, query, dtype in name_query_type_list: data[name].append(dtype(self._parent[SCPI].send_query(query))) return data @@ -413,25 +409,24 @@ def _get_readable_device(self, simulator=True) -> teawg.TEWXAwg: # Features class TaborVoltageRange(VoltageRange): def __init__(self, channel: "TaborChannel"): - super().__init__() - self._parent = weakref.ref(channel) + super().__init__(channel=channel) @property @with_select def offset(self) -> float: """Get offset of AWG channel""" return float( - self._parent().device[SCPI].send_query(":VOLT:OFFS?".format(channel=self._parent().idn))) + self._channel.device[SCPI].send_query(":VOLT:OFFS?".format(channel=self._channel.idn))) @property @with_select def amplitude(self) -> float: """Get amplitude of AWG channel""" - coupling = self._parent().device[SCPI].send_query(":OUTP:COUP?") + coupling = self._channel.device[SCPI].send_query(":OUTP:COUP?") if coupling == "DC": - return float(self._parent().device[SCPI].send_query(":VOLT?")) + return float(self._channel.device[SCPI].send_query(":VOLT?")) elif coupling == "HV": - return float(self._parent().device[SCPI].send_query(":VOLT:HV?")) + return float(self._channel.device[SCPI].send_query(":VOLT:HV?")) else: raise TaborException("Unknown coupling: {}".format(coupling)) @@ -441,7 +436,7 @@ def amplitude_offset_handling(self) -> AmplitudeOffsetHandling: Gets the amplitude and offset handling of this channel. The amplitude-offset controls if the amplitude and offset settings are constant or if these should be optimized by the driver """ - return self._parent()._amplitude_offset_handling + return self._channel._amplitude_offset_handling @amplitude_offset_handling.setter def amplitude_offset_handling(self, amp_offs_handling: Union[AmplitudeOffsetHandling, str]) -> None: @@ -449,16 +444,15 @@ def amplitude_offset_handling(self, amp_offs_handling: Union[AmplitudeOffsetHand amp_offs_handling: See possible values at `AWGAmplitudeOffsetHandling` """ amp_offs_handling = AmplitudeOffsetHandling(AmplitudeOffsetHandling) - self._parent()._amplitude_offset_handling = amp_offs_handling + self._channel._amplitude_offset_handling = amp_offs_handling def _select(self) -> None: - self._parent()._select() + self._channel._select() class TaborActivatableChannels(ActivatableChannels): def __init__(self, channel: "TaborChannel"): - super().__init__() - self._parent = weakref.ref(channel) + super().__init__(channel=channel) @property def enabled(self) -> bool: @@ -466,22 +460,23 @@ def enabled(self) -> bool: Returns the the state a channel has at the moment. A channel is either activated or deactivated True stands for activated and false for deactivated """ - return self._parent().device[SCPI].send_query(":OUTP ?") == "ON" + return self._channel.device[SCPI].send_query(":OUTP ?") == "ON" @with_select def enable(self): """Enables the output of a certain channel""" - command_string = ":OUTP ON".format(ch_id=self._parent().idn) - self._parent().device[SCPI].send_cmd(command_string) + command_string = ":OUTP ON".format(ch_id=self._channel.idn) + self._channel.device[SCPI].send_cmd(command_string) @with_select def disable(self): """Disables the output of a certain channel""" - command_string = ":OUTP OFF".format(ch_id=self._parent().idn) - self._parent().device[SCPI].send_cmd(command_string) + command_string = ":OUTP OFF".format(ch_id=self._channel.idn) + self._channel.device[SCPI].send_cmd(command_string) def _select(self) -> None: - self._parent()._select() + self._channel._select() + # Implementation class TaborChannel(AWGChannel): @@ -531,6 +526,9 @@ def __init__(self, channel_tuple: "TaborChannelTuple"): self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._trigger_source = 'BUS' + # TODO: QUESTION: is this right? + self._default_repetition_mode = RepetitionMode("auto_rearm") + def get_repetition_mode(self, program_name: str) -> str: """ Returns the default repetition mode of a certain program @@ -554,10 +552,10 @@ def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) - #TODO: erweitern @property + # TODO: QUESTION: is this right? def supported_repetition_modes(self) -> Set[RepetitionMode]: - return {RepetitionMode.INFINITE} + return {RepetitionMode.INFINITE, RepetitionMode.AUTO_REARM} @with_configuration_guard @with_select @@ -645,7 +643,7 @@ def upload(self, name: str, waveform_to_segment[to_amend] = self._channel_tuple._amend_segments(segments_to_amend) self._channel_tuple._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, - program=tabor_program) + program=tabor_program) # set the default repetionmode for a programm self.set_repetition_mode(program_name=name, repetition_mode=repetition_mode) @@ -677,7 +675,8 @@ def clear(self) -> None: self._channel_tuple.device[SCPI].send_cmd(":TRAC:DEF 1, 192") self._channel_tuple.device[SCPI].send_cmd(":TRAC:SEL 1") self._channel_tuple.device[SCPI].send_cmd(":TRAC:MODE COMB") - self._channel_tuple.device._send_binary_data(pref=":TRAC:DATA", bin_dat=self._channel_tuple._idle_segment.get_as_binary()) + self._channel_tuple.device._send_binary_data(pref=":TRAC:DATA", + bin_dat=self._channel_tuple._idle_segment.get_as_binary()) self._channel_tuple._segment_lengths = 192 * np.ones(1, dtype=np.uint32) self._channel_tuple._segment_capacity = 192 * np.ones(1, dtype=np.uint32) @@ -709,7 +708,7 @@ def programs(self) -> Set[str]: return set(program.name for program in self._channel_tuple._known_programs.keys()) @with_select - def run_current_program(self) -> None: + def run_current_program(self, repetion_mode: Union[str, RepetitionMode]) -> None: """ This method starts running the active program @@ -720,14 +719,24 @@ def run_current_program(self) -> None: # channel tuple is the first channel tuple if (self._channel_tuple.device._channel_tuples[0] == self): if self._channel_tuple._current_program: - repetition_mode = self._channel_tuple._known_programs[ - self._channel_tuple._current_program].program._repetition_mode - if repetition_mode == "infinite": - self._cont_repetition_mode() - self._channel_tuple.device[SCPI].send_cmd(':TRIG', - paranoia_level=self._channel_tuple.internal_paranoia_level) + default_repetition_mode = RepetitionMode(self._channel_tuple._known_programs[ + self._channel_tuple._current_program].program._repetition_mode) + # TODO: QUESTION: Is this right? + cur_repetition_mode = RepetitionMode(repetion_mode) + + if default_repetition_mode != cur_repetition_mode: + self._change_armed_program(name=self._channel_tuple._current_program, + repetion_mode=cur_repetition_mode) + + if RepetitionMode(default_repetition_mode) == RepetitionMode.INFINITE: + self._infinite_repetition_mode() + elif RepetitionMode(default_repetition_mode) == RepetitionMode.AUTO_REARM: + self._auto_rearm_repetition_mode() else: - raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + raise ValueError("{} is no vaild repetition mode".format(default_repetition_mode)) + + self._channel_tuple.device[TaborSCPI].send_cmd(':TRIG', + paranoia_level=self._channel_tuple.internal_paranoia_level) else: raise RuntimeError("No program active") else: @@ -736,20 +745,36 @@ def run_current_program(self) -> None: else: if self._channel_tuple._current_program: - repetition_mode = self._channel_tuple._known_programs[ + default_repetition_mode = self._channel_tuple._known_programs[ self._channel_tuple._current_program].program._repetition_mode - if repetition_mode == "infinite": - self._cont_repetition_mode() - self._channel_tuple.device[SCPI].send_cmd(':TRIG', paranoia_level=self._channel_tuple.internal_paranoia_level) + + if RepetitionMode(default_repetition_mode) == RepetitionMode("infinite"): + self._infinite_repetition_mode() + elif RepetitionMode(default_repetition_mode) == RepetitionMode("auto_rearm"): + self._auto_rearm_repetition_mode() else: - raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + raise ValueError("{} is no vaild repetition mode".format(default_repetition_mode)) + + self._channel_tuple.device[TaborSCPI].send_cmd(':TRIG', + paranoia_level=self._channel_tuple.internal_paranoia_level) else: raise RuntimeError("No program active") @with_select @with_configuration_guard - def _change_armed_program(self, name: Optional[str]) -> None: + def _change_armed_program(self, name: Optional[str], repetition_mode: RepetitionMode = None) -> None: """The armed program of the channel tuple is changed to the program with the name 'name'""" + + if repetition_mode is None: + if name is not None: + print("--->", self._channel_tuple._known_programs[name][1]) + repetition_mode = RepetitionMode(self._channel_tuple._known_programs[name].program._repetition_mode) + else: + repetition_mode = self.default_repetition_mode + + if repetition_mode not in self.supported_repetition_modes: + raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) + if name is None: sequencer_tables = [self._idle_sequence_table] advanced_sequencer_table = [(1, 1, 0)] @@ -777,24 +802,31 @@ def _change_armed_program(self, name: Optional[str]) -> None: assert advanced_sequencer_table[0][0] == 1 sequencer_tables[1].append((1, 1, 0)) - # insert idle sequence in advanced sequence table - advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table + # TODO: QUESTION: Is this right? + if repetition_mode == RepetitionMode.INFINITE: + advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table + elif repetition_mode == RepetitionMode.AUTO_REARM: + advanced_sequencer_table = [(1, 1, 1)] + advanced_sequencer_table while len(advanced_sequencer_table) < self._channel_tuple.device.dev_properties["min_aseq_len"]: advanced_sequencer_table.append((1, 1, 0)) - self._channel_tuple.device[SCPI].send_cmd("SEQ:DEL:ALL", paranoia_level=self._channel_tuple.internal_paranoia_level) + # reset sequencer and advanced sequencer tables to fix bug which occurs when switching between some programs + self._channel_tuple.device[SCPI].send_cmd("SEQ:DEL:ALL", + paranoia_level=self._channel_tuple.internal_paranoia_level) self._channel_tuple._sequencer_tables = [] - self._channel_tuple.device[SCPI].send_cmd("ASEQ:DEL", paranoia_level=self._channel_tuple.internal_paranoia_level) + self._channel_tuple.device[SCPI].send_cmd("ASEQ:DEL", + paranoia_level=self._channel_tuple.internal_paranoia_level) self._channel_tuple._advanced_sequence_table = [] # download all sequence tables for i, sequencer_table in enumerate(sequencer_tables): self._channel_tuple.device[SCPI].send_cmd("SEQ:SEL {}".format(i + 1), - paranoia_level=self._channel_tuple.internal_paranoia_level) + paranoia_level=self._channel_tuple.internal_paranoia_level) self._channel_tuple.device._download_sequencer_table(sequencer_table) self._channel_tuple._sequencer_tables = sequencer_tables - self._channel_tuple.device[SCPI].send_cmd("SEQ:SEL 1", paranoia_level=self._channel_tuple.internal_paranoia_level) + self._channel_tuple.device[SCPI].send_cmd("SEQ:SEL 1", + paranoia_level=self._channel_tuple.internal_paranoia_level) self._channel_tuple.device._download_adv_seq_table(advanced_sequencer_table) self._channel_tuple._advanced_sequence_table = advanced_sequencer_table @@ -819,12 +851,20 @@ def _exit_config_mode(self): self._channel_tuple._exit_config_mode() @with_select - def _cont_repetition_mode(self): - """Changes the run mode of this channel tuple to continous mode""" + def _infinite_repetition_mode(self): + # TODO: change Docstring + """Changes the run mode of this channel tuple to infinite mode""" self._channel_tuple.device[SCPI].send_cmd(f":TRIG:SOUR:ADV EXT") self._channel_tuple.device[SCPI].send_cmd( f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB ARM; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + @with_select + def _auto_rearm_repetition_mode(self): + """Changes the run mode of this channel tuple to repetition mode""" + self._channel_tuple.device[SCPI].send_cmd(f":TRIG:SOUR:ADV EXT") + self._channel_tuple.device[SCPI].send_cmd( + f":INIT:GATE OFF; :INIT:CONT ON; :INIT:CONT:ENAB SELF; :INIT:CONT:ENAB:SOUR {self._trigger_source}") + class TaborVolatileParameters(VolatileParameters): def __init__(self, channel_tuple: "TaborChannelTuple", ): @@ -893,6 +933,7 @@ class TaborChannelTuple(AWGChannelTuple): def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChannel"], marker_channels: Iterable["TaborMarkerChannel"]): super().__init__(idn) + self._device = weakref.ref(device) self._configuration_guard_count = 0 @@ -1219,7 +1260,8 @@ def cleanup(self) -> None: chunk_size = 10 for chunk_start in range(new_end, old_end, chunk_size): self.device[SCPI].send_cmd("; ".join("TRAC:DEL {}".format(i + 1) - for i in range(chunk_start, min(chunk_start + chunk_size, old_end)))) + for i in + range(chunk_start, min(chunk_start + chunk_size, old_end)))) except Exception as e: raise TaborUndefinedState("Error during cleanup. Device is in undefined state.", device=self) from e @@ -1299,8 +1341,7 @@ def _exit_config_mode(self) -> None: class TaborActivatableMarkerChannels(ActivatableChannels): def __init__(self, marker_channel: "TaborMarkerChannel"): - super().__init__() - self._parent = weakref.ref(marker_channel) + super().__init__(marker_channel) @property def enabled(self) -> bool: @@ -1308,28 +1349,28 @@ def enabled(self) -> bool: Returns the the state a marker channel has at the moment. A channel is either activated or deactivated True stands for activated and false for deactivated """ - return self._parent().device[SCPI].send_query(":MARK:STAT ?") == "ON" + return self._channel.device[SCPI].send_query(":MARK:STAT ?") == "ON" @with_select def enable(self): """Enables the output of a certain marker channel""" command_string = "SOUR:MARK:SOUR USER; :SOUR:MARK:STAT ON" command_string = command_string.format( - channel=self._parent().channel_tuple.channels[0].idn, - marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device[SCPI].send_cmd(command_string) + channel=self._channel.channel_tuple.channels[0].idn, + marker=self._channel.channel_tuple.marker_channels.index(self._channel) + 1) + self._channel.device[SCPI].send_cmd(command_string) @with_select def disable(self): """Disable the output of a certain marker channel""" command_string = ":SOUR:MARK:SOUR USER; :SOUR:MARK:STAT OFF" command_string = command_string.format( - channel=self._parent().channel_tuple.channels[0].idn, - marker=self._parent().channel_tuple.marker_channels.index(self._parent()) + 1) - self._parent().device[SCPI].send_cmd(command_string) + channel=self._channel.channel_tuple.channels[0].idn, + marker=self._channel.channel_tuple.marker_channels.index(self._channel) + 1) + self._channel.device[SCPI].send_cmd(command_string) def _select(self) -> None: - self._parent()._select() + self._channel._select() # Implementation @@ -1367,6 +1408,7 @@ def _select(self) -> None: self.device.channels[int((self.idn - 1) / 2)]._select() self.device[SCPI].send_cmd(":SOUR:MARK:SEL {marker}".format(marker=(((self.idn - 1) % 2) + 1))) + ######################################################################################################################## class TaborUndefinedState(TaborException): From 1381df0fad39469b0c52d39063ee27cf2c0f6622 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Tue, 1 Jun 2021 13:50:33 +0200 Subject: [PATCH 103/107] tried to fix the test --- .../tabor_new_driver_simulator_based_tests.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/hardware/tabor_new_driver_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py index 3f2200245..5e5ba415e 100644 --- a/tests/hardware/tabor_new_driver_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -9,8 +9,9 @@ import numpy as np from qupulse._program.tabor import TableDescription, TableEntry -from qupulse.hardware.feature_awg.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, VolatileParameters -from qupulse.hardware.feature_awg.tabor import TaborDevice, TaborSegment +from qupulse.hardware.feature_awg.features import DeviceControl, VoltageRange, ProgramManagement, SCPI, \ + VolatileParameters, RepetitionMode +from qupulse.hardware.feature_awg.tabor import TaborDevice, TaborSegment, TaborProgramMemory from qupulse.utils.types import TimeType @@ -201,12 +202,20 @@ def update_volatile_parameters(parameters): (0, 1): TableDescription(repetition_count=50, element_id=1, jump_flag=0)} return modifications + # TODO: QUESTION: is this change okay? + @property + def _repetition_mode(self): + return RepetitionMode.AUTO_REARM + + + markers = (None, None) channels = (1, 2) waveform_mode = mode - self.channel_pair._known_programs['dummy_program'] = (waveform_to_segment_index, DummyProgram) + #TODO: QUESTION: is this change okay? + self.channel_pair._known_programs['dummy_program'] = TaborProgramMemory(waveform_to_segment_index, DummyProgram) self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') def test_read_waveforms(self): From 60d7db701c1edf7f3f9074fa030bb976e7de7fdc Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 11 Jun 2021 10:40:53 +0200 Subject: [PATCH 104/107] revise repetition mode check in set_repetition_mode and run_current_program --- qupulse/hardware/feature_awg/tabor.py | 31 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 6c6c90a79..6678b2c24 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -144,9 +144,9 @@ def synchronize_channels(self, group_size: int) -> None: self._device[SCPI].send_cmd(":INST:COUP:STAT OFF") elif group_size == 4: self._device._channel_tuples = [TaborChannelTuple(1, - self._device, - self._device.channels, - self._device.marker_channels)] + self._device, + self._device.channels, + self._device.marker_channels)] self._device[SCPI].send_cmd(":INST:COUP:STAT ON") else: raise TaborException("Invalid group size") @@ -537,7 +537,7 @@ def get_repetition_mode(self, program_name: str) -> str: """ return self._channel_tuple._known_programs[program_name].program._repetition_mode - def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: + def set_repetition_mode(self, program_name: str, repetition_mode: Union[str, RepetitionMode]) -> None: """ Changes the default repetition mode of a certain program @@ -547,7 +547,9 @@ def set_repetition_mode(self, program_name: str, repetition_mode: str) -> None: Throws: ValueError: this Exception is thrown when an invalid repetition mode is given """ - if repetition_mode == "infinite" or repetition_mode == "once": + cur_repetition_mode = RepetitionMode(repetition_mode) + + if cur_repetition_mode in self.supported_repetition_modes: self._channel_tuple._known_programs[program_name].program._repetition_mode = repetition_mode else: raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) @@ -708,12 +710,17 @@ def programs(self) -> Set[str]: return set(program.name for program in self._channel_tuple._known_programs.keys()) @with_select - def run_current_program(self, repetion_mode: Union[str, RepetitionMode]) -> None: + def run_current_program(self, repetition_mode: Union[str, RepetitionMode] = None) -> None: """ This method starts running the active program + Args: + repetition_mode (Union[str, RepetitionMode]): The repetition mode with which the program is executed. + If the repetition mode is none, the default repetition mode of + program is used. + Throws: - RuntimeError: This exception is thrown if there is no active program for this device + RuntimeError: This exception is thrown if there is no active program for this device """ if (self._channel_tuple.device._is_coupled()): # channel tuple is the first channel tuple @@ -722,11 +729,12 @@ def run_current_program(self, repetion_mode: Union[str, RepetitionMode]) -> None default_repetition_mode = RepetitionMode(self._channel_tuple._known_programs[ self._channel_tuple._current_program].program._repetition_mode) # TODO: QUESTION: Is this right? - cur_repetition_mode = RepetitionMode(repetion_mode) + if repetition_mode is not None: + cur_repetition_mode = RepetitionMode(repetition_mode) - if default_repetition_mode != cur_repetition_mode: - self._change_armed_program(name=self._channel_tuple._current_program, - repetion_mode=cur_repetition_mode) + if default_repetition_mode != cur_repetition_mode: + self._change_armed_program(name=self._channel_tuple._current_program, + repetion_mode=cur_repetition_mode) if RepetitionMode(default_repetition_mode) == RepetitionMode.INFINITE: self._infinite_repetition_mode() @@ -767,7 +775,6 @@ def _change_armed_program(self, name: Optional[str], repetition_mode: Repetition if repetition_mode is None: if name is not None: - print("--->", self._channel_tuple._known_programs[name][1]) repetition_mode = RepetitionMode(self._channel_tuple._known_programs[name].program._repetition_mode) else: repetition_mode = self.default_repetition_mode From 5c85c91153e45d49fd12d647015f21233b9eff7a Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 11 Jun 2021 10:41:45 +0200 Subject: [PATCH 105/107] fixed a problem with weakref for the channel tuple --- qupulse/hardware/feature_awg/tabor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 6678b2c24..526e81522 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -941,7 +941,10 @@ def __init__(self, idn: int, device: TaborDevice, channels: Iterable["TaborChann marker_channels: Iterable["TaborMarkerChannel"]): super().__init__(idn) - self._device = weakref.ref(device) + if isinstance(device, weakref.ProxyType): + self._device = device + else: + self._device = weakref.ref(device) self._configuration_guard_count = 0 self._is_in_config_mode = False @@ -1005,7 +1008,7 @@ def _select(self) -> None: @property def device(self) -> TaborDevice: """Returns the device that the channel tuple belongs to""" - return self._device() + return self._device @property def channels(self) -> Collection["TaborChannel"]: From 9b741a5a1e95f66a4a3e3eb6b2d89b2da7017ea8 Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 11 Jun 2021 10:50:35 +0200 Subject: [PATCH 106/107] changed the static DummyProgramm to a class so it can have attributes --- .../tabor_new_driver_simulator_based_tests.py | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tests/hardware/tabor_new_driver_simulator_based_tests.py b/tests/hardware/tabor_new_driver_simulator_based_tests.py index 5e5ba415e..ecbd9b445 100644 --- a/tests/hardware/tabor_new_driver_simulator_based_tests.py +++ b/tests/hardware/tabor_new_driver_simulator_based_tests.py @@ -165,10 +165,10 @@ class TaborMemoryReadTests(TaborSimulatorBasedTest): def setUp(self): super().setUp() - ramp_up = np.linspace(0, 2**14-1, num=192, dtype=np.uint16) + ramp_up = np.linspace(0, 2 ** 14 - 1, num=192, dtype=np.uint16) ramp_down = ramp_up[::-1] - zero = np.ones(192, dtype=np.uint16) * 2**13 - sine = ((np.sin(np.linspace(0, 2*np.pi, 192+64)) + 1) / 2 * (2**14 - 1)).astype(np.uint16) + zero = np.ones(192, dtype=np.uint16) * 2 ** 13 + sine = ((np.sin(np.linspace(0, 2 * np.pi, 192 + 64)) + 1) / 2 * (2 ** 14 - 1)).astype(np.uint16) self.segments = [TaborSegment.from_sampled(ramp_up, ramp_up, None, None), TaborSegment.from_sampled(ramp_down, zero, None, None), @@ -188,34 +188,28 @@ def setUp(self): def arm_program(self, sequencer_tables, advanced_sequencer_table, mode, waveform_to_segment_index): class DummyProgram: - @staticmethod - def get_sequencer_tables(): + def __init__(self): + self._repetition_mode = RepetitionMode.INFINITE + + def get_sequencer_tables(self): return sequencer_tables - @staticmethod - def get_advanced_sequencer_table(): + def get_advanced_sequencer_table(self): return advanced_sequencer_table - @staticmethod - def update_volatile_parameters(parameters): + def update_volatile_parameters(self, parameters): modifications = {1: TableEntry(repetition_count=5, element_number=2, jump_flag=0), (0, 1): TableDescription(repetition_count=50, element_id=1, jump_flag=0)} return modifications - # TODO: QUESTION: is this change okay? - @property - def _repetition_mode(self): - return RepetitionMode.AUTO_REARM - - - markers = (None, None) channels = (1, 2) waveform_mode = mode - #TODO: QUESTION: is this change okay? - self.channel_pair._known_programs['dummy_program'] = TaborProgramMemory(waveform_to_segment_index, DummyProgram) + # TODO: QUESTION: is this change okay? + self.channel_pair._known_programs['dummy_program'] = TaborProgramMemory(waveform_to_segment_index, + DummyProgram()) self.channel_pair[ProgramManagement]._change_armed_program('dummy_program') def test_read_waveforms(self): @@ -242,9 +236,11 @@ def test_read_sequence_tables(self): sequence_tables = self.channel_pair.read_sequence_tables() - actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index+2, jump) - for rep, index, jump in table] - for table in self.sequence_tables_raw] + actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) + for rep, index, jump in + table] + for table in + self.sequence_tables_raw] expected = list(tuple(np.asarray(d) for d in zip(*table)) @@ -270,8 +266,10 @@ def test_set_volatile_parameter(self): para = {'a': 5} actual_sequence_tables = [self.channel_pair[ProgramManagement]._idle_sequence_table] + [[(rep, index + 2, jump) - for rep, index, jump in table] - for table in self.sequence_tables_raw] + for rep, index, jump in + table] + for table in + self.sequence_tables_raw] actual_advanced_table = [(1, 1, 0)] + [(rep, idx + 1, jmp) for rep, idx, jmp in self.advanced_sequence_table] From f3bfee4150d7f4e2ab29e9e2e6792e70705eaa9e Mon Sep 17 00:00:00 2001 From: "b.papajewski" <bFZJp2001> Date: Fri, 11 Jun 2021 11:13:05 +0200 Subject: [PATCH 107/107] commented the todos --- qupulse/hardware/feature_awg/tabor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qupulse/hardware/feature_awg/tabor.py b/qupulse/hardware/feature_awg/tabor.py index 526e81522..fcc20e868 100644 --- a/qupulse/hardware/feature_awg/tabor.py +++ b/qupulse/hardware/feature_awg/tabor.py @@ -526,7 +526,7 @@ def __init__(self, channel_tuple: "TaborChannelTuple"): self._idle_sequence_table = [(1, 1, 0), (1, 1, 0), (1, 1, 0)] self._trigger_source = 'BUS' - # TODO: QUESTION: is this right? + # TODO: QUESTION: is this right? - Is auto_ream the default repetition mode self._default_repetition_mode = RepetitionMode("auto_rearm") def get_repetition_mode(self, program_name: str) -> str: @@ -555,7 +555,7 @@ def set_repetition_mode(self, program_name: str, repetition_mode: Union[str, Rep raise ValueError("{} is no vaild repetition mode".format(repetition_mode)) @property - # TODO: QUESTION: is this right? + # TODO: QUESTION: is this right? - Are these the two run modes the are supported? def supported_repetition_modes(self) -> Set[RepetitionMode]: return {RepetitionMode.INFINITE, RepetitionMode.AUTO_REARM} @@ -728,7 +728,7 @@ def run_current_program(self, repetition_mode: Union[str, RepetitionMode] = None if self._channel_tuple._current_program: default_repetition_mode = RepetitionMode(self._channel_tuple._known_programs[ self._channel_tuple._current_program].program._repetition_mode) - # TODO: QUESTION: Is this right? + if repetition_mode is not None: cur_repetition_mode = RepetitionMode(repetition_mode) @@ -809,7 +809,7 @@ def _change_armed_program(self, name: Optional[str], repetition_mode: Repetition assert advanced_sequencer_table[0][0] == 1 sequencer_tables[1].append((1, 1, 0)) - # TODO: QUESTION: Is this right? + # TODO: QUESTION: Is this right? - are the things that are attached at the end correct? if repetition_mode == RepetitionMode.INFINITE: advanced_sequencer_table = [(1, 1, 0)] + advanced_sequencer_table elif repetition_mode == RepetitionMode.AUTO_REARM: