From 8ba73ff07aebc8ce07e73256d4b00a9c09e18f2f Mon Sep 17 00:00:00 2001 From: Jeremy Stevens Date: Fri, 26 Feb 2021 16:14:20 +0100 Subject: [PATCH 1/7] New pulse shapes: Gaussian, tanh --- exopy_pulses/pulses/manifest.enaml | 9 ++ exopy_pulses/pulses/shapes/gaussian_shape.py | 86 ++++++++++++++++++ exopy_pulses/pulses/shapes/tanh_shape.py | 88 +++++++++++++++++++ .../shapes/views/gaussian_shape_view.enaml | 42 +++++++++ .../pulses/shapes/views/tanh_shape_view.enaml | 42 +++++++++ exopy_pulses/testing/fixtures.py | 3 +- 6 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 exopy_pulses/pulses/shapes/gaussian_shape.py create mode 100644 exopy_pulses/pulses/shapes/tanh_shape.py create mode 100644 exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml create mode 100644 exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml diff --git a/exopy_pulses/pulses/manifest.enaml b/exopy_pulses/pulses/manifest.enaml index dce3427..9abf95b 100644 --- a/exopy_pulses/pulses/manifest.enaml +++ b/exopy_pulses/pulses/manifest.enaml @@ -432,6 +432,15 @@ enamldef PulsesManagerManifest(PluginManifest): Shape: shape = 'slope_shape:SlopeShape' view = 'views.slope_shape_view:SlopeShapeView' + Shape: + shape = 'gaussian_shape:GaussianShape' + view = 'views.gaussian_shape_view:GaussianShapeView' + Shape: + shape = 'tanh_shape:TanhShape' + view = 'views.tanh_shape_view:TanhShapeView' + Shape: + shape = 'arbitrary_shape:ArbitraryShape' + view = 'views.arbitrary_shape_view:ArbitraryShapeView' PulsesBuildingDependenciesExtension: pass diff --git a/exopy_pulses/pulses/shapes/gaussian_shape.py b/exopy_pulses/pulses/shapes/gaussian_shape.py new file mode 100644 index 0000000..3a7fadd --- /dev/null +++ b/exopy_pulses/pulses/shapes/gaussian_shape.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Most basic shape for an analogical pulse. + +""" +from numbers import Real + +import numpy as np +from atom.api import Unicode + +from ..utils.validators import Feval + +from .base_shape import AbstractShape + + +class GaussianShape(AbstractShape): + """ Gaussian pulse with a variable amplitude and sigma. + + """ + #: Amplitude of the pulse this should be a number between -1.0 and 1.0 + amplitude = Unicode('1.0').tag(pref=True, feval=Feval(types=Real)) + sigma = Unicode('10.0').tag(pref=True, feval=Feval(types=Real)) + + def eval_entries(self, root_vars, sequence_locals, missing, errors): + """ Evaluate the amplitude of the pulse. + + Parameters + ---------- + root_vars : dict + Global variables. As shapes and modulation cannot update them an + empty dict is passed. + + sequence_locals : dict + Known locals variables for the pulse sequence. + + missing : set + Set of variables missing to evaluate some entries in the sequence. + + errors : dict + Errors which occurred when trying to compile the pulse sequence. + + Returns + ------- + result : bool + Flag indicating whether or not the evaluation succeeded. + + """ + res = super(GaussianShape, self).eval_entries(root_vars, sequence_locals, + missing, errors) + + if res: + if not -1.0 <= self._cache['amplitude'] <= 1.0: + msg = 'Shape amplitude must be between -1 and 1.' + errors[self.format_error_id('amplitude')] = msg + res = False + + return res + + def compute(self, time, unit): + """ Computes the shape of the pulse at a given time. + + Parameters + ---------- + time : ndarray + Times at which to compute the modulation. + + unit : str + Unit in which the time is expressed. + + Returns + ------- + shape : ndarray + Amplitude of the pulse. + + """ + amp = self._cache['amplitude'] + sigma = self._cache['sigma'] + t0 = (time[0]+time[-1])/2 + pulse_shape = [amp*np.exp(-(t-t0)**2/2/sigma**2) for t in time] + return np.asarray(pulse_shape) diff --git a/exopy_pulses/pulses/shapes/tanh_shape.py b/exopy_pulses/pulses/shapes/tanh_shape.py new file mode 100644 index 0000000..444d6cd --- /dev/null +++ b/exopy_pulses/pulses/shapes/tanh_shape.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Most basic shape for an analogical pulse. + +""" +from numbers import Real + +import numpy as np +from atom.api import Unicode + +from ..utils.validators import Feval + +from .base_shape import AbstractShape + + +class TanhShape(AbstractShape): + """ Atan pulse with a variable amplitude and sigma. + + """ + #: Amplitude of the pulse this should be a number between -1.0 and 1.0 + amplitude = Unicode('1.0').tag(pref=True, feval=Feval(types=Real)) + sigma = Unicode('10.0').tag(pref=True, feval=Feval(types=Real)) + + def eval_entries(self, root_vars, sequence_locals, missing, errors): + """ Evaluate the amplitude of the pulse. + + Parameters + ---------- + root_vars : dict + Global variables. As shapes and modulation cannot update them an + empty dict is passed. + + sequence_locals : dict + Known locals variables for the pulse sequence. + + missing : set + Set of variables missing to evaluate some entries in the sequence. + + errors : dict + Errors which occurred when trying to compile the pulse sequence. + + Returns + ------- + result : bool + Flag indicating whether or not the evaluation succeeded. + + """ + res = super(TanhShape, self).eval_entries(root_vars, sequence_locals, + missing, errors) + + if res: + if not -1.0 <= self._cache['amplitude'] <= 1.0: + msg = 'Shape amplitude must be between -1 and 1.' + errors[self.format_error_id('amplitude')] = msg + res = False + + return res + + def compute(self, time, unit): + """ Computes the shape of the pulse at a given time. + + Parameters + ---------- + time : ndarray + Times at which to compute the modulation. + + unit : str + Unit in which the time is expressed. + + Returns + ------- + shape : ndarray + Amplitude of the pulse. + + """ + amp = self._cache['amplitude'] + sigma = self._cache['sigma'] + t0 = (time[0]+time[-1])/2 + duration = time[-1]-time[0] + func = lambda t:(0.5+0.5*np.tanh((t+duration/2)/sigma*2*np.pi-np.pi)) + pulse_shape = [amp*func(t-t0)*func(-(t-t0)) for t in time] + return np.asarray(pulse_shape) diff --git a/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml b/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml new file mode 100644 index 0000000..e6d5140 --- /dev/null +++ b/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""View for the Gaussian Shape. + +""" +from enaml.layout.api import hbox, vbox, align +from enaml.widgets.api import (Label, GroupBox) + +from exopy.utils.widgets.qt_completers import QtLineCompleter +from ...utils.entry_eval import EVALUATER_TOOLTIP + +from .base_shape_view import AbstractShapeView + +enamldef GaussianShapeView(AbstractShapeView): view: + """ View for a Gaussian pulse. + + """ + GroupBox: + title = 'Gaussian' + constraints = [hbox(amp_lab, amp_val, sigma_lab, sigma_val), + align('v_center', amp_lab, amp_val, sigma_lab, sigma_val)] + + Label: amp_lab: + text = 'Amplitude' + QtLineCompleter: amp_val: + text := shape.amplitude + entries_updater = item.parent.get_accessible_vars + tool_tip = ('Relative amplitude of the pulse (should be between ' + '-1.0 and 1.0)') + + Label: sigma_lab: + text = 'Sigma' + QtLineCompleter: sigma_val: + text := shape.sigma + entries_updater = item.parent.get_accessible_vars + tool_tip = ('Sigma of gaussian pulse, units are AWG context units') diff --git a/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml b/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml new file mode 100644 index 0000000..705b8b0 --- /dev/null +++ b/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""View for the Gaussian Shape. + +""" +from enaml.layout.api import hbox, vbox, align +from enaml.widgets.api import (Label, GroupBox) + +from exopy.utils.widgets.qt_completers import QtLineCompleter +from ...utils.entry_eval import EVALUATER_TOOLTIP + +from .base_shape_view import AbstractShapeView + +enamldef TanhShapeView(AbstractShapeView): view: + """ View for a Tanh pulse. + + """ + GroupBox: + title = 'Tanh' + constraints = [hbox(amp_lab, amp_val, sigma_lab, sigma_val), + align('v_center', amp_lab, amp_val, sigma_lab, sigma_val)] + + Label: amp_lab: + text = 'Amplitude' + QtLineCompleter: amp_val: + text := shape.amplitude + entries_updater = item.parent.get_accessible_vars + tool_tip = ('Relative amplitude of the pulse (should be between ' + '-1.0 and 1.0)') + + Label: sigma_lab: + text = 'Sigma' + QtLineCompleter: sigma_val: + text := shape.sigma + entries_updater = item.parent.get_accessible_vars + tool_tip = ('Sigma of tanh pulse, basically raising time, units are AWG context units') diff --git a/exopy_pulses/testing/fixtures.py b/exopy_pulses/testing/fixtures.py index 39e4200..b2b93cd 100644 --- a/exopy_pulses/testing/fixtures.py +++ b/exopy_pulses/testing/fixtures.py @@ -76,8 +76,9 @@ def template_sequence(pulses_plugin): """ from exopy_pulses.pulses.pulse import Pulse from exopy_pulses.pulses.sequences.base_sequences import (RootSequence, - BaseSequence) + BaseSequence) from exopy_pulses.pulses.shapes.square_shape import SquareShape + from exopy_pulses.pulses.shapes.gaussian_shape import GaussianShape from exopy_pulses.pulses.contexts.template_context import TemplateContext root = RootSequence() From 107811496aeab1cef50feda8c11817d1dd11b0a8 Mon Sep 17 00:00:00 2001 From: Jeremy Stevens Date: Fri, 26 Feb 2021 16:17:16 +0100 Subject: [PATCH 2/7] Added pulse sequence viewer. Added ability to create pulse loop. --- exopy_pulses/tasks/manifest.enaml | 8 + .../tasks/instrs/transfer_pulse_loop_task.py | 302 +++++++++++++ .../tasks/instrs/transfer_sequence_task.py | 49 +++ .../views/transfer_pulse_loop_task_view.enaml | 410 ++++++++++++++++++ .../views/transfer_sequence_task_view.enaml | 52 ++- 5 files changed, 816 insertions(+), 5 deletions(-) create mode 100644 exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py create mode 100644 exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml diff --git a/exopy_pulses/tasks/manifest.enaml b/exopy_pulses/tasks/manifest.enaml index 685e16e..450bcb6 100644 --- a/exopy_pulses/tasks/manifest.enaml +++ b/exopy_pulses/tasks/manifest.enaml @@ -31,3 +31,11 @@ enamldef PulsesTasksManifest(PluginManifest): # Way to declare instrument dependencies without specifying # any instrument. instruments = [None] + path = 'exopy_pulses.tasks.tasks.instrs' + Task: + task = 'transfer_pulse_loop_task:TransferPulseLoopTask' + view = ('views.transfer_pulse_loop_task_view:' + 'TransferPulseLoopView') + # Way to declare instrument dependencies without specifying + # any instrument. + instruments = [None] diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py new file mode 100644 index 0000000..2411b8d --- /dev/null +++ b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2016 by ExopyHqcLegacy Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Task to transfer a sequence on an AWG. + +""" +import os +from traceback import format_exc +from pprint import pformat +from collections import OrderedDict + +import numpy as np +from atom.api import (Value, Unicode, Float, Typed, Bool, set_default, Enum) + +from exopy.tasks.api import (InstrumentTask) +from exopy.utils.atom_util import ordered_dict_from_pref, ordered_dict_to_pref + + +class TransferPulseLoopTask(InstrumentTask): + """Build and transfer a pulse sequence to an instrument. + + """ + + #: Sequence path for the case of sequence simply referenced. + sequence_path = Unicode().tag(pref=True) + + #: Time stamp of the last modification of the sequence file. + sequence_timestamp = Float().tag(pref=True) + + #: Sequence of pulse to compile and transfer to the instrument. + sequence = Value() + + #: Global variable to use for the sequence. + sequence_vars = Typed(OrderedDict, ()).tag(pref=(ordered_dict_to_pref, + ordered_dict_from_pref)) + + #: operation mode of the awg + operation = Enum('Clean, Load & Enqueue', 'Clean & Load', 'Load', 'Load & Enqueue all').tag(pref=True) + + #: Loop variables: channels on which the loop will be done, loop parameters + #: names, start value, stop value and number of points per loop + + loop_start = Unicode('0').tag(pref=True) + + loop_stop = Unicode('0').tag(pref=True) + + loop_points = Unicode('2').tag(pref=True) + + #: run mode of the awg + run_mode = Enum('Ext Trig', 'Int Trig', 'Continuous').tag(pref=True) + + #: Internal trigger period in mus + trigger_period = Unicode('20').tag(pref=True) + + parameters = Typed(OrderedDict, ()).tag(pref=[ordered_dict_to_pref, + ordered_dict_from_pref]) + + database_entries = set_default({'num_loop': 1}) + +# def check(self, *args, **kwargs): +# """Check that the sequence can be compiled. +# +# """ +# test, traceback = super(TransferPulseLoopTask, +# self).check(*args, **kwargs) +# err_path = self.path + '/' + self.name + '-' +# +# msg = 'Failed to evaluate {} ({}): {}' +# seq = self.sequence +# for k, v in self.sequence_vars.items(): +# try: +# seq.external_vars[k] = self.format_and_eval_string(v) +# except Exception: +# test = False +# traceback[err_path+k] = msg.format(k, v, format_exc()) +# +# if not test: +# return test, traceback +# +# context = seq.context +# res, infos, errors = context.compile_and_transfer_sequence(seq) +# +# if not res: +# traceback[err_path+'compil'] = errors +# return False, traceback +# +# for k, v in infos.items(): +# self.write_in_database(k, v) +# +# if self.sequence_path: +# if not (self.sequence_timestamp == +# os.path.getmtime(self.sequence_path)): +# msg = 'The sequence is outdated, consider refreshing it.' +# traceback[err_path+'outdated'] = msg +# +# return test, traceback + + def perform(self): + """Compile the sequence. + + """ + operation = self.operation + seq = self.sequence + context = seq.context + if self.run_mode=='Int Trig': + internal_trigger = True + else: + internal_trigger = False + + self.driver.internal_trigger = internal_trigger + if internal_trigger: + self.driver.internal_trigger_period = int(float(self.trigger_period) * 1000) + + self.driver.clear_all_sequences() + if operation in ['Clean, Load & Enqueue', 'Clean & Load']: + self.driver.delete_all_waveforms() + first_index = 1 +# print("First index shoud be %d"%first_index) + else: + n_remaining_wf = self.driver.get_waveform_number() +# print('Remaining of waveforms = '+str(n_remaining_wf)) + last_wf = str(self.driver.get_waveform_name(n_remaining_wf-1)) + last_wf = last_wf.split('_') + first_index = int(last_wf[1])+1 +# print("First index shoud be %d"%first_index) + + _used_channels = [] + + loops = [] + name_parameters = [] + n_loops = len(self.parameters) +# print(n_loops) + if n_loops>0: + context.run_after_transfer = False + context.select_after_transfer = False + self.driver.run_mode = 'SEQUENCE' + for params in self.parameters.items(): + loop_start = float(self.format_and_eval_string(params[1][0])) + loop_stop = float(self.format_and_eval_string(params[1][1])) + loop_points = int(self.format_and_eval_string(params[1][2])) + loops.append(np.linspace(loop_start, loop_stop, loop_points)) + name_parameters.append(params[0]) + self.write_in_database(params[0]+'_loop', np.linspace(loop_start, loop_stop, loop_points)) + + loop_values = np.moveaxis(np.array(np.meshgrid(*loops)),0,-1).reshape((-1,n_loops)) + if operation=='Clean, Load & Enqueue': + self.write_in_database('num_loop', len(loop_values)) + for nn, loop_value in enumerate(loop_values): + for ii, name_parameter in enumerate(name_parameters): + self.write_in_database(name_parameter, loop_value[ii]) + for k, v in self.sequence_vars.items(): + seq.external_vars[k] = self.format_and_eval_string(v) + # context.sequence_name = '{}_{}'.format(seq_name_0, nn+1) #RL replaced, caused bug + context.sequence_name = '{}_{}'.format('', nn+first_index) +# print(context.sequence_name) + res, infos, errors = context.compile_and_transfer_sequence( + seq, + driver=self.driver) + if operation=='Clean, Load & Enqueue': + for cc in range(4): + _seq = 'sequence_ch'+str(cc+1) + if infos[_seq]: + self.driver.get_channel(cc+1).set_sequence_pos(infos[_seq], + nn+1) + _used_channels.append(cc+1) + self.driver.set_jump_pos(nn+1, 1) + self.driver.set_goto_pos(len(loop_values), 1) + for cc in set(_used_channels): + self.driver.get_channel(cc).output_state = 'on' + + if not res: + raise Exception('Failed to compile sequence :\n' + + pformat(errors)) + self.write_in_database(name_parameter, loop_value[ii]) + + else: + for k, v in self.sequence_vars.items(): + seq.external_vars[k] = self.format_and_eval_string(v) + if self.run_mode=='Continuous': + self.driver.run_mode = 'CONT' + else: + self.driver.run_mode = 'TRIG' + context.sequence_name = '{}_{}'.format('', first_index) + res, infos, errors = context.compile_and_transfer_sequence(seq, + self.driver) + + if not res: + raise Exception('Failed to compile sequence :\n' + + pformat(errors)) + + for k, v in infos.items(): + self.write_in_database(k, v) + + if operation=='Load & Enqueue all': + n_wf = self.driver.get_waveform_number()-25 + channels_to_turn_on = set() + for ii in range(n_wf): + index = ii+25 + current_wf = str(self.driver.get_waveform_name(index)) + current_ch = int(current_wf[-1]) + current_index = int(current_wf.split('_')[1]) + channels_to_turn_on.add(current_ch) + self.driver.get_channel(current_ch).set_sequence_pos(current_wf, + current_index) + self.driver.set_jump_pos(current_index, 1) +# print(channels_to_turn_on) + self.driver.set_goto_pos(current_index, 1) + self.write_in_database('num_loop', current_index) + for cc in channels_to_turn_on: + self.driver.get_channel(cc).output_state = 'on' + + def register_preferences(self): + """Register the task preferences into the preferences system. + + """ + super(TransferPulseLoopTask, self).register_preferences() + + if self.sequence: + self.preferences['sequence'] =\ + self.sequence.preferences_from_members() + + update_preferences_from_members = register_preferences + + def traverse(self, depth=-1): + """Reimplemented to also yield the sequence + + """ + infos = super(TransferPulseLoopTask, self).traverse(depth) + + for i in infos: + yield i + + for item in self.sequence.traverse(): + yield item + + @classmethod + def build_from_config(cls, config, dependencies): + """Rebuild the task and the sequence from a config file. + + """ + builder = cls.mro()[1].build_from_config.__func__ + task = builder(cls, config, dependencies) + + if 'sequence' in config: + pulse_dep = dependencies['exopy.pulses.item'] + builder = pulse_dep['exopy_pulses.RootSequence'] + conf = config['sequence'] + seq = builder.build_from_config(conf, dependencies) + task.sequence = seq + + return task + + def _post_setattr_sequence(self, old, new): + """Set up n observer on the sequence context to properly update the + database entries. + + """ + entries = self.database_entries.copy() + if old: + old.unobserve('context', self._update_database_entries) + if old.context: + for k in old.context.list_sequence_infos(): + del entries[k] + if new: + new.observe('context', self._update_database_entries) + if new.context: + entries.update(new.context.list_sequence_infos()) + + if entries != self.database_entries: + self.database_entries = entries + + def _post_setattr_parameters(self, old, new): + """Observer keeping the database entries in sync with the declared + definitions. + + """ + entries = self.database_entries.copy() + for e in old: + del entries[e] + del entries[e+'_loop'] + for e in new: + entries.update({key: 0.0 for key in new}) + entries.update({key+'_loop': 0.0 for key in new}) + self.database_entries = entries + + def _update_database_entries(self, change): + """Reflect in the database the sequence infos of the context. + + """ + entries = self.database_entries.copy() + if change.get('oldvalue'): + for k in change['oldvalue'].list_sequence_infos(): + del entries[k] + if change['value']: + context = change['value'] + entries.update(context.list_sequence_infos()) + self.database_entries = entries diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py index 45a0908..e3c9eaf 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py @@ -18,6 +18,9 @@ from exopy.utils.atom_util import ordered_dict_from_pref, ordered_dict_to_pref from exopy.utils.traceback import format_exc +import matplotlib.pyplot as plt +import numpy as np + class TransferPulseSequenceTask(InstrumentTask): """Build and transfer a pulse sequence to an instrument. @@ -35,6 +38,7 @@ class TransferPulseSequenceTask(InstrumentTask): #: Global variable to use for the sequence. sequence_vars = Typed(OrderedDict, ()).tag(pref=(ordered_dict_to_pref, ordered_dict_from_pref)) + def check(self, *args, **kwargs): """Check that the sequence can be compiled. @@ -87,6 +91,7 @@ def perform(self): for k, v in self.sequence_vars.items(): seq.external_vars[k] = self.format_and_eval_string(v) +# self.driver.run_mode = 'CONT' res, infos, errors = context.compile_and_transfer_sequence(seq, self.driver) if not res: @@ -95,6 +100,50 @@ def perform(self): for k, v in infos.items(): self.write_in_database(k, v) + + def compile_and_plot(self, variables): + """Compile the sequence and plot it. + + """ + + seq = self.sequence + context = seq.context + for k, v in variables.items(): + seq.external_vars[k] = self.format_and_eval_string(v) + + table, marker1, marker2, errors = context.compile_sequence(seq) + if not table: + raise Exception('Failed to compile sequence :\n' + + pformat(errors)) + freq = context.list_sequence_infos()['sampling_frequency'] + + channel_num = len(table) + #fig, axs = plt.subplots(4,1, figsize=(15, 8)) + fig, axs = plt.subplots(channel_num,1, figsize=(15, 2.5*channel_num), + sharex=True) + fig.subplots_adjust(hspace = 0.5, wspace=.001) + + x = np.arange(len(table[list(table.keys())[0]]))/freq*10**6 + + if len(list(table.keys())) == 1: + key = list(table.keys())[0] + axs.plot(x,table[key], label = 'wvfm') + axs.plot(x,marker1[key], label = 'M1') + axs.plot(x,marker2[key], label = 'M2') + axs.set_xlabel('time (us)') + axs.set_ylabel(key) + axs.axis(xmin = 0, xmax = x[-1],ymin = -1.2, ymax = 1.2) + axs.legend(loc=6) + else: + for i, key in enumerate(np.sort(list(table.keys()))): + axs[i].plot(x,table[key], label = 'wvfm') + axs[i].plot(x,marker1[key], label = 'M1') + axs[i].plot(x,marker2[key], label = 'M2') + axs[i].set_xlabel('time (us)') + axs[i].set_ylabel(key) + axs[i].axis(xmin = 0, xmax = x[-1],ymin = -1.2, ymax = 1.2) + axs[i].legend(loc=6) + return fig def register_preferences(self): """Register the task preferences into the preferences system. diff --git a/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml b/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml new file mode 100644 index 0000000..893dfdf --- /dev/null +++ b/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml @@ -0,0 +1,410 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""View of the task used to transfer a pulse sequence on an arbitrary +waveform generator. + +""" +import os +import logging +from traceback import format_exc +from textwrap import fill +from inspect import cleandoc +from collections import OrderedDict + +from enaml.styling import StyleSheet, Style, Setter +from enaml.layout.api import factory, hbox, vbox, spacer, align +from enaml.core.api import Conditional, Include +from enaml.widgets.api import (GroupBox, Label, Field, ObjectCombo, CheckBox, + Notebook, Page, PushButton, Menu, Action, + FileDialogEx, Container) +from enaml.stdlib.message_box import information, question, warning + +from exopy.utils.widgets.qt_completers import QtLineCompleter +from exopy.utils.widgets.dict_editor import DictEditor +from exopy.utils.widgets.dict_list_editor import DictListEditor +from exopy.tasks.tasks.instr_view import InstrTaskView + +from exopy_pulses.pulses.utils.entry_eval import EVALUATER_TOOLTIP +from exopy_pulses.pulses.utils.sequences_io import save_sequence_prefs +from exopy_pulses.pulses.sequences.views.base_sequences_views\ + import instantiate_context_view + +def fc(txt): + return fill(cleandoc(txt)) + +enamldef RedButton(StyleSheet): + """Style sheet allowing to use a large font for the save confirmation popup + + """ + Style: + style_class = 'red-button' + Setter: + field = 'background' + value = 'red' + + +enamldef VarEditor(Container): + """ Fields allowing top edit the global variables of a sequence. + + """ + #: Model object describing the key/value pair. + attr model + + padding = 1 + constraints = [hbox(lab, val), align('v_center', lab, val)] + Label: lab: + hug_width = 'strong' + text << model.key + QtLineCompleter: val: + text := model.value + entries_updater << model.task.list_accessible_database_entries + tool_tip = EVALUATER_TOOLTIP + +enamldef LoopEditor(Container): + """ Fields allowing to edit the sweep of a sequence. + + """ + #: Model object describing the key/value pair. + attr model + + padding = 1 + constraints = [hbox(loop_name, + param_start, param_start_val, param_stop, + param_stop_val, param_points, param_points_val), align('v_center', loop_name, param_start)] +# Label: lab: +# hug_width = 'strong' +# text << model.key +# QtLineCompleter: val: +# text := model.value +# entries_updater << model.task.list_accessible_database_entries +# tool_tip = EVALUATER_TOOLTIP + + Field: loop_name: + text := model.key +# print(model) +# items << list(task.sequence_vars.keys()) +# selected := task.loop_name +# tool_tip = ("Name a variable to loop") + + Label: param_start: + text = 'Start value' + QtLineCompleter: param_start_val: + text := model.value0 + entries_updater << model.task.list_accessible_database_entries + tool_tip = fc('''Start value of the loop parameter to be called in the + variables. If several loops are needed, separate the + values by a comma. Do not put a space + after the commas. + ''') + + Label: param_stop: + text = 'Stop value' + QtLineCompleter: param_stop_val: + text := model.value1 + entries_updater << model.task.list_accessible_database_entries + tool_tip = fc('''Stop value of the loop parameter to be called in the + variables. If several loops are needed, separate the + values by a comma. Do not put a space + after the commas. + ''') + + Label: param_points: + text = 'Number of points' + QtLineCompleter: param_points_val: + text := model.value2 + entries_updater << model.task.list_accessible_database_entries + tool_tip = fc('''Number of points of the loop. If several loops + are needed, separate the values by a comma. + Do not put a space after the commas. + ''') + + +enamldef ObjectComboFieldCompleterEditor(Container): + """ + + """ + #: Reference to the object holding the completion information. + attr model + constraints = [hbox(k, v), 2*k.width <= v.width] + padding = 1 + + ObjectCombo: k: + items << list(model.task.sequence_vars.keys()) + selected := model.task.loop_name + tool_tip = ("Select external sequence variable to add") + QtLineCompleter: v: + text := model.value + entries_updater << list(model.task.sequence_vars.keys()) + tool_tip = EVALUATER_TOOLTIP + +def validate_context_driver_pair(core, context, task, parent=None): + """Validate that the context can work in conjunction with the selected + driver. + + """ + if task.selected_instrument and task.selected_instrument[0]: + cmd = 'exopy.pulses.get_context_infos' + c_id = context.context_id + c_infos = core.invoke_command(cmd, dict(context_id=c_id)) + driver_id = task.selected_instrument[1] + if driver_id not in c_infos.instruments: + del task.selected_instrument + information(parent, title='Invalid context/driver association', + text=fill('The context of the loaded sequence does ' + 'not support the selected driver, please ' + ' elect a different driver.')) + + +def load_sequence(core, task, parent=None, path=''): + """Open a dialog and load a pulse sequence. + + Parameters + ---------- + core : CorePlugin + Refrence to the core plugin of the application. + task : TransferPulseSequenceTask + Task for which to load the sequence. + parent : enaml.Widget + Parent for the dialog. + path : unicode, optional + Path of the sequence to load. If this does not point to a real + file the path is used as a hint for the file selection dialog. + + """ + if not os.path.isfile(path): + path = FileDialogEx.get_open_file_name(parent, current_path=path, + name_filters=['*.pulse.ini']) + if path: + cmd = 'exopy.pulses.build_sequence' + try: + seq = core.invoke_command(cmd, {'path': path}) + except Exception: + cmd = 'exopy.app.errors.signal' + msg = 'Failed to load sequence {}: {}'.format(path, format_exc()) + core.invoke_command(cmd, dict(kind='error', message=msg)) + return + + if seq.context: + validate_context_driver_pair(core, seq.context, task, parent) + + task.sequence = seq + new = OrderedDict.fromkeys(seq.external_vars, '') + for k in (e for e in task.sequence_vars if e in new): + new[k] = task.sequence_vars[k] + task.sequence_vars = new + task.sequence_path = path + task.sequence_timestamp = os.path.getmtime(path) + + +enamldef TransferPulseLoopView(InstrTaskView): view: + """View for the TransferPulseSequenceTask. + + """ + constraints << [vbox(hbox(seq, seq_name, seq_re, seq_sav, spacer, operation_lab, operation_val, + instr_label, instr_selection), parameter, + hbox(run_mode_lab, run_mode_val, trig_period_lab, trig_period_val), + nb)] + + initialized :: + + task.observe('sequence', _install_context_observer) + + if task.sequence and os.path.isfile(task.sequence_path): + if os.path.getmtime(task.sequence_path) != task.sequence_timestamp: + seq_re.style_class = 'red-button' + seq_re.tool_tip = fill('The sequence appears to have been ' + 'edited since it has been reload. ' + 'Consider refreshing the sequence.') + + if task.sequence.context: + validate_context_driver_pair(root.core, task.sequence.context, + task, self) + + task.sequence.observe('context', + _check_context_driver_compatibility) + + filter_profiles => (profiles): + """Only allow profile whose at least one driver can be used by the + context. + + """ + if not task.sequence or not task.sequence.context: + return profiles + + cmd = 'exopy.pulses.get_context_infos' + c_id = task.sequence.context.context_id + c_infos = self.root.core.invoke_command(cmd, dict(context_id=c_id)) + + return [p for p, v in profiles.items() + if any([d.id in c_infos.instruments + for d in v.model.drivers])] + + filter_drivers => (drivers): + """Only allow drivers supported by the context. + + """ + if not task.sequence or not task.sequence.context: + return drivers + + cmd = 'exopy.pulses.get_context_infos' + c_id = task.sequence.context.context_id + c_infos = self.root.core.invoke_command(cmd, dict(context_id=c_id)) + + return [d for d in drivers if d.id in c_infos.instruments] + + instr_selection.enabled << bool(task.sequence and task.sequence.context) + instr_selection.tool_tip << ('Please first select a context' + if not instr_selection.enabled else + '') + + PushButton: seq: + text = 'Select sequence' + clicked :: + dir_path = os.path.dirname(task.sequence_path) + load_sequence(view.root.core, task, parent=view, path=dir_path) + seq_re.style_class = '' + + Field: seq_name: + visible << bool(task.sequence_path) + read_only = True + text << os.path.basename(task.sequence_path).rstrip('.pulse.ini') + + PushButton: seq_re: + enabled << bool(task.sequence_path and + os.path.isfile(task.sequence_path)) + text = 'Refresh' + tool_tip << ('Referenced file does not exist anymore.' + if not self.enabled else '') + clicked :: + btn = question(self, title='Confirm refresh', + text='If you refresh any local modification to the ' + 'sequence will be lost.\nConfirm refresh ?') + if btn and btn.action == 'accept': + try: + load_sequence(view.root.core, task, + path=task.sequence_path) + except Exception: + warning(self, 'Failed to refresh sequence', format_exc()) + + self.tool_tip = 'Reload the sequence from file.' + self.style_class = '' + + PushButton: seq_sav: + enabled << bool(task.sequence) + text = 'Save' + Menu: + Action: + text = 'Save' + enabled << bool(task.sequence_path and + os.path.isfile(task.sequence_path)) + tool_tip << ('Referenced file does not exist anymore.' + if not self.enabled else '') + triggered :: + btn = question(seq_sav, title='Confirm save', + text='If you save any local modification will ' + 'override the original sequence.\n' + 'Confirm save ?') + if btn and btn.action == 'accept': + seq = task.sequence + prefs = seq.preferences_from_members() + ext_vars = OrderedDict.fromkeys(seq.external_vars, '') + prefs['external_vars'] = repr(list(ext_vars.items())) + save_sequence_prefs(task.sequence_path, prefs) + tstmp = os.path.getmtime(task.sequence_path) + task.sequence_timestamp = tstmp + Action: + text = 'Save as' + triggered :: + explore = FileDialogEx.get_save_file_name + path = explore(seq_sav, current_path=task.sequence_path, + name_filters=['*.pulse.ini']) + if path: + if not path.endswith('.pulse.ini'): + path += '.pulse.ini' + seq = task.sequence + prefs = seq.preferences_from_members() + ext_vars = OrderedDict.fromkeys(seq.external_vars, '') + prefs['external_vars'] = repr(list(ext_vars.items())) + save_sequence_prefs(path, prefs) + tstmp = os.path.getmtime(path) + task.sequence_timestamp = tstmp + task.sequence_path = path + + Label: operation_lab: + text = 'Operation :' + ObjectCombo: operation_val: + items << list(task.get_member('operation').items) + selected := task.operation + tool_tip = ("Clean: delete all previously loaded waveforms\n" + "Load: load waveforms in the waveform list of the AWG \n" + "Enqueue: build a sequence from loaded waveforms") + + DictListEditor(LoopEditor): parameter: + parameter.mapping := task.parameters + parameter.operations = ['add','remove'] + parameter.attributes = {'task' : task} + + Label: run_mode_lab: + text = 'Running mode' + ObjectCombo: run_mode_val: + items << list(task.get_member('run_mode').items) + selected := task.run_mode + + Label: trig_period_lab: + text = 'Int trigger period (us)' + QtLineCompleter: trig_period_val: + entries_updater << task.list_accessible_database_entries + text := task.trigger_period + tool_tip = EVALUATER_TOOLTIP + enabled << (task.run_mode=='Int Trig') + + Notebook: nb: + tabs_closable = False + visible << bool(task.sequence) + Page: + title = 'Variables (Name: values)' + DictEditor(VarEditor): ed: + ed.mapping := task.sequence_vars + ed.attributes << {'task': task} + Page: + title = 'Context' + Include: + objects << ([instantiate_context_view(view.root.core, + task.sequence, + task.sequence.context + )] + if task.sequence and task.sequence.context else []) + + # ========================================================================= + # --- Private API --------------------------------------------------------- + # ========================================================================= + + func _install_context_observer(change): + """Setup an observer to validate the driver/context match on context + change. + + """ + if 'oldvalue' in change and change['oldvalue']: + change['oldvalue'].unobserve('context', + _check_context_driver_compatibility) + if change['value']: + sequence = change['value'] + sequence.observe('context', _check_context_driver_compatibility) + if sequence.context: + validate_context_driver_pair(view.root.core, + task.sequence.context, task, view) + + func _check_context_driver_compatibility(change): + """Check whether the selected driver can be used with the selected + context. + + """ + if task.sequence.context: + validate_context_driver_pair(view.root.core, task.sequence.context, + task, view) diff --git a/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml b/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml index f4203bb..cb3d450 100644 --- a/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml +++ b/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml @@ -20,7 +20,8 @@ from enaml.layout.api import factory, hbox, vbox, spacer, align from enaml.core.api import Conditional, Include from enaml.widgets.api import (GroupBox, Label, Field, ObjectCombo, CheckBox, Notebook, Page, PushButton, Menu, Action, - FileDialogEx, Container) + FileDialogEx, Container, MPLCanvas, Window, + ScrollArea) from enaml.stdlib.message_box import information, question, warning from exopy.utils.traceback import format_exc @@ -33,6 +34,9 @@ from exopy_pulses.pulses.utils.sequences_io import save_sequence_prefs from exopy_pulses.pulses.sequences.views.base_sequences_views\ import instantiate_context_view +import matplotlib.pyplot as plt +import numpy as np + enamldef RedButton(StyleSheet): """Style sheet allowing to use a large font for the save confirmation popup @@ -63,6 +67,37 @@ enamldef VarEditor(Container): tool_tip = EVALUATER_TOOLTIP +enamldef SequenceWindow(Window): seqwin: + #attr task + + title = 'Sequences' + + initial_size = (1500,1000) + + Container: + ScrollArea: + Container: + constraints = [vbox(ed, update, check, plot)] + + DictEditor(VarEditor): ed: + ed.mapping = task.sequence_vars + ed.attributes << {'task': task} + + PushButton: update: + text = 'Update Plot' + clicked :: + variables = ed.mapping + #canvas.figure = plot_sequences(task,variables) + canvas.figure = task.compile_and_plot(variables) + + CheckBox: check: + text = 'Toolbar Visible' + checked := canvas.toolbar_visible + Container: plot: + MPLCanvas: canvas: + #figure = plot_sequences(task,ed.mapping) + figure = task.compile_and_plot(ed.mapping) + def validate_context_driver_pair(core, context, task, parent=None): """Validate that the context can work in conjunction with the selected driver. @@ -126,7 +161,7 @@ enamldef TransferPulseSequenceView(InstrTaskView): view: """View for the TransferPulseSequenceTask. """ - constraints << [vbox(hbox(seq, seq_name, seq_re, seq_sav, spacer, + constraints << [vbox(hbox(seq, seq_name, seq_re, seq_sav, seq_plot, spacer, instr_label, instr_selection), nb)] @@ -255,6 +290,13 @@ enamldef TransferPulseSequenceView(InstrTaskView): view: task.sequence_timestamp = tstmp task.sequence_path = path + PushButton: seq_plot: + enabled << bool(task.sequence) + text = 'Plot sequence' + clicked :: + win = SequenceWindow(self) + win.show() + Notebook: nb: tabs_closable = False visible << bool(task.sequence) @@ -287,9 +329,9 @@ enamldef TransferPulseSequenceView(InstrTaskView): view: if change['value']: sequence = change['value'] sequence.observe('context', _check_context_driver_compatibility) - if sequence.context: - validate_context_driver_pair(view.root.core, - task.sequence.context, task, view) + #if sequence.context: + # validate_context_driver_pair(view.root.core, + # task.sequence.context, task, view) func _check_context_driver_compatibility(change): """Check whether the selected driver can be used with the selected From 1a4c838a2a78849d08ac979b7d4e823693550d8c Mon Sep 17 00:00:00 2001 From: Jeremy Stevens Date: Fri, 26 Feb 2021 16:30:08 +0100 Subject: [PATCH 3/7] Fixing some residual bugs from previous branch. --- exopy_pulses/pulses/manifest.enaml | 3 --- exopy_pulses/pulses/shapes/gaussian_shape.py | 6 +++--- exopy_pulses/pulses/shapes/tanh_shape.py | 6 +++--- .../tasks/instrs/transfer_pulse_loop_task.py | 15 ++++++--------- .../tasks/tasks/instrs/transfer_sequence_task.py | 3 --- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/exopy_pulses/pulses/manifest.enaml b/exopy_pulses/pulses/manifest.enaml index 9abf95b..3439036 100644 --- a/exopy_pulses/pulses/manifest.enaml +++ b/exopy_pulses/pulses/manifest.enaml @@ -438,9 +438,6 @@ enamldef PulsesManagerManifest(PluginManifest): Shape: shape = 'tanh_shape:TanhShape' view = 'views.tanh_shape_view:TanhShapeView' - Shape: - shape = 'arbitrary_shape:ArbitraryShape' - view = 'views.arbitrary_shape_view:ArbitraryShapeView' PulsesBuildingDependenciesExtension: pass diff --git a/exopy_pulses/pulses/shapes/gaussian_shape.py b/exopy_pulses/pulses/shapes/gaussian_shape.py index 3a7fadd..5b65232 100644 --- a/exopy_pulses/pulses/shapes/gaussian_shape.py +++ b/exopy_pulses/pulses/shapes/gaussian_shape.py @@ -12,7 +12,7 @@ from numbers import Real import numpy as np -from atom.api import Unicode +from atom.api import Str from ..utils.validators import Feval @@ -24,8 +24,8 @@ class GaussianShape(AbstractShape): """ #: Amplitude of the pulse this should be a number between -1.0 and 1.0 - amplitude = Unicode('1.0').tag(pref=True, feval=Feval(types=Real)) - sigma = Unicode('10.0').tag(pref=True, feval=Feval(types=Real)) + amplitude = Str('1.0').tag(pref=True, feval=Feval(types=Real)) + sigma = Str('10.0').tag(pref=True, feval=Feval(types=Real)) def eval_entries(self, root_vars, sequence_locals, missing, errors): """ Evaluate the amplitude of the pulse. diff --git a/exopy_pulses/pulses/shapes/tanh_shape.py b/exopy_pulses/pulses/shapes/tanh_shape.py index 444d6cd..a53512d 100644 --- a/exopy_pulses/pulses/shapes/tanh_shape.py +++ b/exopy_pulses/pulses/shapes/tanh_shape.py @@ -12,7 +12,7 @@ from numbers import Real import numpy as np -from atom.api import Unicode +from atom.api import Str from ..utils.validators import Feval @@ -24,8 +24,8 @@ class TanhShape(AbstractShape): """ #: Amplitude of the pulse this should be a number between -1.0 and 1.0 - amplitude = Unicode('1.0').tag(pref=True, feval=Feval(types=Real)) - sigma = Unicode('10.0').tag(pref=True, feval=Feval(types=Real)) + amplitude = Str('1.0').tag(pref=True, feval=Feval(types=Real)) + sigma = Str('10.0').tag(pref=True, feval=Feval(types=Real)) def eval_entries(self, root_vars, sequence_locals, missing, errors): """ Evaluate the amplitude of the pulse. diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py index 2411b8d..8af513d 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py @@ -15,7 +15,7 @@ from collections import OrderedDict import numpy as np -from atom.api import (Value, Unicode, Float, Typed, Bool, set_default, Enum) +from atom.api import (Value, Str, Float, Typed, Bool, set_default, Enum) from exopy.tasks.api import (InstrumentTask) from exopy.utils.atom_util import ordered_dict_from_pref, ordered_dict_to_pref @@ -27,7 +27,7 @@ class TransferPulseLoopTask(InstrumentTask): """ #: Sequence path for the case of sequence simply referenced. - sequence_path = Unicode().tag(pref=True) + sequence_path = Str().tag(pref=True) #: Time stamp of the last modification of the sequence file. sequence_timestamp = Float().tag(pref=True) @@ -45,17 +45,17 @@ class TransferPulseLoopTask(InstrumentTask): #: Loop variables: channels on which the loop will be done, loop parameters #: names, start value, stop value and number of points per loop - loop_start = Unicode('0').tag(pref=True) + loop_start = Str('0').tag(pref=True) - loop_stop = Unicode('0').tag(pref=True) + loop_stop = Str('0').tag(pref=True) - loop_points = Unicode('2').tag(pref=True) + loop_points = Str('2').tag(pref=True) #: run mode of the awg run_mode = Enum('Ext Trig', 'Int Trig', 'Continuous').tag(pref=True) #: Internal trigger period in mus - trigger_period = Unicode('20').tag(pref=True) + trigger_period = Str('20').tag(pref=True) parameters = Typed(OrderedDict, ()).tag(pref=[ordered_dict_to_pref, ordered_dict_from_pref]) @@ -155,9 +155,7 @@ def perform(self): self.write_in_database(name_parameter, loop_value[ii]) for k, v in self.sequence_vars.items(): seq.external_vars[k] = self.format_and_eval_string(v) - # context.sequence_name = '{}_{}'.format(seq_name_0, nn+1) #RL replaced, caused bug context.sequence_name = '{}_{}'.format('', nn+first_index) -# print(context.sequence_name) res, infos, errors = context.compile_and_transfer_sequence( seq, driver=self.driver) @@ -208,7 +206,6 @@ def perform(self): self.driver.get_channel(current_ch).set_sequence_pos(current_wf, current_index) self.driver.set_jump_pos(current_index, 1) -# print(channels_to_turn_on) self.driver.set_goto_pos(current_index, 1) self.write_in_database('num_loop', current_index) for cc in channels_to_turn_on: diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py index e3c9eaf..0fb766a 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py @@ -21,7 +21,6 @@ import matplotlib.pyplot as plt import numpy as np - class TransferPulseSequenceTask(InstrumentTask): """Build and transfer a pulse sequence to an instrument. @@ -39,7 +38,6 @@ class TransferPulseSequenceTask(InstrumentTask): sequence_vars = Typed(OrderedDict, ()).tag(pref=(ordered_dict_to_pref, ordered_dict_from_pref)) - def check(self, *args, **kwargs): """Check that the sequence can be compiled. @@ -91,7 +89,6 @@ def perform(self): for k, v in self.sequence_vars.items(): seq.external_vars[k] = self.format_and_eval_string(v) -# self.driver.run_mode = 'CONT' res, infos, errors = context.compile_and_transfer_sequence(seq, self.driver) if not res: From b4a350c9233e4f6c2368daea9bf4d9436dad9ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9ouven=20Assouly?= Date: Wed, 14 Apr 2021 11:16:19 +0200 Subject: [PATCH 4/7] Formating and style --- exopy_pulses/pulses/shapes/gaussian_shape.py | 4 +- exopy_pulses/pulses/shapes/tanh_shape.py | 6 +- .../shapes/views/gaussian_shape_view.enaml | 2 +- .../pulses/shapes/views/tanh_shape_view.enaml | 2 +- .../tasks/instrs/transfer_pulse_loop_task.py | 184 +++++++++--------- .../tasks/instrs/transfer_sequence_task.py | 18 +- 6 files changed, 114 insertions(+), 102 deletions(-) diff --git a/exopy_pulses/pulses/shapes/gaussian_shape.py b/exopy_pulses/pulses/shapes/gaussian_shape.py index 5b65232..85dc849 100644 --- a/exopy_pulses/pulses/shapes/gaussian_shape.py +++ b/exopy_pulses/pulses/shapes/gaussian_shape.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -25,6 +25,8 @@ class GaussianShape(AbstractShape): """ #: Amplitude of the pulse this should be a number between -1.0 and 1.0 amplitude = Str('1.0').tag(pref=True, feval=Feval(types=Real)) + + #: Sigma of gaussian pulse, units are AWG context units sigma = Str('10.0').tag(pref=True, feval=Feval(types=Real)) def eval_entries(self, root_vars, sequence_locals, missing, errors): diff --git a/exopy_pulses/pulses/shapes/tanh_shape.py b/exopy_pulses/pulses/shapes/tanh_shape.py index a53512d..dc45f4a 100644 --- a/exopy_pulses/pulses/shapes/tanh_shape.py +++ b/exopy_pulses/pulses/shapes/tanh_shape.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -20,11 +20,13 @@ class TanhShape(AbstractShape): - """ Atan pulse with a variable amplitude and sigma. + """ Tanh pulse with a variable amplitude and sigma. """ #: Amplitude of the pulse this should be a number between -1.0 and 1.0 amplitude = Str('1.0').tag(pref=True, feval=Feval(types=Real)) + + #: Sigma of tanh pulse, basically raising time, units are AWG context units sigma = Str('10.0').tag(pref=True, feval=Feval(types=Real)) def eval_entries(self, root_vars, sequence_locals, missing, errors): diff --git a/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml b/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml index e6d5140..2f0ace0 100644 --- a/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml +++ b/exopy_pulses/pulses/shapes/views/gaussian_shape_view.enaml @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # diff --git a/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml b/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml index 705b8b0..6e74e71 100644 --- a/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml +++ b/exopy_pulses/pulses/shapes/views/tanh_shape_view.enaml @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py index 8af513d..a0c0ef8 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2016 by ExopyHqcLegacy Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyHqcLegacy Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -25,7 +25,6 @@ class TransferPulseLoopTask(InstrumentTask): """Build and transfer a pulse sequence to an instrument. """ - #: Sequence path for the case of sequence simply referenced. sequence_path = Str().tag(pref=True) @@ -38,9 +37,14 @@ class TransferPulseLoopTask(InstrumentTask): #: Global variable to use for the sequence. sequence_vars = Typed(OrderedDict, ()).tag(pref=(ordered_dict_to_pref, ordered_dict_from_pref)) - + #: operation mode of the awg - operation = Enum('Clean, Load & Enqueue', 'Clean & Load', 'Load', 'Load & Enqueue all').tag(pref=True) + operation = Enum( + 'Clean, Load & Enqueue', + 'Clean & Load', + 'Load', + 'Load & Enqueue all').tag( + pref=True) #: Loop variables: channels on which the loop will be done, loop parameters #: names, start value, stop value and number of points per loop @@ -50,55 +54,55 @@ class TransferPulseLoopTask(InstrumentTask): loop_stop = Str('0').tag(pref=True) loop_points = Str('2').tag(pref=True) - + #: run mode of the awg run_mode = Enum('Ext Trig', 'Int Trig', 'Continuous').tag(pref=True) #: Internal trigger period in mus trigger_period = Str('20').tag(pref=True) - + parameters = Typed(OrderedDict, ()).tag(pref=[ordered_dict_to_pref, - ordered_dict_from_pref]) - + ordered_dict_from_pref]) + database_entries = set_default({'num_loop': 1}) -# def check(self, *args, **kwargs): -# """Check that the sequence can be compiled. -# -# """ -# test, traceback = super(TransferPulseLoopTask, -# self).check(*args, **kwargs) -# err_path = self.path + '/' + self.name + '-' -# -# msg = 'Failed to evaluate {} ({}): {}' -# seq = self.sequence -# for k, v in self.sequence_vars.items(): -# try: -# seq.external_vars[k] = self.format_and_eval_string(v) -# except Exception: -# test = False -# traceback[err_path+k] = msg.format(k, v, format_exc()) -# -# if not test: -# return test, traceback -# -# context = seq.context -# res, infos, errors = context.compile_and_transfer_sequence(seq) -# -# if not res: -# traceback[err_path+'compil'] = errors -# return False, traceback -# -# for k, v in infos.items(): -# self.write_in_database(k, v) -# -# if self.sequence_path: -# if not (self.sequence_timestamp == -# os.path.getmtime(self.sequence_path)): -# msg = 'The sequence is outdated, consider refreshing it.' -# traceback[err_path+'outdated'] = msg -# -# return test, traceback + def check(self, *args, **kwargs): + """Check that the sequence can be compiled. + + """ + test, traceback = super(TransferPulseLoopTask, + self).check(*args, **kwargs) + err_path = self.path + '/' + self.name + '-' + + msg = 'Failed to evaluate {} ({}): {}' + seq = self.sequence + for k, v in self.sequence_vars.items(): + try: + seq.external_vars[k] = self.format_and_eval_string(v) + except Exception: + test = False + traceback[err_path + k] = msg.format(k, v, format_exc()) + + if not test: + return test, traceback + + context = seq.context + res, infos, errors = context.compile_and_transfer_sequence(seq) + + if not res: + traceback[err_path + 'compil'] = errors + return False, traceback + + for k, v in infos.items(): + self.write_in_database(k, v) + + if self.sequence_path: + if not (self.sequence_timestamp == + os.path.getmtime(self.sequence_path)): + msg = 'The sequence is outdated, consider refreshing it.' + traceback[err_path + 'outdated'] = msg + + return test, traceback def perform(self): """Compile the sequence. @@ -107,35 +111,31 @@ def perform(self): operation = self.operation seq = self.sequence context = seq.context - if self.run_mode=='Int Trig': + if self.run_mode == 'Int Trig': internal_trigger = True - else: + else: internal_trigger = False self.driver.internal_trigger = internal_trigger if internal_trigger: - self.driver.internal_trigger_period = int(float(self.trigger_period) * 1000) - + self.driver.internal_trigger_period = int( + float(self.trigger_period) * 1000) + self.driver.clear_all_sequences() if operation in ['Clean, Load & Enqueue', 'Clean & Load']: self.driver.delete_all_waveforms() first_index = 1 -# print("First index shoud be %d"%first_index) else: n_remaining_wf = self.driver.get_waveform_number() -# print('Remaining of waveforms = '+str(n_remaining_wf)) - last_wf = str(self.driver.get_waveform_name(n_remaining_wf-1)) + last_wf = str(self.driver.get_waveform_name(n_remaining_wf - 1)) last_wf = last_wf.split('_') - first_index = int(last_wf[1])+1 -# print("First index shoud be %d"%first_index) + first_index = int(last_wf[1]) + 1 _used_channels = [] - loops = [] name_parameters = [] n_loops = len(self.parameters) -# print(n_loops) - if n_loops>0: + if n_loops > 0: context.run_after_transfer = False context.select_after_transfer = False self.driver.run_mode = 'SEQUENCE' @@ -145,72 +145,82 @@ def perform(self): loop_points = int(self.format_and_eval_string(params[1][2])) loops.append(np.linspace(loop_start, loop_stop, loop_points)) name_parameters.append(params[0]) - self.write_in_database(params[0]+'_loop', np.linspace(loop_start, loop_stop, loop_points)) - - loop_values = np.moveaxis(np.array(np.meshgrid(*loops)),0,-1).reshape((-1,n_loops)) - if operation=='Clean, Load & Enqueue': + self.write_in_database( + params[0] + '_loop', + np.linspace( + loop_start, + loop_stop, + loop_points)) + + loop_values = np.moveaxis( + np.array(np.meshgrid(*loops)), 0, -1).reshape((-1, n_loops)) + if operation == 'Clean, Load & Enqueue': self.write_in_database('num_loop', len(loop_values)) for nn, loop_value in enumerate(loop_values): for ii, name_parameter in enumerate(name_parameters): self.write_in_database(name_parameter, loop_value[ii]) for k, v in self.sequence_vars.items(): seq.external_vars[k] = self.format_and_eval_string(v) - context.sequence_name = '{}_{}'.format('', nn+first_index) + context.sequence_name = '{}_{}'.format('', nn + first_index) res, infos, errors = context.compile_and_transfer_sequence( - seq, - driver=self.driver) - if operation=='Clean, Load & Enqueue': + seq, + driver=self.driver) + if operation == 'Clean, Load & Enqueue': for cc in range(4): - _seq = 'sequence_ch'+str(cc+1) + _seq = 'sequence_ch' + str(cc + 1) if infos[_seq]: - self.driver.get_channel(cc+1).set_sequence_pos(infos[_seq], - nn+1) - _used_channels.append(cc+1) - self.driver.set_jump_pos(nn+1, 1) + self.driver.get_channel( + cc + + 1).set_sequence_pos( + infos[_seq], + nn + + 1) + _used_channels.append(cc + 1) + self.driver.set_jump_pos(nn + 1, 1) self.driver.set_goto_pos(len(loop_values), 1) for cc in set(_used_channels): self.driver.get_channel(cc).output_state = 'on' - + if not res: raise Exception('Failed to compile sequence :\n' + pformat(errors)) self.write_in_database(name_parameter, loop_value[ii]) - + else: for k, v in self.sequence_vars.items(): seq.external_vars[k] = self.format_and_eval_string(v) - if self.run_mode=='Continuous': + if self.run_mode == 'Continuous': self.driver.run_mode = 'CONT' else: self.driver.run_mode = 'TRIG' context.sequence_name = '{}_{}'.format('', first_index) - res, infos, errors = context.compile_and_transfer_sequence(seq, - self.driver) + res, infos, errors = context.compile_and_transfer_sequence( + seq, self.driver) if not res: raise Exception('Failed to compile sequence :\n' + pformat(errors)) - + for k, v in infos.items(): self.write_in_database(k, v) - - if operation=='Load & Enqueue all': - n_wf = self.driver.get_waveform_number()-25 + + if operation == 'Load & Enqueue all': + n_wf = self.driver.get_waveform_number() - 25 channels_to_turn_on = set() for ii in range(n_wf): - index = ii+25 + index = ii + 25 current_wf = str(self.driver.get_waveform_name(index)) current_ch = int(current_wf[-1]) - current_index = int(current_wf.split('_')[1]) + current_index = int(current_wf.split('_')[1]) channels_to_turn_on.add(current_ch) - self.driver.get_channel(current_ch).set_sequence_pos(current_wf, - current_index) + self.driver.get_channel(current_ch).set_sequence_pos( + current_wf, current_index) self.driver.set_jump_pos(current_index, 1) self.driver.set_goto_pos(current_index, 1) self.write_in_database('num_loop', current_index) for cc in channels_to_turn_on: self.driver.get_channel(cc).output_state = 'on' - + def register_preferences(self): """Register the task preferences into the preferences system. @@ -270,7 +280,7 @@ def _post_setattr_sequence(self, old, new): if entries != self.database_entries: self.database_entries = entries - + def _post_setattr_parameters(self, old, new): """Observer keeping the database entries in sync with the declared definitions. @@ -279,10 +289,10 @@ def _post_setattr_parameters(self, old, new): entries = self.database_entries.copy() for e in old: del entries[e] - del entries[e+'_loop'] + del entries[e + '_loop'] for e in new: entries.update({key: 0.0 for key in new}) - entries.update({key+'_loop': 0.0 for key in new}) + entries.update({key + '_loop': 0.0 for key in new}) self.database_entries = entries def _update_database_entries(self, change): diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py index 0fb766a..c8dea69 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_sequence_task.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyHqcLegacy Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyHqcLegacy Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -37,7 +37,7 @@ class TransferPulseSequenceTask(InstrumentTask): #: Global variable to use for the sequence. sequence_vars = Typed(OrderedDict, ()).tag(pref=(ordered_dict_to_pref, ordered_dict_from_pref)) - + def check(self, *args, **kwargs): """Check that the sequence can be compiled. @@ -97,12 +97,11 @@ def perform(self): for k, v in infos.items(): self.write_in_database(k, v) - + def compile_and_plot(self, variables): """Compile the sequence and plot it. """ - seq = self.sequence context = seq.context for k, v in variables.items(): @@ -112,16 +111,15 @@ def compile_and_plot(self, variables): if not table: raise Exception('Failed to compile sequence :\n' + pformat(errors)) - freq = context.list_sequence_infos()['sampling_frequency'] - + freq = context.list_sequence_infos()['sampling_frequency'] + channel_num = len(table) - #fig, axs = plt.subplots(4,1, figsize=(15, 8)) - fig, axs = plt.subplots(channel_num,1, figsize=(15, 2.5*channel_num), + fig, axs = plt.subplots(channel_num, 1, figsize=(15, 2.5*channel_num), sharex=True) fig.subplots_adjust(hspace = 0.5, wspace=.001) - + x = np.arange(len(table[list(table.keys())[0]]))/freq*10**6 - + if len(list(table.keys())) == 1: key = list(table.keys())[0] axs.plot(x,table[key], label = 'wvfm') From 49edc842db6fe96bbd7a5d1bea1fd904a61afaa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9ouven=20Assouly?= Date: Wed, 14 Apr 2021 11:52:05 +0200 Subject: [PATCH 5/7] Document TransferLoopTask driver requirements --- .../tasks/tasks/instrs/transfer_pulse_loop_task.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py index a0c0ef8..bfd4489 100644 --- a/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py +++ b/exopy_pulses/tasks/tasks/instrs/transfer_pulse_loop_task.py @@ -107,6 +107,18 @@ def check(self, *args, **kwargs): def perform(self): """Compile the sequence. + Uses the following properties and methods from the driver: + - internal_trigger + - internal_trigger_period + - clear_all_sequences() + - delete_all_waveforms() + - get_waveform_number() + - get_waveform_name() + - run_mode + - get_channel() + - set_jump_pos() + - set_goto_pos() + """ operation = self.operation seq = self.sequence From 88c9aea039711a0763ce784532427812e8dafc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9ouven=20Assouly?= Date: Wed, 14 Apr 2021 14:51:11 +0200 Subject: [PATCH 6/7] Clean up --- exopy_pulses/pulses/shapes/gaussian_shape.py | 2 +- .../views/transfer_pulse_loop_task_view.enaml | 30 +++++++------------ .../views/transfer_sequence_task_view.enaml | 18 +++++------ 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/exopy_pulses/pulses/shapes/gaussian_shape.py b/exopy_pulses/pulses/shapes/gaussian_shape.py index 85dc849..9136870 100644 --- a/exopy_pulses/pulses/shapes/gaussian_shape.py +++ b/exopy_pulses/pulses/shapes/gaussian_shape.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # diff --git a/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml b/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml index 893dfdf..a3e7fae 100644 --- a/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml +++ b/exopy_pulses/tasks/tasks/instrs/views/transfer_pulse_loop_task_view.enaml @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -65,7 +65,7 @@ enamldef VarEditor(Container): text := model.value entries_updater << model.task.list_accessible_database_entries tool_tip = EVALUATER_TOOLTIP - + enamldef LoopEditor(Container): """ Fields allowing to edit the sweep of a sequence. @@ -76,21 +76,11 @@ enamldef LoopEditor(Container): padding = 1 constraints = [hbox(loop_name, param_start, param_start_val, param_stop, - param_stop_val, param_points, param_points_val), align('v_center', loop_name, param_start)] -# Label: lab: -# hug_width = 'strong' -# text << model.key -# QtLineCompleter: val: -# text := model.value -# entries_updater << model.task.list_accessible_database_entries -# tool_tip = EVALUATER_TOOLTIP - + param_stop_val, param_points, param_points_val), + align('v_center', loop_name, param_start)] + Field: loop_name: text := model.key -# print(model) -# items << list(task.sequence_vars.keys()) -# selected := task.loop_name -# tool_tip = ("Name a variable to loop") Label: param_start: text = 'Start value' @@ -126,7 +116,7 @@ enamldef LoopEditor(Container): enamldef ObjectComboFieldCompleterEditor(Container): - """ + """ """ #: Reference to the object holding the completion information. @@ -335,7 +325,7 @@ enamldef TransferPulseLoopView(InstrTaskView): view: tstmp = os.path.getmtime(path) task.sequence_timestamp = tstmp task.sequence_path = path - + Label: operation_lab: text = 'Operation :' ObjectCombo: operation_val: @@ -344,7 +334,7 @@ enamldef TransferPulseLoopView(InstrTaskView): view: tool_tip = ("Clean: delete all previously loaded waveforms\n" "Load: load waveforms in the waveform list of the AWG \n" "Enqueue: build a sequence from loaded waveforms") - + DictListEditor(LoopEditor): parameter: parameter.mapping := task.parameters parameter.operations = ['add','remove'] @@ -355,7 +345,7 @@ enamldef TransferPulseLoopView(InstrTaskView): view: ObjectCombo: run_mode_val: items << list(task.get_member('run_mode').items) selected := task.run_mode - + Label: trig_period_lab: text = 'Int trigger period (us)' QtLineCompleter: trig_period_val: @@ -363,7 +353,7 @@ enamldef TransferPulseLoopView(InstrTaskView): view: text := task.trigger_period tool_tip = EVALUATER_TOOLTIP enabled << (task.run_mode=='Int Trig') - + Notebook: nb: tabs_closable = False visible << bool(task.sequence) diff --git a/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml b/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml index cb3d450..7e77b30 100644 --- a/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml +++ b/exopy_pulses/tasks/tasks/instrs/views/transfer_sequence_task_view.enaml @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- -# Copyright 2015-2018 by ExopyPulses Authors, see AUTHORS for more details. +# Copyright 2015-2021 by ExopyPulses Authors, see AUTHORS for more details. # # Distributed under the terms of the BSD license. # @@ -69,33 +69,31 @@ enamldef VarEditor(Container): enamldef SequenceWindow(Window): seqwin: #attr task - + title = 'Sequences' - + initial_size = (1500,1000) - + Container: ScrollArea: Container: constraints = [vbox(ed, update, check, plot)] - + DictEditor(VarEditor): ed: ed.mapping = task.sequence_vars ed.attributes << {'task': task} - + PushButton: update: text = 'Update Plot' clicked :: variables = ed.mapping - #canvas.figure = plot_sequences(task,variables) canvas.figure = task.compile_and_plot(variables) - + CheckBox: check: text = 'Toolbar Visible' - checked := canvas.toolbar_visible + checked := canvas.toolbar_visible Container: plot: MPLCanvas: canvas: - #figure = plot_sequences(task,ed.mapping) figure = task.compile_and_plot(ed.mapping) def validate_context_driver_pair(core, context, task, parent=None): From c7f3b80ac8e372ab16d858e51684c75ca28b8c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9ouven=20Assouly?= Date: Sat, 8 May 2021 16:20:38 +0200 Subject: [PATCH 7/7] Add matplotlib as a dependency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d5ea0ac..c2c2cc2 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ def long_description(): packages=find_packages(exclude=['tests', 'tests.*']), package_data={'': ['*.enaml']}, setup_requires=['setuptools'], - install_requires=['exopy', 'numpy'], + install_requires=['exopy', 'numpy', 'matplotlib'], entry_points={ 'exopy_package_extension': 'exopy_pulses = %s:list_manifests' % PROJECT_NAME}