|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
|
|
3
|
|
|
""" |
|
4
|
|
|
This file contains the Qudi data object classes needed for pulse sequence generation. |
|
5
|
|
|
|
|
6
|
|
|
Qudi is free software: you can redistribute it and/or modify |
|
7
|
|
|
it under the terms of the GNU General Public License as published by |
|
8
|
|
|
the Free Software Foundation, either version 3 of the License, or |
|
9
|
|
|
(at your option) any later version. |
|
10
|
|
|
|
|
11
|
|
|
Qudi is distributed in the hope that it will be useful, |
|
12
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14
|
|
|
GNU General Public License for more details. |
|
15
|
|
|
|
|
16
|
|
|
You should have received a copy of the GNU General Public License |
|
17
|
|
|
along with Qudi. If not, see <http://www.gnu.org/licenses/>. |
|
18
|
|
|
|
|
19
|
|
|
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the |
|
20
|
|
|
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/> |
|
21
|
|
|
""" |
|
22
|
|
|
|
|
23
|
|
|
import copy |
|
24
|
|
|
import os |
|
25
|
|
|
import sys |
|
26
|
|
|
import inspect |
|
27
|
|
|
import importlib |
|
28
|
|
|
from collections import OrderedDict |
|
29
|
|
|
|
|
30
|
|
|
from logic.pulsed.sampling_functions import SamplingFunctions |
|
31
|
|
|
from core.util.modules import get_main_dir |
|
32
|
|
|
|
|
33
|
|
|
|
|
34
|
|
|
class PulseBlockElement(object): |
|
35
|
|
|
""" |
|
36
|
|
|
Object representing a single atomic element in a pulse block. |
|
37
|
|
|
|
|
38
|
|
|
This class can build waiting times, sine waves, etc. The pulse block may |
|
39
|
|
|
contain many Pulse_Block_Element Objects. These objects can be displayed in |
|
40
|
|
|
a GUI as single rows of a Pulse_Block. |
|
41
|
|
|
""" |
|
42
|
|
|
def __init__(self, init_length_s=10e-9, increment_s=0, pulse_function=None, digital_high=None): |
|
43
|
|
|
""" |
|
44
|
|
|
The constructor for a Pulse_Block_Element needs to have: |
|
45
|
|
|
|
|
46
|
|
|
@param float init_length_s: an initial length of the element, this parameters should not be |
|
47
|
|
|
zero but must have a finite value. |
|
48
|
|
|
@param float increment_s: the number which will be incremented during each repetition of |
|
49
|
|
|
this element. |
|
50
|
|
|
@param dict pulse_function: dictionary with keys being the qudi analog channel string |
|
51
|
|
|
descriptors ('a_ch1', 'a_ch2' etc.) and the corresponding |
|
52
|
|
|
objects being instances of the mathematical function objects |
|
53
|
|
|
provided by SamplingFunctions class. |
|
54
|
|
|
@param dict digital_high: dictionary with keys being the qudi digital channel string |
|
55
|
|
|
descriptors ('d_ch1', 'd_ch2' etc.) and the corresponding objects |
|
56
|
|
|
being boolean values describing if the channel should be logical |
|
57
|
|
|
low (False) or high (True). |
|
58
|
|
|
For 3 digital channel it may look like: |
|
59
|
|
|
{'d_ch1': True, 'd_ch2': False, 'd_ch5': False} |
|
60
|
|
|
""" |
|
61
|
|
|
# FIXME: Sanity checks need to be implemented here |
|
62
|
|
|
self.init_length_s = init_length_s |
|
63
|
|
|
self.increment_s = increment_s |
|
64
|
|
|
if pulse_function is None: |
|
65
|
|
|
self.pulse_function = OrderedDict() |
|
66
|
|
|
else: |
|
67
|
|
|
self.pulse_function = pulse_function |
|
68
|
|
|
if digital_high is None: |
|
69
|
|
|
self.digital_high = OrderedDict() |
|
70
|
|
|
else: |
|
71
|
|
|
self.digital_high = digital_high |
|
72
|
|
|
|
|
73
|
|
|
# determine set of used digital and analog channels |
|
74
|
|
|
self.analog_channels = set(self.pulse_function) |
|
75
|
|
|
self.digital_channels = set(self.digital_high) |
|
76
|
|
|
self.channel_set = self.analog_channels.union(self.digital_channels) |
|
77
|
|
|
|
|
78
|
|
|
def __repr__(self): |
|
79
|
|
|
repr_str = 'PulseBlockElement(init_length_s={0}, increment_s={1}, pulse_function='.format( |
|
80
|
|
|
self.init_length_s, self.increment_s) |
|
81
|
|
|
repr_str += '{' |
|
82
|
|
|
for ind, (channel, sampling_func) in enumerate(self.pulse_function.items()): |
|
83
|
|
|
repr_str += '{0}: {1}'.format(channel, repr(sampling_func)) |
|
84
|
|
|
if ind < len(self.pulse_function) - 1: |
|
85
|
|
|
repr_str += ', ' |
|
86
|
|
|
repr_str += '}, ' |
|
87
|
|
|
repr_str += 'digital_high={0})'.format(dict(self.digital_high)) |
|
88
|
|
|
return repr_str |
|
89
|
|
|
|
|
90
|
|
|
def __str__(self): |
|
91
|
|
|
pulse_func_dict = {chnl: type(func).__name__ for chnl, func in self.pulse_function.items()} |
|
92
|
|
|
return_str = 'PulseBlockElement\n\tinitial length: {0}s\n\tlength increment: {1}s\n\t' \ |
|
93
|
|
|
'analog channels: {2}\n\tdigital channels: {3}'.format(self.init_length_s, |
|
94
|
|
|
self.increment_s, |
|
95
|
|
|
pulse_func_dict, |
|
96
|
|
|
dict(self.digital_high)) |
|
97
|
|
|
return return_str |
|
98
|
|
|
|
|
99
|
|
|
def get_dict_representation(self): |
|
100
|
|
|
dict_repr = dict() |
|
101
|
|
|
dict_repr['init_length_s'] = self.init_length_s |
|
102
|
|
|
dict_repr['increment_s'] = self.increment_s |
|
103
|
|
|
dict_repr['digital_high'] = self.digital_high |
|
104
|
|
|
dict_repr['pulse_function'] = dict() |
|
105
|
|
|
for chnl, func in self.pulse_function.items(): |
|
106
|
|
|
dict_repr['pulse_function'][chnl] = func.get_dict_representation() |
|
107
|
|
|
return dict_repr |
|
108
|
|
|
|
|
109
|
|
|
@staticmethod |
|
110
|
|
|
def element_from_dict(element_dict): |
|
111
|
|
|
for chnl, sample_dict in element_dict['pulse_function'].items(): |
|
112
|
|
|
sf_class = getattr(SamplingFunctions, sample_dict['name']) |
|
113
|
|
|
element_dict['pulse_function'][chnl] = sf_class(**sample_dict['params']) |
|
114
|
|
|
return PulseBlockElement(**element_dict) |
|
115
|
|
|
|
|
116
|
|
|
|
|
117
|
|
|
class PulseBlock(object): |
|
118
|
|
|
""" |
|
119
|
|
|
Collection of Pulse_Block_Elements which is called a Pulse_Block. |
|
120
|
|
|
""" |
|
121
|
|
|
def __init__(self, name, element_list=None): |
|
122
|
|
|
""" |
|
123
|
|
|
The constructor for a Pulse_Block needs to have: |
|
124
|
|
|
|
|
125
|
|
|
@param str name: chosen name for the Pulse_Block |
|
126
|
|
|
@param list element_list: which contains the Pulse_Block_Element Objects forming a |
|
127
|
|
|
Pulse_Block, e.g. [Pulse_Block_Element, Pulse_Block_Element, ...] |
|
128
|
|
|
""" |
|
129
|
|
|
self.name = name |
|
130
|
|
|
self.element_list = list() if element_list is None else element_list |
|
131
|
|
|
self.init_length_s = 0.0 |
|
132
|
|
|
self.increment_s = 0.0 |
|
133
|
|
|
self.analog_channels = set() |
|
134
|
|
|
self.digital_channels = set() |
|
135
|
|
|
self.channel_set = set() |
|
136
|
|
|
self.refresh_parameters() |
|
137
|
|
|
return |
|
138
|
|
|
|
|
139
|
|
|
def __repr__(self): |
|
140
|
|
|
elem_repr_list = [repr(elem) for elem in self.element_list] |
|
141
|
|
|
repr_str = 'PulseBlock(name={0}, element_list={1})'.format(self.name, elem_repr_list) |
|
142
|
|
|
return repr_str.replace('"', '') |
|
143
|
|
|
|
|
144
|
|
|
def __str__(self): |
|
145
|
|
|
return_str = 'PulseBlock "{0}"\n\tnumber of elements: {1}\n\t'.format( |
|
146
|
|
|
self.name, len(self.element_list)) |
|
147
|
|
|
return_str += 'initial length: {0}s\n\tlength increment: {1}s\n\t'.format( |
|
148
|
|
|
self.init_length_s, self.increment_s) |
|
149
|
|
|
return_str += 'active analog channels: {0}\n\tactive digital channels: {1}'.format( |
|
150
|
|
|
sorted(self.analog_channels), sorted(self.digital_channels)) |
|
151
|
|
|
return return_str |
|
152
|
|
|
|
|
153
|
|
|
def __len__(self): |
|
154
|
|
|
return len(self.element_list) |
|
155
|
|
|
|
|
156
|
|
|
def __getitem__(self, key): |
|
157
|
|
|
if not isinstance(key, (slice, int)): |
|
158
|
|
|
raise TypeError('PulseBlock indices must be int or slice, not {0}'.format(type(key))) |
|
159
|
|
|
return self.element_list[key] |
|
160
|
|
|
|
|
161
|
|
|
def __setitem__(self, key, value): |
|
162
|
|
|
if isinstance(key, int): |
|
163
|
|
|
if not isinstance(value, PulseBlockElement): |
|
164
|
|
|
raise TypeError('PulseBlock element list entries must be of type PulseBlockElement,' |
|
165
|
|
|
' not {0}'.format(type(value))) |
|
166
|
|
|
if not self.channel_set: |
|
167
|
|
|
self.channel_set = value.channel_set.copy() |
|
168
|
|
|
self.analog_channels = {chnl for chnl in self.channel_set if chnl.startswith('a')} |
|
169
|
|
|
self.digital_channels = {chnl for chnl in self.channel_set if chnl.startswith('d')} |
|
170
|
|
|
elif value.channel_set != self.channel_set: |
|
171
|
|
|
raise ValueError('Usage of different sets of analog and digital channels in the ' |
|
172
|
|
|
'same PulseBlock is prohibited. Used channel sets are:\n{0}\n{1}' |
|
173
|
|
|
''.format(self.channel_set, value.channel_set)) |
|
174
|
|
|
|
|
175
|
|
|
self.init_length_s -= self.element_list[key].init_length_s |
|
176
|
|
|
self.increment_s -= self.element_list[key].increment_s |
|
177
|
|
|
self.init_length_s += value.init_length_s |
|
178
|
|
|
self.increment_s += value.increment_s |
|
179
|
|
|
elif isinstance(key, slice): |
|
180
|
|
|
add_length = 0 |
|
181
|
|
|
add_increment = 0 |
|
182
|
|
|
for element in value: |
|
183
|
|
|
if not isinstance(element, PulseBlockElement): |
|
184
|
|
|
raise TypeError('PulseBlock element list entries must be of type ' |
|
185
|
|
|
'PulseBlockElement, not {0}'.format(type(value))) |
|
186
|
|
|
if not self.channel_set: |
|
187
|
|
|
self.channel_set = element.channel_set.copy() |
|
188
|
|
|
self.analog_channels = {chnl for chnl in self.channel_set if |
|
189
|
|
|
chnl.startswith('a')} |
|
190
|
|
|
self.digital_channels = {chnl for chnl in self.channel_set if |
|
191
|
|
|
chnl.startswith('d')} |
|
192
|
|
|
elif element.channel_set != self.channel_set: |
|
193
|
|
|
raise ValueError( |
|
194
|
|
|
'Usage of different sets of analog and digital channels in the ' |
|
195
|
|
|
'same PulseBlock is prohibited. Used channel sets are:\n{0}\n{1}' |
|
196
|
|
|
''.format(self.channel_set, element.channel_set)) |
|
197
|
|
|
|
|
198
|
|
|
add_length += element.init_length_s |
|
199
|
|
|
add_increment += element.increment_s |
|
200
|
|
|
|
|
201
|
|
|
for element in self.element_list[key]: |
|
202
|
|
|
self.init_length_s -= element.init_length_s |
|
203
|
|
|
self.increment_s -= element.increment_s |
|
204
|
|
|
|
|
205
|
|
|
self.init_length_s += add_length |
|
206
|
|
|
self.increment_s += add_increment |
|
207
|
|
|
else: |
|
208
|
|
|
raise TypeError('PulseBlock indices must be int or slice, not {0}'.format(type(key))) |
|
209
|
|
|
self.element_list[key] = copy.deepcopy(value) |
|
210
|
|
|
return |
|
211
|
|
|
|
|
212
|
|
|
def __delitem__(self, key): |
|
213
|
|
|
if not isinstance(key, (slice, int)): |
|
214
|
|
|
raise TypeError('PulseBlock indices must be int or slice, not {0}'.format(type(key))) |
|
215
|
|
|
|
|
216
|
|
|
if isinstance(key, int): |
|
217
|
|
|
items_to_delete = [self.element_list[key]] |
|
218
|
|
|
else: |
|
219
|
|
|
items_to_delete = self.element_list[key] |
|
220
|
|
|
|
|
221
|
|
|
for element in items_to_delete: |
|
222
|
|
|
self.init_length_s -= element.init_length_s |
|
223
|
|
|
self.increment_s -= element.increment_s |
|
224
|
|
|
del self.element_list[key] |
|
225
|
|
|
if len(self.element_list) == 0: |
|
226
|
|
|
self.init_length_s = 0.0 |
|
227
|
|
|
self.increment_s = 0.0 |
|
228
|
|
|
return |
|
229
|
|
|
|
|
230
|
|
|
def refresh_parameters(self): |
|
231
|
|
|
""" Initialize the parameters which describe this Pulse_Block object. |
|
232
|
|
|
|
|
233
|
|
|
The information is gained from all the Pulse_Block_Element objects, |
|
234
|
|
|
which are attached in the element_list. |
|
235
|
|
|
""" |
|
236
|
|
|
# the Pulse_Block parameters |
|
237
|
|
|
self.init_length_s = 0.0 |
|
238
|
|
|
self.increment_s = 0.0 |
|
239
|
|
|
self.channel_set = set() |
|
240
|
|
|
|
|
241
|
|
|
for elem in self.element_list: |
|
242
|
|
|
self.init_length_s += elem.init_length_s |
|
243
|
|
|
self.increment_s += elem.increment_s |
|
244
|
|
|
|
|
245
|
|
|
if not self.channel_set: |
|
246
|
|
|
self.channel_set = elem.channel_set |
|
247
|
|
|
elif self.channel_set != elem.channel_set: |
|
248
|
|
|
raise ValueError('Usage of different sets of analog and digital channels in the ' |
|
249
|
|
|
'same PulseBlock is prohibited.\nPulseBlock creation failed!\n' |
|
250
|
|
|
'Used channel sets are:\n{0}\n{1}'.format(self.channel_set, |
|
251
|
|
|
elem.channel_set)) |
|
252
|
|
|
break |
|
253
|
|
|
self.analog_channels = {chnl for chnl in self.channel_set if chnl.startswith('a')} |
|
254
|
|
|
self.digital_channels = {chnl for chnl in self.channel_set if chnl.startswith('d')} |
|
255
|
|
|
return |
|
256
|
|
|
|
|
257
|
|
|
def pop(self, position=None): |
|
258
|
|
|
if len(self.element_list) == 0: |
|
259
|
|
|
raise IndexError('pop from empty PulseBlock') |
|
260
|
|
|
|
|
261
|
|
|
if position is None: |
|
262
|
|
|
self.init_length_s -= self.element_list[-1].init_length_s |
|
263
|
|
|
self.increment_s -= self.element_list[-1].increment_s |
|
264
|
|
|
return self.element_list.pop() |
|
265
|
|
|
|
|
266
|
|
|
if not isinstance(position, int): |
|
267
|
|
|
raise TypeError('PulseBlock.pop position argument expects integer, not {0}' |
|
268
|
|
|
''.format(type(position))) |
|
269
|
|
|
|
|
270
|
|
|
if position < 0: |
|
271
|
|
|
position = len(self.element_list) + position |
|
272
|
|
|
|
|
273
|
|
|
if len(self.element_list) <= position or position < 0: |
|
274
|
|
|
raise IndexError('PulseBlock element list index out of range') |
|
275
|
|
|
|
|
276
|
|
|
self.init_length_s -= self.element_list[position].init_length_s |
|
277
|
|
|
self.increment_s -= self.element_list[position].increment_s |
|
278
|
|
|
return self.element_list.pop(position) |
|
279
|
|
|
|
|
280
|
|
|
def insert(self, position, element): |
|
281
|
|
|
""" Insert a PulseBlockElement at the given position. The old element at this position and |
|
282
|
|
|
all consecutive elements after that will be shifted to higher indices. |
|
283
|
|
|
|
|
284
|
|
|
@param int position: position in the element list |
|
285
|
|
|
@param PulseBlockElement element: PulseBlockElement instance |
|
286
|
|
|
""" |
|
287
|
|
|
if not isinstance(element, PulseBlockElement): |
|
288
|
|
|
raise ValueError('PulseBlock elements must be of type PulseBlockElement, not {0}' |
|
289
|
|
|
''.format(type(element))) |
|
290
|
|
|
|
|
291
|
|
|
if position < 0: |
|
292
|
|
|
position = len(self.element_list) + position |
|
293
|
|
|
|
|
294
|
|
|
if len(self.element_list) < position or position < 0: |
|
295
|
|
|
raise IndexError('PulseBlock element list index out of range') |
|
296
|
|
|
|
|
297
|
|
|
if not self.channel_set: |
|
298
|
|
|
self.channel_set = element.channel_set.copy() |
|
299
|
|
|
self.analog_channels = {chnl for chnl in self.channel_set if chnl.startswith('a')} |
|
300
|
|
|
self.digital_channels = {chnl for chnl in self.channel_set if chnl.startswith('d')} |
|
301
|
|
|
elif element.channel_set != self.channel_set: |
|
302
|
|
|
raise ValueError('Usage of different sets of analog and digital channels in the ' |
|
303
|
|
|
'same PulseBlock is prohibited. Used channel sets are:\n{0}\n{1}' |
|
304
|
|
|
''.format(self.channel_set, element.channel_set)) |
|
305
|
|
|
|
|
306
|
|
|
self.init_length_s += element.init_length_s |
|
307
|
|
|
self.increment_s += element.increment_s |
|
308
|
|
|
|
|
309
|
|
|
self.element_list.insert(position, copy.deepcopy(element)) |
|
310
|
|
|
return |
|
311
|
|
|
|
|
312
|
|
|
def append(self, element): |
|
313
|
|
|
""" |
|
314
|
|
|
""" |
|
315
|
|
|
self.insert(position=len(self.element_list), element=element) |
|
316
|
|
|
return |
|
317
|
|
|
|
|
318
|
|
|
def extend(self, iterable): |
|
319
|
|
|
for element in iterable: |
|
320
|
|
|
self.append(element=element) |
|
321
|
|
|
return |
|
322
|
|
|
|
|
323
|
|
|
def clear(self): |
|
324
|
|
|
del self.element_list[:] |
|
325
|
|
|
self.init_length_s = 0.0 |
|
326
|
|
|
self.increment_s = 0.0 |
|
327
|
|
|
self.analog_channels = set() |
|
328
|
|
|
self.digital_channels = set() |
|
329
|
|
|
self.channel_set = set() |
|
330
|
|
|
return |
|
331
|
|
|
|
|
332
|
|
|
def reverse(self): |
|
333
|
|
|
self.element_list.reverse() |
|
334
|
|
|
return |
|
335
|
|
|
|
|
336
|
|
|
def get_dict_representation(self): |
|
337
|
|
|
dict_repr = dict() |
|
338
|
|
|
dict_repr['name'] = self.name |
|
339
|
|
|
dict_repr['element_list'] = list() |
|
340
|
|
|
for element in self.element_list: |
|
341
|
|
|
dict_repr['element_list'].append(element.get_dict_representation()) |
|
342
|
|
|
return dict_repr |
|
343
|
|
|
|
|
344
|
|
|
@staticmethod |
|
345
|
|
|
def block_from_dict(block_dict): |
|
346
|
|
|
for ii, element_dict in enumerate(block_dict['element_list']): |
|
347
|
|
|
block_dict['element_list'][ii] = PulseBlockElement.element_from_dict(element_dict) |
|
348
|
|
|
return PulseBlock(**block_dict) |
|
349
|
|
|
|
|
350
|
|
|
|
|
351
|
|
|
class PulseBlockEnsemble(object): |
|
352
|
|
|
""" |
|
353
|
|
|
Represents a collection of PulseBlock objects which is called a PulseBlockEnsemble. |
|
354
|
|
|
|
|
355
|
|
|
This object is used as a construction plan to create one sampled file. |
|
356
|
|
|
""" |
|
357
|
|
|
def __init__(self, name, block_list=None, rotating_frame=True): |
|
358
|
|
|
""" |
|
359
|
|
|
The constructor for a Pulse_Block_Ensemble needs to have: |
|
360
|
|
|
|
|
361
|
|
|
@param str name: chosen name for the PulseBlockEnsemble |
|
362
|
|
|
@param list block_list: contains the PulseBlock names with their number of repetitions, |
|
363
|
|
|
e.g. [(name, repetitions), (name, repetitions), ...]) |
|
364
|
|
|
@param bool rotating_frame: indicates whether the phase should be preserved for all the |
|
365
|
|
|
functions. |
|
366
|
|
|
""" |
|
367
|
|
|
# FIXME: Sanity checking needed here |
|
368
|
|
|
self.name = name |
|
369
|
|
|
self.rotating_frame = rotating_frame |
|
370
|
|
|
if isinstance(block_list, list): |
|
371
|
|
|
self.block_list = block_list |
|
372
|
|
|
else: |
|
373
|
|
|
self.block_list = list() |
|
374
|
|
|
|
|
375
|
|
|
# Dictionary container to store information related to the actually sampled |
|
376
|
|
|
# Waveform like pulser settings used during sampling (sample_rate, activation_config etc.) |
|
377
|
|
|
# and additional information about the discretization of the waveform (timebin positions of |
|
378
|
|
|
# the PulseBlockElement transitions etc.) as well as the names of the created waveforms. |
|
379
|
|
|
# This container will be populated during sampling and will be emptied upon deletion of the |
|
380
|
|
|
# corresponding waveforms from the pulse generator |
|
381
|
|
|
self.sampling_information = dict() |
|
382
|
|
|
# Dictionary container to store additional information about for measurement settings |
|
383
|
|
|
# (ignore_lasers, controlled_variable, alternating etc.). |
|
384
|
|
|
# This container needs to be populated by the script creating the PulseBlockEnsemble |
|
385
|
|
|
# before saving it. (e.g. in generate methods in PulsedObjectGenerator class) |
|
386
|
|
|
self.measurement_information = dict() |
|
387
|
|
|
return |
|
388
|
|
|
|
|
389
|
|
|
def __repr__(self): |
|
390
|
|
|
repr_str = 'PulseBlockEnsemble(name={0}, block_list={1}, rotating_frame={2})'.format( |
|
391
|
|
|
self.name, self.block_list, self.rotating_frame) |
|
392
|
|
|
return repr_str |
|
393
|
|
|
|
|
394
|
|
|
def __str__(self): |
|
395
|
|
|
return_str = 'PulseBlockEnsemble "{0}"\n\trotating frame: {1}\n\t' \ |
|
396
|
|
|
'has been sampled: {2}\n\t<block name>\t<repetitions>\n\t'.format( |
|
397
|
|
|
self.name, self.rotating_frame, bool(self.sampling_information)) |
|
398
|
|
|
return_str += '\n\t'.join(('{0}\t{1}'.format(name, reps) for name, reps in self.block_list)) |
|
399
|
|
|
return return_str |
|
400
|
|
|
|
|
401
|
|
|
def __len__(self): |
|
402
|
|
|
return len(self.block_list) |
|
403
|
|
|
|
|
404
|
|
|
def __getitem__(self, key): |
|
405
|
|
|
if not isinstance(key, (slice, int)): |
|
406
|
|
|
raise TypeError('PulseBlockEnsemble indices must be int or slice, not {0}' |
|
407
|
|
|
''.format(type(key))) |
|
408
|
|
|
return self.block_list[key] |
|
409
|
|
|
|
|
410
|
|
|
def __setitem__(self, key, value): |
|
411
|
|
|
if isinstance(key, int): |
|
412
|
|
|
if not isinstance(value, (tuple, list)) or len(value) != 2: |
|
413
|
|
|
raise TypeError('PulseBlockEnsemble block list entries must be a tuple or list of ' |
|
414
|
|
|
'length 2') |
|
415
|
|
|
elif not isinstance(value[0], str): |
|
416
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 0 must contain str, ' |
|
417
|
|
|
'not {0}'.format(type(value[0]))) |
|
418
|
|
|
elif not isinstance(value[1], int) or value[1] < 0: |
|
419
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 1 must contain int >= 0') |
|
420
|
|
|
elif isinstance(key, slice): |
|
421
|
|
|
for element in value: |
|
422
|
|
|
if not isinstance(element, (tuple, list)) or len(value) != 2: |
|
423
|
|
|
raise TypeError('PulseBlockEnsemble block list entries must be a tuple or list ' |
|
424
|
|
|
'of length 2') |
|
425
|
|
|
elif not isinstance(element[0], str): |
|
426
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 0 must contain str, ' |
|
427
|
|
|
'not {0}'.format(type(element[0]))) |
|
428
|
|
|
elif not isinstance(element[1], int) or element[1] < 0: |
|
429
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 1 must contain int >= ' |
|
430
|
|
|
'0') |
|
431
|
|
|
else: |
|
432
|
|
|
raise TypeError('PulseBlockEnsemble indices must be int or slice, not {0}' |
|
433
|
|
|
''.format(type(key))) |
|
434
|
|
|
self.block_list[key] = tuple(value) |
|
435
|
|
|
self.sampling_information = dict() |
|
436
|
|
|
self.measurement_information = dict() |
|
437
|
|
|
return |
|
438
|
|
|
|
|
439
|
|
|
def __delitem__(self, key): |
|
440
|
|
|
if not isinstance(key, (slice, int)): |
|
441
|
|
|
raise TypeError('PulseBlockEnsemble indices must be int or slice, not {0}' |
|
442
|
|
|
''.format(type(key))) |
|
443
|
|
|
|
|
444
|
|
|
del self.block_list[key] |
|
445
|
|
|
self.sampling_information = dict() |
|
446
|
|
|
self.measurement_information = dict() |
|
447
|
|
|
return |
|
448
|
|
|
|
|
449
|
|
|
def pop(self, position=None): |
|
450
|
|
|
if len(self.block_list) == 0: |
|
451
|
|
|
raise IndexError('pop from empty PulseBlockEnsemble') |
|
452
|
|
|
|
|
453
|
|
|
if position is None: |
|
454
|
|
|
self.sampling_information = dict() |
|
455
|
|
|
self.measurement_information = dict() |
|
456
|
|
|
return self.block_list.pop() |
|
457
|
|
|
|
|
458
|
|
|
if not isinstance(position, int): |
|
459
|
|
|
raise TypeError('PulseBlockEnsemble.pop position argument expects integer, not {0}' |
|
460
|
|
|
''.format(type(position))) |
|
461
|
|
|
|
|
462
|
|
|
if position < 0: |
|
463
|
|
|
position = len(self.block_list) + position |
|
464
|
|
|
|
|
465
|
|
|
if len(self.block_list) <= position or position < 0: |
|
466
|
|
|
raise IndexError('PulseBlockEnsemble block list index out of range') |
|
467
|
|
|
|
|
468
|
|
|
self.sampling_information = dict() |
|
469
|
|
|
self.measurement_information = dict() |
|
470
|
|
|
return self.block_list.pop(position) |
|
471
|
|
|
|
|
472
|
|
|
def insert(self, position, element): |
|
473
|
|
|
""" Insert a (PulseBlock.name, repetitions) tuple at the given position. The old element |
|
474
|
|
|
at this position and all consecutive elements after that will be shifted to higher indices. |
|
475
|
|
|
|
|
476
|
|
|
@param int position: position in the element list |
|
477
|
|
|
@param tuple element: (PulseBlock name (str), repetitions (int)) |
|
478
|
|
|
""" |
|
479
|
|
|
if not isinstance(element, (tuple, list)) or len(element) != 2: |
|
480
|
|
|
raise TypeError('PulseBlockEnsemble block list entries must be a tuple or list of ' |
|
481
|
|
|
'length 2') |
|
482
|
|
|
elif not isinstance(element[0], str): |
|
483
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 0 must contain str, ' |
|
484
|
|
|
'not {0}'.format(type(element[0]))) |
|
485
|
|
|
elif not isinstance(element[1], int) or element[1] < 0: |
|
486
|
|
|
raise ValueError('PulseBlockEnsemble element tuple index 1 must contain int >= 0') |
|
487
|
|
|
|
|
488
|
|
|
if position < 0: |
|
489
|
|
|
position = len(self.block_list) + position |
|
490
|
|
|
if len(self.block_list) < position or position < 0: |
|
491
|
|
|
raise IndexError('PulseBlockEnsemble block list index out of range') |
|
492
|
|
|
|
|
493
|
|
|
self.block_list.insert(position, tuple(element)) |
|
494
|
|
|
self.sampling_information = dict() |
|
495
|
|
|
self.measurement_information = dict() |
|
496
|
|
|
return |
|
497
|
|
|
|
|
498
|
|
|
def append(self, element): |
|
499
|
|
|
""" |
|
500
|
|
|
""" |
|
501
|
|
|
self.insert(position=len(self), element=element) |
|
502
|
|
|
return |
|
503
|
|
|
|
|
504
|
|
|
def extend(self, iterable): |
|
505
|
|
|
for element in iterable: |
|
506
|
|
|
self.append(element=element) |
|
507
|
|
|
return |
|
508
|
|
|
|
|
509
|
|
|
def clear(self): |
|
510
|
|
|
del self.block_list[:] |
|
511
|
|
|
self.sampling_information = dict() |
|
512
|
|
|
self.measurement_information = dict() |
|
513
|
|
|
return |
|
514
|
|
|
|
|
515
|
|
|
def reverse(self): |
|
516
|
|
|
self.block_list.reverse() |
|
517
|
|
|
self.sampling_information = dict() |
|
518
|
|
|
self.measurement_information = dict() |
|
519
|
|
|
return |
|
520
|
|
|
|
|
521
|
|
|
def get_dict_representation(self): |
|
522
|
|
|
dict_repr = dict() |
|
523
|
|
|
dict_repr['name'] = self.name |
|
524
|
|
|
dict_repr['rotating_frame'] = self.rotating_frame |
|
525
|
|
|
dict_repr['block_list'] = self.block_list |
|
526
|
|
|
dict_repr['sampling_information'] = self.sampling_information |
|
527
|
|
|
dict_repr['measurement_information'] = self.measurement_information |
|
528
|
|
|
return dict_repr |
|
529
|
|
|
|
|
530
|
|
|
@staticmethod |
|
531
|
|
|
def ensemble_from_dict(ensemble_dict): |
|
532
|
|
|
new_ens = PulseBlockEnsemble(name=ensemble_dict['name'], |
|
533
|
|
|
block_list=ensemble_dict['block_list'], |
|
534
|
|
|
rotating_frame=ensemble_dict['rotating_frame']) |
|
535
|
|
|
new_ens.sampling_information = ensemble_dict['sampling_information'] |
|
536
|
|
|
new_ens.measurement_information = ensemble_dict['measurement_information'] |
|
537
|
|
|
return new_ens |
|
538
|
|
|
|
|
539
|
|
|
|
|
540
|
|
|
class PulseSequence(object): |
|
541
|
|
|
""" |
|
542
|
|
|
Higher order object for sequence capability. |
|
543
|
|
|
|
|
544
|
|
|
Represents a playback procedure for a number of PulseBlockEnsembles. Unused for pulse |
|
545
|
|
|
generator hardware without sequencing functionality. |
|
546
|
|
|
""" |
|
547
|
|
|
__default_seq_params = {'repetitions': 1, |
|
548
|
|
|
'go_to': -1, |
|
549
|
|
|
'event_jump_to': -1, |
|
550
|
|
|
'event_trigger': 'OFF', |
|
551
|
|
|
'wait_for': 'OFF', |
|
552
|
|
|
'flag_trigger': 'OFF', |
|
553
|
|
|
'flag_high': 'OFF'} |
|
554
|
|
|
|
|
555
|
|
|
def __init__(self, name, ensemble_list=None, rotating_frame=False): |
|
556
|
|
|
""" |
|
557
|
|
|
The constructor for a PulseSequence objects needs to have: |
|
558
|
|
|
|
|
559
|
|
|
@param str name: the actual name of the sequence |
|
560
|
|
|
@param list ensemble_list: list containing a tuple of two entries: |
|
561
|
|
|
[(PulseBlockEnsemble name, seq_param), |
|
562
|
|
|
(PulseBlockEnsemble name, seq_param), ...] |
|
563
|
|
|
The seq_param is a dictionary, where the various sequence |
|
564
|
|
|
parameters are saved with their keywords and the |
|
565
|
|
|
according parameter (as item). |
|
566
|
|
|
Available parameters are: |
|
567
|
|
|
'repetitions': The number of repetitions for that sequence |
|
568
|
|
|
step. (Default 0) |
|
569
|
|
|
0 meaning the step is played once. |
|
570
|
|
|
Set to -1 for infinite looping. |
|
571
|
|
|
'go_to': The sequence step index to jump to after |
|
572
|
|
|
having played all repetitions. (Default -1) |
|
573
|
|
|
Indices starting at 1 for first step. |
|
574
|
|
|
Set to 0 or -1 to follow up with the next step. |
|
575
|
|
|
'event_jump_to': The sequence step to jump to |
|
576
|
|
|
(starting from 1) in case of a trigger |
|
577
|
|
|
event (see event_trigger). |
|
578
|
|
|
Setting it to 0 or -1 means jump to next |
|
579
|
|
|
step. Ignored if event_trigger is 'OFF'. |
|
580
|
|
|
'event_trigger': The trigger input to listen to in order |
|
581
|
|
|
to perform sequence jumps. Set to 'OFF' |
|
582
|
|
|
(default) in order to ignore triggering. |
|
583
|
|
|
'wait_for': The trigger input to wait for before playing |
|
584
|
|
|
this sequence step. Set to 'OFF' (default) |
|
585
|
|
|
in order to play the current step immediately. |
|
586
|
|
|
'flag_trigger': The flag to trigger when this sequence |
|
587
|
|
|
step starts playing. Select 'OFF' |
|
588
|
|
|
(default) for no flag trigger. |
|
589
|
|
|
'flag_high': The flag to set to high while this step is |
|
590
|
|
|
playing. Select 'OFF' (default) to set all |
|
591
|
|
|
flags to low. |
|
592
|
|
|
|
|
593
|
|
|
If only 'repetitions' are in the dictionary, then the dict |
|
594
|
|
|
will look like: |
|
595
|
|
|
seq_param = {'repetitions': 41} |
|
596
|
|
|
and so the respective sequence step will play 42 times. |
|
597
|
|
|
@param bool rotating_frame: indicates, whether the phase has to be preserved in all |
|
598
|
|
|
analog signals ACROSS different waveforms |
|
599
|
|
|
""" |
|
600
|
|
|
self.name = name |
|
601
|
|
|
self.rotating_frame = rotating_frame |
|
602
|
|
|
self.ensemble_list = list() if ensemble_list is None else ensemble_list |
|
603
|
|
|
self.is_finite = True |
|
604
|
|
|
self.refresh_parameters() |
|
605
|
|
|
|
|
606
|
|
|
# self.sampled_ensembles = OrderedDict() |
|
607
|
|
|
# Dictionary container to store information related to the actually sampled |
|
608
|
|
|
# Waveforms like pulser settings used during sampling (sample_rate, activation_config etc.) |
|
609
|
|
|
# and additional information about the discretization of the waveform (timebin positions of |
|
610
|
|
|
# the PulseBlockElement transitions etc.) |
|
611
|
|
|
# This container is not necessary for the sampling process but serves only the purpose of |
|
612
|
|
|
# holding optional information for different modules. |
|
613
|
|
|
self.sampling_information = dict() |
|
614
|
|
|
# Dictionary container to store additional information about for measurement settings |
|
615
|
|
|
# (ignore_lasers, controlled_values, alternating etc.). |
|
616
|
|
|
# This container needs to be populated by the script creating the PulseSequence |
|
617
|
|
|
# before saving it. |
|
618
|
|
|
self.measurement_information = dict() |
|
619
|
|
|
return |
|
620
|
|
|
|
|
621
|
|
|
def refresh_parameters(self): |
|
622
|
|
|
self.is_finite = True |
|
623
|
|
|
for ensemble_name, params in self.ensemble_list: |
|
624
|
|
|
if params['repetitions'] < 0: |
|
625
|
|
|
self.is_finite = False |
|
626
|
|
|
break |
|
627
|
|
|
return |
|
628
|
|
|
|
|
629
|
|
|
def __repr__(self): |
|
630
|
|
|
repr_str = 'PulseSequence(name={0}, ensemble_list={1}, rotating_frame={2})'.format( |
|
631
|
|
|
self.name, self.ensemble_list, self.rotating_frame) |
|
632
|
|
|
return repr_str |
|
633
|
|
|
|
|
634
|
|
|
def __str__(self): |
|
635
|
|
|
return_str = 'PulseSequence "{0}"\n\trotating frame: {1}\n\t' \ |
|
636
|
|
|
'has finite length: {2}\n\thas been sampled: {3}\n\t<ensemble name>\t' \ |
|
637
|
|
|
'<sequence parameters>\n\t'.format(self.name, |
|
638
|
|
|
self.rotating_frame, |
|
639
|
|
|
self.is_finite, |
|
640
|
|
|
bool(self.sampling_information)) |
|
641
|
|
|
return_str += '\n\t'.join(('{0}\t{1}'.format(name, param) for name, param in self)) |
|
642
|
|
|
return return_str |
|
643
|
|
|
|
|
644
|
|
|
def __len__(self): |
|
645
|
|
|
return len(self.ensemble_list) |
|
646
|
|
|
|
|
647
|
|
|
def __getitem__(self, key): |
|
648
|
|
|
if not isinstance(key, (slice, int)): |
|
649
|
|
|
raise TypeError('PulseSequence indices must be int or slice, not {0}'.format(type(key))) |
|
650
|
|
|
return self.ensemble_list[key] |
|
651
|
|
|
|
|
652
|
|
|
def __setitem__(self, key, value): |
|
653
|
|
|
stage_refresh = False |
|
654
|
|
|
if isinstance(key, int): |
|
655
|
|
|
if isinstance(value, str): |
|
656
|
|
|
value = (value, self.__default_seq_params.copy()) |
|
657
|
|
|
if not isinstance(value, (tuple, list)) or len(value) != 2: |
|
658
|
|
|
raise TypeError('PulseSequence ensemble list entries must be a tuple or list of ' |
|
659
|
|
|
'length 2') |
|
660
|
|
|
elif not isinstance(value[0], str): |
|
661
|
|
|
raise ValueError('PulseSequence element tuple index 0 must contain str, not {0}' |
|
662
|
|
|
''.format(type(value[0]))) |
|
663
|
|
|
elif not isinstance(value[1], dict): |
|
664
|
|
|
raise ValueError('PulseSequence element tuple index 1 must contain dict, not {0}' |
|
665
|
|
|
''.format(type(value[1]))) |
|
666
|
|
|
|
|
667
|
|
|
if value[1]['repetitions'] < 0: |
|
668
|
|
|
self.is_finite = False |
|
669
|
|
|
elif not self.is_finite and self[key][1]['repetitions'] < 0: |
|
670
|
|
|
stage_refresh = True |
|
671
|
|
|
elif isinstance(key, slice): |
|
672
|
|
|
if isinstance(value[0], str): |
|
673
|
|
|
tmp_value = list() |
|
674
|
|
|
for element in value: |
|
675
|
|
|
tmp_value.append((element, self.__default_seq_params.copy())) |
|
676
|
|
|
value = tmp_value |
|
677
|
|
|
for element in value: |
|
678
|
|
|
if not isinstance(element, (tuple, list)) or len(value) != 2: |
|
679
|
|
|
raise TypeError('PulseSequence block list entries must be a tuple or list ' |
|
680
|
|
|
'of length 2') |
|
681
|
|
|
elif not isinstance(element[0], str): |
|
682
|
|
|
raise ValueError('PulseSequence element tuple index 0 must contain str, not {0}' |
|
683
|
|
|
''.format(type(element[0]))) |
|
684
|
|
|
elif not isinstance(element[1], dict): |
|
685
|
|
|
raise ValueError('PulseSequence element tuple index 1 must contain dict, not ' |
|
686
|
|
|
'{0}'.format(type(element[1]))) |
|
687
|
|
|
|
|
688
|
|
|
if element[1]['repetitions'] < 0: |
|
689
|
|
|
self.is_finite = False |
|
690
|
|
|
elif not self.is_finite: |
|
691
|
|
|
stage_refresh = True |
|
692
|
|
|
else: |
|
693
|
|
|
raise TypeError('PulseSequence indices must be int or slice, not {0}'.format(type(key))) |
|
694
|
|
|
self.ensemble_list[key] = tuple(value) |
|
695
|
|
|
self.sampling_information = dict() |
|
696
|
|
|
self.measurement_information = dict() |
|
697
|
|
|
if stage_refresh: |
|
698
|
|
|
self.refresh_parameters() |
|
699
|
|
|
return |
|
700
|
|
|
|
|
701
|
|
|
def __delitem__(self, key): |
|
702
|
|
|
if isinstance(key, slice): |
|
703
|
|
|
stage_refresh = False |
|
704
|
|
|
for element in self.ensemble_list[key]: |
|
705
|
|
|
if element[1]['repetitions'] < 0: |
|
706
|
|
|
stage_refresh = True |
|
707
|
|
|
break |
|
708
|
|
|
elif isinstance(key, int): |
|
709
|
|
|
stage_refresh = self.ensemble_list[key][1]['repetitions'] < 0 |
|
710
|
|
|
else: |
|
711
|
|
|
raise TypeError('PulseSequence indices must be int or slice, not {0}'.format(type(key))) |
|
712
|
|
|
del self.ensemble_list[key] |
|
713
|
|
|
self.sampling_information = dict() |
|
714
|
|
|
self.measurement_information = dict() |
|
715
|
|
|
if stage_refresh: |
|
716
|
|
|
self.refresh_parameters() |
|
717
|
|
|
return |
|
718
|
|
|
|
|
719
|
|
|
def pop(self, position=None): |
|
720
|
|
|
if len(self.ensemble_list) == 0: |
|
721
|
|
|
raise IndexError('pop from empty PulseSequence') |
|
722
|
|
|
|
|
723
|
|
|
if position is None: |
|
724
|
|
|
position = len(self.ensemble_list) - 1 |
|
725
|
|
|
|
|
726
|
|
|
if not isinstance(position, int): |
|
727
|
|
|
raise TypeError('PulseSequence.pop position argument expects integer, not {0}' |
|
728
|
|
|
''.format(type(position))) |
|
729
|
|
|
|
|
730
|
|
|
if position < 0: |
|
731
|
|
|
position = len(self.ensemble_list) + position |
|
732
|
|
|
|
|
733
|
|
|
if len(self.ensemble_list) <= position or position < 0: |
|
734
|
|
|
raise IndexError('PulseSequence ensemble list index out of range') |
|
735
|
|
|
|
|
736
|
|
|
self.sampling_information = dict() |
|
737
|
|
|
self.measurement_information = dict() |
|
738
|
|
|
if self.ensemble_list[-1][1]['repetitions'] < 0: |
|
739
|
|
|
popped_element = self.ensemble_list.pop(position) |
|
740
|
|
|
self.refresh_parameters() |
|
741
|
|
|
return popped_element |
|
742
|
|
|
return self.ensemble_list.pop(position) |
|
743
|
|
|
|
|
744
|
|
|
def insert(self, position, element): |
|
745
|
|
|
""" Insert a (PulseSequence.name, parameters) tuple at the given position. The old element |
|
746
|
|
|
at this position and all consecutive elements after that will be shifted to higher indices. |
|
747
|
|
|
|
|
748
|
|
|
@param int position: position in the ensemble list |
|
749
|
|
|
@param tuple|str element: PulseBlock name (str)[, seq_parameters (dict)] |
|
750
|
|
|
""" |
|
751
|
|
|
if isinstance(element, str): |
|
752
|
|
|
element = (element, self.__default_seq_params.copy()) |
|
753
|
|
|
|
|
754
|
|
|
if not isinstance(element, (tuple, list)) or len(element) != 2: |
|
755
|
|
|
raise TypeError('PulseSequence ensemble list entries must be a tuple or list of ' |
|
756
|
|
|
'length 2') |
|
757
|
|
|
elif not isinstance(element[0], str): |
|
758
|
|
|
raise ValueError('PulseSequence element tuple index 0 must contain str, ' |
|
759
|
|
|
'not {0}'.format(type(element[0]))) |
|
760
|
|
|
elif not isinstance(element[1], dict): |
|
761
|
|
|
raise ValueError('PulseSequence element tuple index 1 must contain dict') |
|
762
|
|
|
|
|
763
|
|
|
if position < 0: |
|
764
|
|
|
position = len(self.ensemble_list) + position |
|
765
|
|
|
if len(self.ensemble_list) < position or position < 0: |
|
766
|
|
|
raise IndexError('PulseSequence ensemble list index out of range') |
|
767
|
|
|
|
|
768
|
|
|
self.ensemble_list.insert(position, tuple(element)) |
|
769
|
|
|
if element[1]['repetitions'] < 0: |
|
770
|
|
|
self.is_finite = False |
|
771
|
|
|
self.sampling_information = dict() |
|
772
|
|
|
self.measurement_information = dict() |
|
773
|
|
|
return |
|
774
|
|
|
|
|
775
|
|
|
def append(self, element): |
|
776
|
|
|
""" |
|
777
|
|
|
""" |
|
778
|
|
|
self.insert(position=len(self), element=element) |
|
779
|
|
|
return |
|
780
|
|
|
|
|
781
|
|
|
def extend(self, iterable): |
|
782
|
|
|
for element in iterable: |
|
783
|
|
|
self.append(element=element) |
|
784
|
|
|
return |
|
785
|
|
|
|
|
786
|
|
|
def clear(self): |
|
787
|
|
|
del self.ensemble_list[:] |
|
788
|
|
|
self.sampling_information = dict() |
|
789
|
|
|
self.measurement_information = dict() |
|
790
|
|
|
self.is_finite = True |
|
791
|
|
|
return |
|
792
|
|
|
|
|
793
|
|
|
def reverse(self): |
|
794
|
|
|
self.ensemble_list.reverse() |
|
795
|
|
|
self.sampling_information = dict() |
|
796
|
|
|
self.measurement_information = dict() |
|
797
|
|
|
return |
|
798
|
|
|
|
|
799
|
|
|
def get_dict_representation(self): |
|
800
|
|
|
dict_repr = dict() |
|
801
|
|
|
dict_repr['name'] = self.name |
|
802
|
|
|
dict_repr['rotating_frame'] = self.rotating_frame |
|
803
|
|
|
dict_repr['ensemble_list'] = self.ensemble_list |
|
804
|
|
|
dict_repr['sampling_information'] = self.sampling_information |
|
805
|
|
|
dict_repr['measurement_information'] = self.measurement_information |
|
806
|
|
|
return dict_repr |
|
807
|
|
|
|
|
808
|
|
|
@staticmethod |
|
809
|
|
|
def sequence_from_dict(sequence_dict): |
|
810
|
|
|
new_seq = PulseSequence(name=sequence_dict['name'], |
|
811
|
|
|
ensemble_list=sequence_dict['ensemble_list'], |
|
812
|
|
|
rotating_frame=sequence_dict['rotating_frame']) |
|
813
|
|
|
new_seq.sampling_information = sequence_dict['sampling_information'] |
|
814
|
|
|
new_seq.measurement_information = sequence_dict['measurement_information'] |
|
815
|
|
|
return new_seq |
|
816
|
|
|
|
|
817
|
|
|
|
|
818
|
|
|
class PredefinedGeneratorBase: |
|
819
|
|
|
""" |
|
820
|
|
|
Base class for PulseObjectGenerator and predefined generator classes containing the actual |
|
821
|
|
|
"generate_"-methods. |
|
822
|
|
|
|
|
823
|
|
|
This class holds a protected reference to the SequenceGeneratorLogic and provides read-only |
|
824
|
|
|
access via properties to various attributes of the logic module. |
|
825
|
|
|
SequenceGeneratorLogic logger is also accessible via this base class and can be used as in any |
|
826
|
|
|
qudi module (e.g. self.log.error(...)). |
|
827
|
|
|
Also provides helper methods to simplify sequence/ensemble generation. |
|
828
|
|
|
""" |
|
829
|
|
|
def __init__(self, sequencegeneratorlogic): |
|
830
|
|
|
# Keep protected reference to the SequenceGeneratorLogic |
|
831
|
|
|
self.__sequencegeneratorlogic = sequencegeneratorlogic |
|
832
|
|
|
|
|
833
|
|
|
@property |
|
834
|
|
|
def log(self): |
|
835
|
|
|
return self.__sequencegeneratorlogic.log |
|
836
|
|
|
|
|
837
|
|
|
@property |
|
838
|
|
|
def pulse_generator_settings(self): |
|
839
|
|
|
return self.__sequencegeneratorlogic.pulse_generator_settings |
|
840
|
|
|
|
|
841
|
|
|
@property |
|
842
|
|
|
def generation_parameters(self): |
|
843
|
|
|
return self.__sequencegeneratorlogic.generation_parameters |
|
844
|
|
|
|
|
845
|
|
|
@property |
|
846
|
|
|
def channel_set(self): |
|
847
|
|
|
channels = self.pulse_generator_settings.get('activation_config') |
|
848
|
|
|
if channels is None: |
|
849
|
|
|
channels = ('', set()) |
|
850
|
|
|
return channels[1] |
|
851
|
|
|
|
|
852
|
|
|
@property |
|
853
|
|
|
def analog_channels(self): |
|
854
|
|
|
return {chnl for chnl in self.channel_set if chnl.startswith('a')} |
|
855
|
|
|
|
|
856
|
|
|
@property |
|
857
|
|
|
def digital_channels(self): |
|
858
|
|
|
return {chnl for chnl in self.channel_set if chnl.startswith('d')} |
|
859
|
|
View Code Duplication |
|
|
|
|
|
|
|
860
|
|
|
@property |
|
861
|
|
|
def laser_channel(self): |
|
862
|
|
|
return self.generation_parameters.get('laser_channel') |
|
863
|
|
|
|
|
864
|
|
|
@property |
|
865
|
|
|
def sync_channel(self): |
|
866
|
|
|
channel = self.generation_parameters.get('sync_channel') |
|
867
|
|
|
return None if channel == '' else channel |
|
868
|
|
|
|
|
869
|
|
|
@property |
|
870
|
|
|
def gate_channel(self): |
|
871
|
|
|
channel = self.generation_parameters.get('gate_channel') |
|
872
|
|
|
return None if channel == '' else channel |
|
873
|
|
|
|
|
874
|
|
|
@property |
|
875
|
|
|
def analog_trigger_voltage(self): |
|
876
|
|
|
return self.generation_parameters.get('analog_trigger_voltage') |
|
877
|
|
|
|
|
878
|
|
|
@property |
|
879
|
|
|
def laser_delay(self): |
|
880
|
|
|
return self.generation_parameters.get('laser_delay') |
|
881
|
|
|
|
|
882
|
|
|
@property |
|
883
|
|
|
def microwave_channel(self): |
|
884
|
|
|
channel = self.generation_parameters.get('microwave_channel') |
|
885
|
|
|
return None if channel == '' else channel |
|
886
|
|
|
|
|
887
|
|
|
@property |
|
888
|
|
|
def microwave_frequency(self): |
|
889
|
|
|
return self.generation_parameters.get('microwave_frequency') |
|
890
|
|
|
|
|
891
|
|
|
@property |
|
892
|
|
|
def microwave_amplitude(self): |
|
893
|
|
|
return self.generation_parameters.get('microwave_amplitude') |
|
894
|
|
|
|
|
895
|
|
|
@property |
|
896
|
|
|
def laser_length(self): |
|
897
|
|
|
return self.generation_parameters.get('laser_length') |
|
898
|
|
|
|
|
899
|
|
|
@property |
|
900
|
|
|
def wait_time(self): |
|
901
|
|
|
return self.generation_parameters.get('wait_time') |
|
902
|
|
|
|
|
903
|
|
|
@property |
|
904
|
|
|
def rabi_period(self): |
|
905
|
|
|
return self.generation_parameters.get('rabi_period') |
|
906
|
|
|
|
|
907
|
|
|
################################################################################################ |
|
908
|
|
|
# Helper methods #### |
|
909
|
|
|
################################################################################################ |
|
910
|
|
|
def _get_idle_element(self, length, increment): |
|
911
|
|
|
""" |
|
912
|
|
|
Creates an idle pulse PulseBlockElement |
|
913
|
|
|
|
|
914
|
|
|
@param float length: idle duration in seconds |
|
915
|
|
|
@param float increment: idle duration increment in seconds |
|
916
|
|
|
|
|
917
|
|
|
@return: PulseBlockElement, the generated idle element |
|
918
|
|
|
""" |
|
919
|
|
|
# Create idle element |
|
920
|
|
|
return PulseBlockElement( |
|
921
|
|
|
init_length_s=length, |
|
922
|
|
|
increment_s=increment, |
|
923
|
|
|
pulse_function={chnl: SamplingFunctions.Idle() for chnl in self.analog_channels}, |
|
924
|
|
|
digital_high={chnl: False for chnl in self.digital_channels}) |
|
925
|
|
|
|
|
926
|
|
|
def _get_trigger_element(self, length, increment, channels): |
|
927
|
|
|
""" |
|
928
|
|
|
Creates a trigger PulseBlockElement |
|
929
|
|
|
|
|
930
|
|
|
@param float length: trigger duration in seconds |
|
931
|
|
|
@param float increment: trigger duration increment in seconds |
|
932
|
|
|
@param str|list channels: The pulser channel(s) to be triggered. |
|
933
|
|
|
|
|
934
|
|
|
@return: PulseBlockElement, the generated trigger element |
|
935
|
|
|
""" |
|
936
|
|
|
if isinstance(channels, str): |
|
937
|
|
|
channels = [channels] |
|
938
|
|
|
|
|
939
|
|
|
# input params for element generation |
|
940
|
|
|
pulse_function = {chnl: SamplingFunctions.Idle() for chnl in self.analog_channels} |
|
941
|
|
|
digital_high = {chnl: False for chnl in self.digital_channels} |
|
942
|
|
|
|
|
943
|
|
|
# Determine analogue or digital trigger channel and set channels accordingly. |
|
944
|
|
|
for channel in channels: |
|
945
|
|
|
if channel.startswith('d'): |
|
946
|
|
|
digital_high[channel] = True |
|
947
|
|
|
else: |
|
948
|
|
|
pulse_function[channel] = SamplingFunctions.DC(voltage=self.analog_trigger_voltage) |
|
949
|
|
|
|
|
950
|
|
|
# return trigger element |
|
951
|
|
|
return PulseBlockElement(init_length_s=length, |
|
952
|
|
|
increment_s=increment, |
|
953
|
|
|
pulse_function=pulse_function, |
|
954
|
|
|
digital_high=digital_high) |
|
955
|
|
|
|
|
956
|
|
|
def _get_laser_element(self, length, increment): |
|
957
|
|
|
""" |
|
958
|
|
|
Creates laser trigger PulseBlockElement |
|
959
|
|
|
|
|
960
|
|
|
@param float length: laser pulse duration in seconds |
|
961
|
|
|
@param float increment: laser pulse duration increment in seconds |
|
962
|
|
|
|
|
963
|
|
|
@return: PulseBlockElement, two elements for laser and gate trigger (delay element) |
|
964
|
|
|
""" |
|
965
|
|
|
return self._get_trigger_element(length=length, |
|
966
|
|
|
increment=increment, |
|
967
|
|
|
channels=self.laser_channel) |
|
968
|
|
|
|
|
969
|
|
|
def _get_laser_gate_element(self, length, increment): |
|
970
|
|
|
""" |
|
971
|
|
|
""" |
|
972
|
|
|
laser_gate_element = self._get_laser_element(length=length, |
|
973
|
|
|
increment=increment) |
|
974
|
|
|
if self.gate_channel: |
|
975
|
|
|
if self.gate_channel.startswith('d'): |
|
976
|
|
|
laser_gate_element.digital_high[self.gate_channel] = True |
|
977
|
|
|
else: |
|
978
|
|
|
laser_gate_element.pulse_function[self.gate_channel] = SamplingFunctions.DC( |
|
979
|
|
|
voltage=self.analog_trigger_voltage) |
|
980
|
|
|
return laser_gate_element |
|
981
|
|
|
|
|
982
|
|
|
def _get_delay_element(self): |
|
983
|
|
|
""" |
|
984
|
|
|
Creates an idle element of length of the laser delay |
|
985
|
|
|
|
|
986
|
|
|
@return PulseBlockElement: The delay element |
|
987
|
|
|
""" |
|
988
|
|
|
return self._get_idle_element(length=self.laser_delay, |
|
989
|
|
|
increment=0) |
|
990
|
|
|
|
|
991
|
|
|
def _get_delay_gate_element(self): |
|
992
|
|
|
""" |
|
993
|
|
|
Creates a gate trigger of length of the laser delay. |
|
994
|
|
|
If no gate channel is specified will return a simple idle element. |
|
995
|
|
|
|
|
996
|
|
|
@return PulseBlockElement: The delay element |
|
997
|
|
|
""" |
|
998
|
|
|
if self.gate_channel: |
|
999
|
|
|
return self._get_trigger_element(length=self.laser_delay, |
|
1000
|
|
|
increment=0, |
|
1001
|
|
|
channels=self.gate_channel) |
|
1002
|
|
|
else: |
|
1003
|
|
|
return self._get_delay_element() |
|
1004
|
|
|
|
|
1005
|
|
|
def _get_sync_element(self): |
|
1006
|
|
|
""" |
|
1007
|
|
|
|
|
1008
|
|
|
""" |
|
1009
|
|
|
return self._get_trigger_element(length=50e-9, |
|
1010
|
|
|
increment=0, |
|
1011
|
|
|
channels=self.sync_channel) |
|
1012
|
|
|
|
|
1013
|
|
|
def _get_mw_element(self, length, increment, amp=None, freq=None, phase=None): |
|
1014
|
|
|
""" |
|
1015
|
|
|
Creates a MW pulse PulseBlockElement |
|
1016
|
|
|
|
|
1017
|
|
|
@param float length: MW pulse duration in seconds |
|
1018
|
|
|
@param float increment: MW pulse duration increment in seconds |
|
1019
|
|
|
@param float freq: MW frequency in case of analogue MW channel in Hz |
|
1020
|
|
|
@param float amp: MW amplitude in case of analogue MW channel in V |
|
1021
|
|
|
@param float phase: MW phase in case of analogue MW channel in deg |
|
1022
|
|
|
|
|
1023
|
|
|
@return: PulseBlockElement, the generated MW element |
|
1024
|
|
|
""" |
|
1025
|
|
|
if self.microwave_channel.startswith('d'): |
|
1026
|
|
|
mw_element = self._get_trigger_element( |
|
1027
|
|
|
length=length, |
|
1028
|
|
|
increment=increment, |
|
1029
|
|
|
channels=self.microwave_channel) |
|
1030
|
|
|
else: |
|
1031
|
|
|
mw_element = self._get_idle_element( |
|
1032
|
|
|
length=length, |
|
1033
|
|
|
increment=increment) |
|
1034
|
|
|
mw_element.pulse_function[self.microwave_channel] = SamplingFunctions.Sin( |
|
1035
|
|
|
amplitude=amp, |
|
1036
|
|
|
frequency=freq, |
|
1037
|
|
|
phase=phase) |
|
1038
|
|
|
return mw_element |
|
1039
|
|
|
|
|
1040
|
|
|
def _get_multiple_mw_element(self, length, increment, amps=None, freqs=None, phases=None): |
|
1041
|
|
|
""" |
|
1042
|
|
|
Creates single, double or triple sine mw element. |
|
1043
|
|
|
|
|
1044
|
|
|
@param float length: MW pulse duration in seconds |
|
1045
|
|
|
@param float increment: MW pulse duration increment in seconds |
|
1046
|
|
|
@param amps: list containing the amplitudes |
|
1047
|
|
|
@param freqs: list containing the frequencies |
|
1048
|
|
|
@param phases: list containing the phases |
|
1049
|
|
|
@return: PulseBlockElement, the generated MW element |
|
1050
|
|
|
""" |
|
1051
|
|
|
if isinstance(amps, (int, float)): |
|
1052
|
|
|
amps = [amps] |
|
1053
|
|
|
if isinstance(freqs, (int, float)): |
|
1054
|
|
|
freqs = [freqs] |
|
1055
|
|
|
if isinstance(phases, (int, float)): |
|
1056
|
|
|
phases = [phases] |
|
1057
|
|
|
|
|
1058
|
|
|
if self.microwave_channel.startswith('d'): |
|
1059
|
|
|
mw_element = self._get_trigger_element( |
|
1060
|
|
|
length=length, |
|
1061
|
|
|
increment=increment, |
|
1062
|
|
|
channels=self.microwave_channel) |
|
1063
|
|
|
else: |
|
1064
|
|
|
mw_element = self._get_idle_element( |
|
1065
|
|
|
length=length, |
|
1066
|
|
|
increment=increment) |
|
1067
|
|
|
|
|
1068
|
|
|
sine_number = min(len(amps), len(freqs), len(phases)) |
|
1069
|
|
|
|
|
1070
|
|
|
if sine_number < 2: |
|
1071
|
|
|
mw_element.pulse_function[self.microwave_channel] = SamplingFunctions.Sin( |
|
1072
|
|
|
amplitude=amps[0], |
|
1073
|
|
|
frequency=freqs[0], |
|
1074
|
|
|
phase=phases[0]) |
|
1075
|
|
|
elif sine_number == 2: |
|
1076
|
|
|
mw_element.pulse_function[self.microwave_channel] = SamplingFunctions.DoubleSin( |
|
1077
|
|
|
amplitude_1=amps[0], |
|
1078
|
|
|
amplitude_2=amps[1], |
|
1079
|
|
|
frequency_1=freqs[0], |
|
1080
|
|
|
frequency_2=freqs[1], |
|
1081
|
|
|
phase_1=phases[0], |
|
1082
|
|
|
phase_2=phases[1]) |
|
1083
|
|
|
else: |
|
1084
|
|
|
mw_element.pulse_function[self.microwave_channel] = SamplingFunctions.TripleSin( |
|
1085
|
|
|
amplitude_1=amps[0], |
|
1086
|
|
|
amplitude_2=amps[1], |
|
1087
|
|
|
amplitude_3=amps[2], |
|
1088
|
|
|
frequency_1=freqs[0], |
|
1089
|
|
|
frequency_2=freqs[1], |
|
1090
|
|
|
frequency_3=freqs[2], |
|
1091
|
|
|
phase_1=phases[0], |
|
1092
|
|
|
phase_2=phases[1], |
|
1093
|
|
|
phase_3=phases[2]) |
|
1094
|
|
|
return mw_element |
|
1095
|
|
|
|
|
1096
|
|
|
def _get_mw_laser_element(self, length, increment, amp=None, freq=None, phase=None): |
|
1097
|
|
|
""" |
|
1098
|
|
|
|
|
1099
|
|
|
@param length: |
|
1100
|
|
|
@param increment: |
|
1101
|
|
|
@param amp: |
|
1102
|
|
|
@param freq: |
|
1103
|
|
|
@param phase: |
|
1104
|
|
|
@return: |
|
1105
|
|
|
""" |
|
1106
|
|
|
mw_laser_element = self._get_mw_element(length=length, |
|
1107
|
|
|
increment=increment, |
|
1108
|
|
|
amp=amp, |
|
1109
|
|
|
freq=freq, |
|
1110
|
|
|
phase=phase) |
|
1111
|
|
|
if self.laser_channel.startswith('d'): |
|
1112
|
|
|
mw_laser_element.digital_high[self.laser_channel] = True |
|
1113
|
|
|
else: |
|
1114
|
|
|
mw_laser_element.pulse_function[self.laser_channel] = SamplingFunctions.DC( |
|
1115
|
|
|
voltage=self.analog_trigger_voltage) |
|
1116
|
|
|
return mw_laser_element |
|
1117
|
|
|
|
|
1118
|
|
|
def _get_ensemble_count_length(self, ensemble, created_blocks): |
|
1119
|
|
|
""" |
|
1120
|
|
|
|
|
1121
|
|
|
@param ensemble: |
|
1122
|
|
|
@param created_blocks: |
|
1123
|
|
|
@return: |
|
1124
|
|
|
""" |
|
1125
|
|
|
if self.gate_channel: |
|
1126
|
|
|
length = self.laser_length + self.laser_delay |
|
1127
|
|
|
else: |
|
1128
|
|
|
blocks = {block.name: block for block in created_blocks} |
|
1129
|
|
|
length = 0.0 |
|
1130
|
|
|
for block_name, reps in ensemble.block_list: |
|
1131
|
|
|
length += blocks[block_name].init_length_s * (reps + 1) |
|
1132
|
|
|
length += blocks[block_name].increment_s * ((reps ** 2 + reps) / 2) |
|
1133
|
|
|
return length |
|
1134
|
|
|
|
|
1135
|
|
|
|
|
1136
|
|
|
class PulseObjectGenerator(PredefinedGeneratorBase): |
|
1137
|
|
|
""" |
|
1138
|
|
|
|
|
1139
|
|
|
""" |
|
1140
|
|
|
def __init__(self, sequencegeneratorlogic): |
|
1141
|
|
|
# Initialize base class |
|
1142
|
|
|
super().__init__(sequencegeneratorlogic) |
|
1143
|
|
|
|
|
1144
|
|
|
# dictionary containing references to all generation methods imported from generator class |
|
1145
|
|
|
# modules. The keys are the method names excluding the prefix "generate_". |
|
1146
|
|
|
self._generate_methods = dict() |
|
1147
|
|
|
# nested dictionary with keys being the generation method names and values being a |
|
1148
|
|
|
# dictionary containing all keyword arguments as keys with their default value |
|
1149
|
|
|
self._generate_method_parameters = dict() |
|
1150
|
|
|
|
|
1151
|
|
|
# import path for generator modules from default dir (logic.predefined_generate_methods) |
|
1152
|
|
|
path_list = [os.path.join(get_main_dir(), 'logic', 'pulsed', 'predefined_generate_methods')] |
|
1153
|
|
|
# import path for generator modules from non-default directory if a path has been given |
|
1154
|
|
|
if isinstance(sequencegeneratorlogic.additional_methods_dir, str): |
|
1155
|
|
|
path_list.append(sequencegeneratorlogic.additional_methods_dir) |
|
1156
|
|
|
|
|
1157
|
|
|
# Import predefined generator modules and get a list of generator classes |
|
1158
|
|
|
generator_classes = self.__import_external_generators(paths=path_list) |
|
1159
|
|
|
|
|
1160
|
|
|
# create an instance of each class and put them in a temporary list |
|
1161
|
|
|
generator_instances = [cls(sequencegeneratorlogic) for cls in generator_classes] |
|
1162
|
|
|
|
|
1163
|
|
|
# add references to all generate methods in each instance to a dict |
|
1164
|
|
|
self.__populate_method_dict(instance_list=generator_instances) |
|
1165
|
|
|
|
|
1166
|
|
|
# populate parameters dictionary from generate method signatures |
|
1167
|
|
|
self.__populate_parameter_dict() |
|
1168
|
|
|
|
|
1169
|
|
|
@property |
|
1170
|
|
|
def predefined_generate_methods(self): |
|
1171
|
|
|
return self._generate_methods |
|
1172
|
|
|
|
|
1173
|
|
|
@property |
|
1174
|
|
|
def predefined_method_parameters(self): |
|
1175
|
|
|
return self._generate_method_parameters.copy() |
|
1176
|
|
|
|
|
1177
|
|
|
def __import_external_generators(self, paths): |
|
1178
|
|
|
""" |
|
1179
|
|
|
Helper method to import all modules from directories contained in paths. |
|
1180
|
|
|
Find all classes in those modules that inherit exclusively from PredefinedGeneratorBase |
|
1181
|
|
|
class and return a list of them. |
|
1182
|
|
|
|
|
1183
|
|
|
@param iterable paths: iterable containing paths to import modules from |
|
1184
|
|
|
@return list: A list of imported valid generator classes |
|
1185
|
|
|
""" |
|
1186
|
|
|
class_list = list() |
|
1187
|
|
|
for path in paths: |
|
1188
|
|
|
if not os.path.exists(path): |
|
1189
|
|
|
self.log.error('Unable to import generate methods from "{0}".\n' |
|
1190
|
|
|
'Path does not exist.'.format(path)) |
|
1191
|
|
|
continue |
|
1192
|
|
|
# Get all python modules to import from. |
|
1193
|
|
|
# The assumption is that in the path, there are *.py files, |
|
1194
|
|
|
# which contain only generator classes! |
|
1195
|
|
|
module_list = [name[:-3] for name in os.listdir(path) if |
|
1196
|
|
|
os.path.isfile(os.path.join(path, name)) and name.endswith('.py')] |
|
1197
|
|
|
|
|
1198
|
|
|
# append import path to sys.path |
|
1199
|
|
|
sys.path.append(path) |
|
1200
|
|
|
|
|
1201
|
|
|
# Go through all modules and create instances of each class found. |
|
1202
|
|
|
for module_name in module_list: |
|
1203
|
|
|
# import module |
|
1204
|
|
|
mod = importlib.import_module('{0}'.format(module_name)) |
|
1205
|
|
|
importlib.reload(mod) |
|
1206
|
|
|
# get all generator class references defined in the module |
|
1207
|
|
|
tmp_list = [m[1] for m in inspect.getmembers(mod, self.is_generator_class)] |
|
1208
|
|
|
# append to class_list |
|
1209
|
|
|
class_list.extend(tmp_list) |
|
1210
|
|
|
return class_list |
|
1211
|
|
|
|
|
1212
|
|
|
def __populate_method_dict(self, instance_list): |
|
1213
|
|
|
""" |
|
1214
|
|
|
Helper method to populate the dictionaries containing all references to callable generate |
|
1215
|
|
|
methods contained in generator instances passed to this method. |
|
1216
|
|
|
|
|
1217
|
|
|
@param list instance_list: List containing instances of generator classes |
|
1218
|
|
|
""" |
|
1219
|
|
|
self._generate_methods = dict() |
|
1220
|
|
|
for instance in instance_list: |
|
1221
|
|
|
for method_name, method_ref in inspect.getmembers(instance, inspect.ismethod): |
|
1222
|
|
|
if method_name.startswith('generate_'): |
|
1223
|
|
|
self._generate_methods[method_name[9:]] = method_ref |
|
1224
|
|
|
return |
|
1225
|
|
|
|
|
1226
|
|
|
def __populate_parameter_dict(self): |
|
1227
|
|
|
""" |
|
1228
|
|
|
Helper method to populate the dictionary containing all possible keyword arguments from all |
|
1229
|
|
|
generate methods. |
|
1230
|
|
|
""" |
|
1231
|
|
|
self._generate_method_parameters = dict() |
|
1232
|
|
|
for method_name, method in self._generate_methods.items(): |
|
1233
|
|
|
method_signature = inspect.signature(method) |
|
1234
|
|
|
param_dict = dict() |
|
1235
|
|
|
for name, param in method_signature.parameters.items(): |
|
1236
|
|
|
param_dict[name] = None if param.default is param.empty else param.default |
|
1237
|
|
|
|
|
1238
|
|
|
self._generate_method_parameters[method_name] = param_dict |
|
1239
|
|
|
return |
|
1240
|
|
|
|
|
1241
|
|
|
@staticmethod |
|
1242
|
|
|
def is_generator_class(obj): |
|
1243
|
|
|
""" |
|
1244
|
|
|
Helper method to check if an object is a valid generator class. |
|
1245
|
|
|
|
|
1246
|
|
|
@param object obj: object to check |
|
1247
|
|
|
@return bool: True if obj is a valid generator class, False otherwise |
|
1248
|
|
|
""" |
|
1249
|
|
|
if inspect.isclass(obj): |
|
1250
|
|
|
return PredefinedGeneratorBase in obj.__bases__ and len(obj.__bases__) == 1 |
|
1251
|
|
|
return False |
|
1252
|
|
|
|
|
1253
|
|
|
|
|
1254
|
|
|
|
|
1255
|
|
|
|