Completed
Pull Request — master (#384)
by
unknown
02:47
created

PulserInterface.set_digital_level()   A

Complexity

Conditions 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 21
rs 9.3142
1
# -*- coding: utf-8 -*-
2
3
"""
4
This file contains the Qudi hardware interface for pulsing devices.
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
24
import abc
25
from core.util.interfaces import InterfaceMetaclass, ScalarConstraint
26
27
28
class PulserInterface(metaclass=InterfaceMetaclass):
29
    """ Interface class to define the abstract controls and
30
    communication with all pulsing devices.
31
    """
32
33
    _modtype = 'PulserInterface'
34
    _modclass = 'interface'
35
36
    @abc.abstractmethod
37
    def get_constraints(self):
38
        """
39
        Retrieve the hardware constrains from the Pulsing device.
40
41
        @return constraints object: object with pulser constraints as attributes.
42
43
        Provides all the constraints (e.g. sample_rate, amplitude, total_length_bins,
44
        channel_config, ...) related to the pulse generator hardware to the caller.
45
46
            SEE PulserConstraints CLASS IN pulser_interface.py FOR AVAILABLE CONSTRAINTS!!!
47
48
        If you are not sure about the meaning, look in other hardware files to get an impression.
49
        If still additional constraints are needed, then they have to be added to the
50
        PulserConstraints class.
51
52
        Each scalar parameter is an ScalarConstraints object defined in cor.util.interfaces.
53
        Essentially it contains min/max values as well as min step size, default value and unit of
54
        the parameter.
55
56
        PulserConstraints.activation_config differs, since it contain the channel
57
        configuration/activation information of the form:
58
            {<descriptor_str>: <channel_set>,
59
             <descriptor_str>: <channel_set>,
60
             ...}
61
62
        If the constraints cannot be set in the pulsing hardware (e.g. because it might have no
63
        sequence mode) just leave it out so that the default is used (only zeros).
64
65
        # Example for configuration with default values:
66
        constraints = PulserConstraints()
67
68
        constraints.sample_rate.min = 10.0e6
69
        constraints.sample_rate.max = 12.0e9
70
        constraints.sample_rate.step = 10.0e6
71
        constraints.sample_rate.default = 12.0e9
72
73
        constraints.a_ch_amplitude.min = 0.02
74
        constraints.a_ch_amplitude.max = 2.0
75
        constraints.a_ch_amplitude.step = 0.001
76
        constraints.a_ch_amplitude.default = 2.0
77
78
        constraints.a_ch_offset.min = -1.0
79
        constraints.a_ch_offset.max = 1.0
80
        constraints.a_ch_offset.step = 0.001
81
        constraints.a_ch_offset.default = 0.0
82
83
        constraints.d_ch_low.min = -1.0
84
        constraints.d_ch_low.max = 4.0
85
        constraints.d_ch_low.step = 0.01
86
        constraints.d_ch_low.default = 0.0
87
88
        constraints.d_ch_high.min = 0.0
89
        constraints.d_ch_high.max = 5.0
90
        constraints.d_ch_high.step = 0.01
91
        constraints.d_ch_high.default = 5.0
92
93
        constraints.waveform_length.min = 80
94
        constraints.waveform_length.max = 64800000
95
        constraints.waveform_length.step = 1
96
        constraints.waveform_length.default = 80
97
98
        constraints.waveform_num.min = 1
99
        constraints.waveform_num.max = 32000
100
        constraints.waveform_num.step = 1
101
        constraints.waveform_num.default = 1
102
103
        constraints.sequence_num.min = 1
104
        constraints.sequence_num.max = 8000
105
        constraints.sequence_num.step = 1
106
        constraints.sequence_num.default = 1
107
108
        constraints.subsequence_num.min = 1
109
        constraints.subsequence_num.max = 4000
110
        constraints.subsequence_num.step = 1
111
        constraints.subsequence_num.default = 1
112
113
        # If sequencer mode is available then these should be specified
114
        constraints.repetitions.min = 0
115
        constraints.repetitions.max = 65539
116
        constraints.repetitions.step = 1
117
        constraints.repetitions.default = 0
118
119
        constraints.event_triggers = ['A', 'B']
120
        constraints.flags = ['A', 'B', 'C', 'D']
121
122
        constraints.sequence_steps.min = 0
123
        constraints.sequence_steps.max = 8000
124
        constraints.sequence_steps.step = 1
125
        constraints.sequence_steps.default = 0
126
127
        # the name a_ch<num> and d_ch<num> are generic names, which describe UNAMBIGUOUSLY the
128
        # channels. Here all possible channel configurations are stated, where only the generic
129
        # names should be used. The names for the different configurations can be customary chosen.
130
        activation_conf = OrderedDict()
131
        activation_conf['yourconf'] = {'a_ch1', 'd_ch1', 'd_ch2', 'a_ch2', 'd_ch3', 'd_ch4'}
132
        activation_conf['different_conf'] = {'a_ch1', 'd_ch1', 'd_ch2'}
133
        activation_conf['something_else'] = {'a_ch2', 'd_ch3', 'd_ch4'}
134
        constraints.activation_config = activation_conf
135
        """
136
        pass
137
138
    @abc.abstractmethod
139
    def pulser_on(self):
140
        """ Switches the pulsing device on.
141
142
        @return int: error code (0:OK, -1:error)
143
        """
144
        pass
145
146
    @abc.abstractmethod
147
    def pulser_off(self):
148
        """ Switches the pulsing device off.
149
150
        @return int: error code (0:OK, -1:error)
151
        """
152
        pass
153
154
    @abc.abstractmethod
155
    def load_waveform(self, load_dict):
156
        """ Loads a waveform to the specified channel of the pulsing device.
157
        For devices that have a workspace (i.e. AWG) this will load the waveform from the device
158
        workspace into the channel.
159
        For a device without mass memory this will make the waveform/pattern that has been
160
        previously written with self.write_waveform ready to play.
161
162
        @param load_dict:  dict|list, a dictionary with keys being one of the available channel
163
                                      index and values being the name of the already written
164
                                      waveform to load into the channel.
165
                                      Examples:   {1: rabi_ch1, 2: rabi_ch2} or
166
                                                  {1: rabi_ch2, 2: rabi_ch1}
167
                                      If just a list of waveform names if given, the channel
168
                                      association will be invoked from the channel
169
                                      suffix '_ch1', '_ch2' etc.
170
171
        @return dict: Dictionary containing the actually loaded waveforms per channel.
172
        """
173
        pass
174
175
    @abc.abstractmethod
176
    def load_sequence(self, sequence_name):
177
        """ Loads a sequence to the channels of the device in order to be ready for playback.
178
        For devices that have a workspace (i.e. AWG) this will load the sequence from the device
179
        workspace into the channels.
180
        For a device without mass memory this will make the waveform/pattern that has been
181
        previously written with self.write_waveform ready to play.
182
183
        @param sequence_name:  dict|list, a dictionary with keys being one of the available channel
184
                                      index and values being the name of the already written
185
                                      waveform to load into the channel.
186
                                      Examples:   {1: rabi_ch1, 2: rabi_ch2} or
187
                                                  {1: rabi_ch2, 2: rabi_ch1}
188
                                      If just a list of waveform names if given, the channel
189
                                      association will be invoked from the channel
190
                                      suffix '_ch1', '_ch2' etc.
191
192
        @return dict: Dictionary containing the actually loaded waveforms per channel.
193
        """
194
        pass
195
196
    @abc.abstractmethod
197
    def get_loaded_assets(self):
198
        """
199
        Retrieve the currently loaded asset names for each active channel of the device.
200
        The returned dictionary will have the channel numbers as keys.
201
        In case of loaded waveforms the dictionary values will be the waveform names.
202
        In case of a loaded sequence the values will be the sequence name appended by a suffix
203
        representing the track loaded to the respective channel (i.e. '<sequence_name>_1').
204
205
        @return (dict, str): Dictionary with keys being the channel number and values being the
206
                             respective asset loaded into the channel,
207
                             string describing the asset type ('waveform' or 'sequence')
208
        """
209
        pass
210
211
    @abc.abstractmethod
212
    def clear_all(self):
213
        """ Clears all loaded waveforms from the pulse generators RAM/workspace.
214
215
        @return int: error code (0:OK, -1:error)
216
        """
217
        pass
218
219
    @abc.abstractmethod
220
    def get_status(self):
221
        """ Retrieves the status of the pulsing hardware
222
223
        @return (int, dict): tuple with an interger value of the current status and a corresponding
224
                             dictionary containing status description for all the possible status
225
                             variables of the pulse generator hardware.
226
        """
227
        pass
228
229
    @abc.abstractmethod
230
    def get_sample_rate(self):
231
        """ Get the sample rate of the pulse generator hardware
232
233
        @return float: The current sample rate of the device (in Hz)
234
235
        Do not return a saved sample rate from an attribute, but instead retrieve the current
236
        sample rate directly from the device.
237
        """
238
        pass
239
240
    @abc.abstractmethod
241
    def set_sample_rate(self, sample_rate):
242
        """ Set the sample rate of the pulse generator hardware.
243
244
        @param float sample_rate: The sampling rate to be set (in Hz)
245
246
        @return float: the sample rate returned from the device (in Hz).
247
248
        Note: After setting the sampling rate of the device, use the actually set return value for
249
              further processing.
250
        """
251
        pass
252
253
    @abc.abstractmethod
254
    def get_analog_level(self, amplitude=None, offset=None):
255
        """ Retrieve the analog amplitude and offset of the provided channels.
256
257
        @param list amplitude: optional, if the amplitude value (in Volt peak to peak, i.e. the
258
                               full amplitude) of a specific channel is desired.
259
        @param list offset: optional, if the offset value (in Volt) of a specific channel is
260
                            desired.
261
262
        @return: (dict, dict): tuple of two dicts, with keys being the channel descriptor string
263
                               (i.e. 'a_ch1') and items being the values for those channels.
264
                               Amplitude is always denoted in Volt-peak-to-peak and Offset in volts.
265
266
        Note: Do not return a saved amplitude and/or offset value but instead retrieve the current
267
              amplitude and/or offset directly from the device.
268
269
        If nothing (or None) is passed then the levels of all channels will be returned. If no
270
        analog channels are present in the device, return just empty dicts.
271
272
        Example of a possible input:
273
            amplitude = ['a_ch1', 'a_ch4'], offset = None
274
        to obtain the amplitude of channel 1 and 4 and the offset of all channels
275
            {'a_ch1': -0.5, 'a_ch4': 2.0} {'a_ch1': 0.0, 'a_ch2': 0.0, 'a_ch3': 1.0, 'a_ch4': 0.0}
276
        """
277
        pass
278
279
    @abc.abstractmethod
280
    def set_analog_level(self, amplitude=None, offset=None):
281
        """ Set amplitude and/or offset value of the provided analog channel(s).
282
283
        @param dict amplitude: dictionary, with key being the channel descriptor string
284
                               (i.e. 'a_ch1', 'a_ch2') and items being the amplitude values
285
                               (in Volt peak to peak, i.e. the full amplitude) for the desired
286
                               channel.
287
        @param dict offset: dictionary, with key being the channel descriptor string
288
                            (i.e. 'a_ch1', 'a_ch2') and items being the offset values
289
                            (in absolute volt) for the desired channel.
290
291
        @return (dict, dict): tuple of two dicts with the actual set values for amplitude and
292
                              offset for ALL channels.
293
294
        If nothing is passed then the command will return the current amplitudes/offsets.
295
296
        Note: After setting the amplitude and/or offset values of the device, use the actual set
297
              return values for further processing.
298
        """
299
        pass
300
301
    @abc.abstractmethod
302
    def get_digital_level(self, low=None, high=None):
303
        """ Retrieve the digital low and high level of the provided/all channels.
304
305
        @param list low: optional, if the low value (in Volt) of a specific channel is desired.
306
        @param list high: optional, if the high value (in Volt) of a specific channel is desired.
307
308
        @return: (dict, dict): tuple of two dicts, with keys being the channel descriptor strings
309
                               (i.e. 'd_ch1', 'd_ch2') and items being the values for those
310
                               channels. Both low and high value of a channel is denoted in volts.
311
312
        Note: Do not return a saved low and/or high value but instead retrieve
313
              the current low and/or high value directly from the device.
314
315
        If nothing (or None) is passed then the levels of all channels are being returned.
316
        If no digital channels are present, return just an empty dict.
317
318
        Example of a possible input:
319
            low = ['d_ch1', 'd_ch4']
320
        to obtain the low voltage values of digital channel 1 an 4. A possible answer might be
321
            {'d_ch1': -0.5, 'd_ch4': 2.0} {'d_ch1': 1.0, 'd_ch2': 1.0, 'd_ch3': 1.0, 'd_ch4': 4.0}
322
        Since no high request was performed, the high values for ALL channels are returned (here 4).
323
        """
324
        pass
325
326
    @abc.abstractmethod
327
    def set_digital_level(self, low=None, high=None):
328
        """ Set low and/or high value of the provided digital channel.
329
330
        @param dict low: dictionary, with key being the channel descriptor string
331
                         (i.e. 'd_ch1', 'd_ch2') and items being the low values (in volt) for the
332
                         desired channel.
333
        @param dict high: dictionary, with key being the channel descriptor string
334
                          (i.e. 'd_ch1', 'd_ch2') and items being the high values (in volt) for the
335
                          desired channel.
336
337
        @return (dict, dict): tuple of two dicts where first dict denotes the current low value and
338
                              the second dict the high value for ALL digital channels.
339
                              Keys are the channel descriptor strings (i.e. 'd_ch1', 'd_ch2')
340
341
        If nothing is passed then the command will return the current voltage levels.
342
343
        Note: After setting the high and/or low values of the device, use the actual set return
344
              values for further processing.
345
        """
346
        pass
347
348
    @abc.abstractmethod
349
    def get_active_channels(self, ch=None):
350
        """ Get the active channels of the pulse generator hardware.
351
352
        @param list ch: optional, if specific analog or digital channels are needed to be asked
353
                        without obtaining all the channels.
354
355
        @return dict:  where keys denoting the channel string and items boolean expressions whether
356
                       channel are active or not.
357
358
        Example for an possible input (order is not important):
359
            ch = ['a_ch2', 'd_ch2', 'a_ch1', 'd_ch5', 'd_ch1']
360
        then the output might look like
361
            {'a_ch2': True, 'd_ch2': False, 'a_ch1': False, 'd_ch5': True, 'd_ch1': False}
362
363
        If no parameter (or None) is passed to this method all channel states will be returned.
364
        """
365
        pass
366
367
    @abc.abstractmethod
368
    def set_active_channels(self, ch=None):
369
        """ Set the active channels for the pulse generator hardware.
370
371
        @param dict ch: dictionary with keys being the analog or digital string generic names for
372
                        the channels (i.e. 'd_ch1', 'a_ch2') with items being a boolean value.
373
                        True: Activate channel, False: Deactivate channel
374
375
        @return dict: with the actual set values for ALL active analog and digital channels
376
377
        If nothing is passed then the command will simply return the unchanged current state.
378
379
        Note: After setting the active channels of the device,
380
              use the returned dict for further processing.
381
382
        Example for possible input:
383
            ch={'a_ch2': True, 'd_ch1': False, 'd_ch3': True, 'd_ch4': True}
384
        to activate analog channel 2 digital channel 3 and 4 and to deactivate
385
        digital channel 1.
386
387
        The hardware itself has to handle, whether separate channel activation is possible.
388
        """
389
        pass
390
391
    @abc.abstractmethod
392
    def write_waveform(self, name, analog_samples, digital_samples, is_first_chunk, is_last_chunk,
393
                       total_number_of_samples):
394
        """
395
        Write a new waveform or append samples to an already existing waveform on the device memory.
396
        The flags is_first_chunk and is_last_chunk can be used as indicator if a new waveform should
397
        be created or if the write process to a waveform should be terminated.
398
399
        @param name: str, the name of the waveform to be created/append to
400
        @param analog_samples: numpy.ndarray of type float32 containing the voltage samples
401
        @param digital_samples: numpy.ndarray of type bool containing the marker states
402
                                (if analog channels are active, this must be the same length as
403
                                analog_samples)
404
        @param is_first_chunk: bool, flag indicating if it is the first chunk to write.
405
                                     If True this method will create a new empty wavveform.
406
                                     If False the samples are appended to the existing waveform.
407
        @param is_last_chunk: bool, flag indicating if it is the last chunk to write.
408
                                    Some devices may need to know when to close the appending wfm.
409
        @param total_number_of_samples: int, The number of sample points for the entire waveform
410
                                        (not only the currently written chunk)
411
412
        @return: (int, list) number of samples written (-1 indicates failed process) and list of
413
                             created waveform names
414
        """
415
        pass
416
417
    @abc.abstractmethod
418
    def write_sequence(self, name, sequence_parameters):
419
        """
420
        Write a new sequence on the device memory.
421
422
        @param name: str, the name of the waveform to be created/append to
423
        @param sequence_parameters: dict, dictionary containing the parameters for a sequence
424
425
        @return: int, number of sequence steps written (-1 indicates failed process)
426
        """
427
        pass
428
429
    @abc.abstractmethod
430
    def get_waveform_names(self):
431
        """ Retrieve the names of all uploaded waveforms on the device.
432
433
        @return list: List of all uploaded waveform name strings in the device workspace.
434
        """
435
        pass
436
437
    @abc.abstractmethod
438
    def get_sequence_names(self):
439
        """ Retrieve the names of all uploaded sequence on the device.
440
441
        @return list: List of all uploaded sequence name strings in the device workspace.
442
        """
443
        pass
444
445
    @abc.abstractmethod
446
    def delete_waveform(self, waveform_name):
447
        """ Delete the waveform with name "waveform_name" from the device memory.
448
449
        @param str waveform_name: The name of the waveform to be deleted
450
                                  Optionally a list of waveform names can be passed.
451
452
        @return list: a list of deleted waveform names.
453
        """
454
        pass
455
456
    @abc.abstractmethod
457
    def delete_sequence(self, sequence_name):
458
        """ Delete the sequence with name "sequence_name" from the device memory.
459
460
        @param str sequence_name: The name of the sequence to be deleted
461
                                  Optionally a list of sequence names can be passed.
462
463
        @return list: a list of deleted sequence names.
464
        """
465
        pass
466
467
    @abc.abstractmethod
468
    def get_interleave(self):
469
        """ Check whether Interleave is ON or OFF in AWG.
470
471
        @return bool: True: ON, False: OFF
472
473
        Will always return False for pulse generator hardware without interleave.
474
        """
475
        pass
476
477
    @abc.abstractmethod
478
    def set_interleave(self, state=False):
479
        """ Turns the interleave of an AWG on or off.
480
481
        @param bool state: The state the interleave should be set to
482
                           (True: ON, False: OFF)
483
484
        @return bool: actual interleave status (True: ON, False: OFF)
485
486
        Note: After setting the interleave of the device, retrieve the
487
              interleave again and use that information for further processing.
488
489
        Unused for pulse generator hardware other than an AWG.
490
        """
491
        pass
492
493
    @abc.abstractmethod
494
    def write(self, command):
495
        """ Sends a command string to the device.
496
497
        @param string command: string containing the command
498
499
        @return int: error code (0:OK, -1:error)
500
        """
501
        pass
502
503
    @abc.abstractmethod
504
    def query(self, question):
505
        """ Asks the device a 'question' and receive and return an answer from it.
506
507
        @param string question: string containing the command
508
509
        @return string: the answer of the device to the 'question' in a string
510
        """
511
        pass
512
513
    @abc.abstractmethod
514
    def reset(self):
515
        """ Reset the device.
516
517
        @return int: error code (0:OK, -1:error)
518
        """
519
        pass
520
521
    @abc.abstractmethod
522
    def has_sequence_mode(self):
523
        """ Asks the pulse generator whether sequence mode exists.
524
525
        @return: bool, True for yes, False for no.
526
        """
527
        pass
528
529
530
class PulserConstraints:
531
    def __init__(self):
532
        # sample rate, i.e. the time base of the pulser
533
        self.sample_rate = ScalarConstraint(unit='Hz')
534
        # The peak-to-peak amplitude and voltage offset of the analog channels
535
        self.a_ch_amplitude = ScalarConstraint(unit='Vpp')
536
        self.a_ch_offset = ScalarConstraint(unit='V')
537
        # Low and high voltage level of the digital channels
538
        self.d_ch_low = ScalarConstraint(unit='V')
539
        self.d_ch_high = ScalarConstraint(unit='V')
540
        # length of the created waveform in samples
541
        self.waveform_length = ScalarConstraint(unit='Samples')
542
        # number of waveforms/sequences to put in a single asset (sequence mode)
543
        self.waveform_num = ScalarConstraint(unit='#')
544
        self.sequence_num = ScalarConstraint(unit='#')
545
        self.subsequence_num = ScalarConstraint(unit='#')
546
        # Sequence parameters
547
        self.sequence_steps = ScalarConstraint(unit='#', min=0)
548
        self.repetitions = ScalarConstraint(unit='#')
549
        self.event_triggers = list()
550
        self.flags = list()
551
552
553
        self.activation_config = dict()
554