1
###############################################################################
2
#    pyrpl - DSP servo controller for quantum optics with the RedPitaya
3
#    Copyright (C) 2014-2016  Leonhard Neuhaus  (neuhaus@spectro.jussieu.fr)
4
#
5
#    This program is free software: you can redistribute it and/or modify
6
#    it under the terms of the GNU General Public License as published by
7
#    the Free Software Foundation, either version 3 of the License, or
8
#    (at your option) any later version.
9
#
10
#    This program is distributed in the hope that it will be useful,
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
#    GNU General Public License for more details.
14
#
15
#    You should have received a copy of the GNU General Public License
16
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
###############################################################################
18

19 3
""" # DEPRECATED DOCSTRING - KEEP UNTIL DOCUMENTATION IS READY
20
pyrpl.py - high-level lockbox functionality
21

22
A lockbox is a device that converts a number of input signals into a number of
23
output signals suitable to stabilize a physical system in a desired state. This
24
task is generally divided into two steps:
25
1) bring the system close the desired state where it can be linearized
26
2) keep it there using linear control.
27

28
The task is further divided into several subtasks:
29
0a) Condition the input signals so that they are suitable for the next steps
30
 - offset removal
31
 - input filters
32
 - demodulation / lockin
33
 - inversion
34
0b) Estimate the system state from the past and present history of input and
35
output signals.
36
0c) Build a filter for the output signals such that they can be conveniently
37
addressed with higher-level lockbox logic.
38

39
1) As above: apply reasonable action to the outputs to approach the system to
40
the desired state. We generally call this the 'search' step.
41
- provide a number of algorithms/recipes to do this
42

43
2) As above: Turn on linear feedback. We call this the 'lock' step.
44
- connect the input signals with appropriate gain to the outputs
45
- the gain depends on the state of the system, so internal state representation
46
will remain useful here
47

48
This naturally divides the lockbox object into 3 subcomponents:
49
a) inputs
50
b) internal model
51
c) outputs
52

53
which will be interconnected by the algorithms that come with the model and
54
make optimal use of the available inputs and outputs. The job of the
55
configuration file is to provide a maximum of information about the inputs,
56
outputs and the physical system (=internal model) so that the lockbox is
57
effective and robust. The lockbox will usually require both a coarse-tuning
58
and an optimization step for optimum performance, which will both adjust the
59
various parameters for the best match between model and real system.
60

61
Let's make this reasoning more clear with an example:
62

63
A Fabry-Perot cavity is to be locked near resonance using a PDH scheme. The
64
incident laser beam goes through a phase modulator. The cavity contains a piezo
65
with estimated bandwidth 10 kHz (appearance of first resonance) and
66
a displacement of 350 nm/V that goes into the piezo amplifier. To limit the
67
effect of amplifier noise, we have inserted an RC lowpass between amplifier and
68
piezo with a cutoff frequency of 100 Hz. The laser contains another piezo with
69
estimated bandwidth of 50 kHz that changes the laser frequency by 5 MHz/V. An
70
RC filter provides a lowpass with 1kHz cutoff. Finally, the cavity can be tuned
71
through its temperature with a bandwidth slower than 0.1 Hz. We estimate from
72
thermal expansion coefficients that 1 V to the Peltier amplifier leading to 3 K
73
heating of the cavity spacer should lead to 30ppm/K*20cm*3K/V = 18 micron/V
74
length change. Both reflection and transmission of the cavity are available
75
error signals. The finesse of the cavity is 5000, and therefore there are
76
large regions of output signals where no useful error signal can be obtained.
77

78
We first generate a clean list of available inputs and outputs and some
79
parameters of the cavity that we know already:
80

81
inputs:
82
  in1:
83
    reflection
84
  in2:
85
    transmission
86
  # also possible
87
  # in2: pdh # for externally generated pdh
88
outputs:
89
  out1:
90
    # we insert a bias-T with separation frequency around 1 MHz behind out1
91
    # this allows us to use the fast output for both the piezo and PDH
92
    modulator:
93
      amplitude: 0.1
94
      frequency: 50e6
95
    cavitypiezo:
96
      # piezo specification: 7 micron/1000V
97
      # amplifier gain: 50
98
      # therefore effective DC gain: 350nm/V
99
      m_per_V: 350e-9
100
      bandwidth: 100.0
101
  out2:
102
    laserpiezo:
103
      Hz_per_V: 5e6
104
      bandwidth: 1e3
105
  pwm1:
106
    temperature:
107
      m_per_V: 18e-6
108
      bandwidth: 0.1
109
model:
110
  type: fabryperot
111
  wavelength: 1064e-9
112
  finesse: 5000
113
  # round-trip length in m (= twice the length for ordinary Fabry-Perot)
114
  length: 0.72
115
  lock: # lock methods in order of preferrence
116
    order:
117
      pdh
118
      reflection
119
      transmission
120
    # when approaching a resonance, we can either abruptly jump or smoothly
121
    # ramp from one error signal to another. We specify our preferrence with
122
    # the order of keywords after transition
123
    transition: [ramp, jump]
124
    # target value for our lock. The API provides many ways to adjust this at
125
    # runtime
126
    target:
127
      detuning: 0
128
  # search algorithms to use in order of preferrence, as available in model
129
  search:
130
    drift
131
    bounce
132

133
Having selected fabryperot as modeltype, the code will automatically search
134
for a class named fabryperot in the file model.py to provide for the internal
135
state representation and all algorithms. You can create your own model by
136
adding other classes to this file, or by inheriting from existing ones and
137
adding further functionality. The naming of all other configuration parameters
138
is linked to the model, since all functionality that makes use of these para-
139
meters is implemented there. Another very often used model type is
140
"interferometer". The only difference is here that
141

142
"""
143

144 3
from __future__ import print_function
145

146 3
import logging
147 3
import os
148 3
import os.path as osp
149 3
from shutil import copyfile
150 3
from qtpy import QtCore, QtWidgets
151

152 3
from .widgets.pyrpl_widget import PyrplWidget
153 3
from . import software_modules
154 3
from .memory import MemoryTree
155 3
from .redpitaya import RedPitaya
156 3
from . import pyrpl_utils
157 3
from .software_modules import get_module
158 3
from .async_utils import sleep as async_sleep
159

160
# it is important that Lockbox is loaded before the models
161
#from .software_modules.lockbox import *
162 3
from .software_modules import lockbox
163 3
from .software_modules.lockbox import models
164
#from .software_modules.lockbox.models import *  # make sure all models are
165
# loaded when we get started
166 3
from . import user_config_dir
167

168
# input is the wrong function in python 2
169 3
try:
170 3
    raw_input
171 2
except NameError:  # Python 3
172 2
    raw_input = input
173

174 3
try:
175 3
    basestring  # in python 2
176 2
except:
177 2
    basestring = (str, bytes)
178

179

180 3
default_pyrpl_config = {'name': 'default_pyrpl_instance',
181
                        'gui': True,
182
                        'loglevel': 'info',
183
                        'background_color': '',
184
                        # reasonable options:
185
                        # 'CCCCEE',  # blueish
186
                        # 'EECCCC', # reddish
187
                        # 'CCEECC', # greenish
188
                        'modules': ['NetworkAnalyzer',
189
                                    'SpectrumAnalyzer',
190
                                    'CurveViewer',
191
                                    'PyrplConfig',
192
                                    'Lockbox'
193
                                    ]}
194

195 3
class Pyrpl(object):
196
    """
197
    Higher level object, in charge of loading the right hardware and software
198
    module, depending on the configuration described in a config file.
199

200
    Parameters
201
    ----------
202
    config: str
203
        Name of the config file. No .yml extension is needed. The file
204
        should be located in the config directory.
205
    source: str
206
        If None, it is ignored. Else, the file 'source' is taken as a
207
        template config file and copied to 'config' if that file does
208
        not exist.
209
    **kwargs: dict
210
        Additional arguments can be passed and will be written to the
211
        redpitaya branch of the config file. See class definition of
212
        RedPitaya for possible keywords.
213
    """
214 3
    def __init__(self,
215
                 config=None,
216
                 source=None,
217
                 **kwargs):
218
        # logger initialisation
219 3
        self.logger = logging.getLogger(name='pyrpl') # default: __name__
220
        # use gui or commandline for questions?
221 3
        gui = 'gui' not in kwargs or kwargs['gui']
222
        # get config file if None is specified
223 3
        if config is None:
224 0
            if gui:
225 0
                self.logger.info("Please select or create a configuration "
226
                                 "file in the file selector window!")
227 0
                config = QtWidgets.QFileDialog.getSaveFileName(
228
                                directory=user_config_dir,
229
                                caption="Pick or create a configuration "
230
                                        "file, or hit 'cancel' for no "
231
                                        "file (all configuration will be "
232
                                        "discarded after restarting)!",
233
                                options=QtWidgets.QFileDialog.DontConfirmOverwrite,
234
                                filter='*.yml')
235 0
                if not isinstance(config, basestring):
236 0
                    config = config[0]
237
            else:  # command line
238 0
                configfiles = [name for name in os.listdir(user_config_dir)
239
                               if name.endswith('.yml')]
240 0
                configfiles = [name[:-4] if name.endswith('.yml') else name
241
                               for name in configfiles]
242 0
                print("Existing config files are:")
243 0
                for name in configfiles:
244 0
                    print("    %s"%name)
245 0
                config = raw_input('\nEnter an existing or new config file name: ')
246 3
        if config is None or config == "" or config.endswith('/.yml'):
247 0
            config = None
248
        # configuration is retrieved from config file
249 3
        self.c = MemoryTree(filename=config, source=source)
250 3
        if self.c._filename is not None:
251 3
            self.logger.info("All your PyRPL settings will be saved to the "
252
                             "config file\n"
253
                             "    %s\n"
254
                             "If you would like to restart "
255
                             "PyRPL with these settings, type \"pyrpl.exe "
256
                             "%s\" in a windows terminal or \n"
257
                             "    from pyrpl import Pyrpl\n"
258
                             "    p = Pyrpl('%s')\n"
259
                             "in a python terminal.",
260
                             self.c._filename,
261
                             self.c._filename_stripped,
262
                             self.c._filename_stripped)
263
        # make sure config file has the required sections and complete with
264
        # missing entries from default
265 3
        pyrplbranch = self.c._get_or_create('pyrpl')
266 3
        for k in default_pyrpl_config:
267 3
            if k not in pyrplbranch._keys():
268 3
                if k =='name':
269
                    # assign the same name as in config file by default
270 0
                    pyrplbranch[k] = self.c._filename_stripped
271
                else:
272
                    # all other (static) defaults
273 3
                    pyrplbranch[k] = default_pyrpl_config[k]
274
        # set global logging level if specified in config file
275 3
        pyrpl_utils.setloglevel(level=self.c.pyrpl.loglevel,
276
                                loggername='pyrpl')
277
        # initialize RedPitaya object with the configured or default parameters
278 3
        self.c._get_or_create('redpitaya')
279 3
        self.c.redpitaya._update(kwargs)
280 3
        self.name = pyrplbranch.name
281 3
        self.rp = RedPitaya(config=self.c)
282 0
        self.rp.parent=self
283 0
        self.widgets = [] # placeholder for widgets
284
        # create software modules...
285 0
        self.load_software_modules()
286
        # load all setup_attributes for modules that do not have an owner
287 0
        for module in self.software_modules + self.hardware_modules:
288 0
            if module.owner is None:
289 0
                module._load_setup_attributes()
290
                # try:
291
                #     module._load_setup_attributes()
292
                # except BaseException as e:
293
                #     self.logger.error('Something went wrong when loading the '
294
                #                       'stored setup_attributes of module "%s". '
295
                #                       'If you do not know what this means, you should '
296
                #                       'be able to fix this error by deleting the '
297
                #                       'corresponding section "%s" in your config file %s. '
298
                #                       'Error message: %s',
299
                #                       module.name, module.name, self.c._filename, e)
300
                #     raise e
301
        # make the gui if applicable
302 0
        if self.c.pyrpl.gui:
303 0
            self.show_gui()
304

305 3
    def show_gui(self):
306 0
        if len(self.widgets) == 0:
307 0
            widget = self._create_widget()
308 0
            widget.show()
309
        else:
310 0
            for w in self.widgets:
311 0
                w.show()
312

313 3
    def hide_gui(self):
314 0
        for w in self.widgets:
315 0
            w.hide()
316

317 3
    def load_software_modules(self):
318
        """
319
        load all software modules defined as root element of the config file.
320
        """
321 0
        self.software_modules = []
322
        # software modules are Managers for various modules plus those defined in the config file
323 0
        soft_mod_names = ['Asgs', 'Iqs', 'Pids', 'Scopes', 'Iirs', 'Trigs'
324
                          ] + self.c.pyrpl.modules
325 0
        module_classes = [get_module(cls_name)
326
                          for cls_name in soft_mod_names]
327 0
        module_names = pyrpl_utils.\
328
            get_unique_name_list_from_class_list(module_classes)
329 0
        for cls, name in zip(module_classes, module_names):
330
            # some modules have generator function, e.g. Lockbox
331
            # @classmethod
332
            # def make_Lockbox(cls, parent, name): ...
333 0
            try:
334 0
                if hasattr(cls, "_make_"+cls.__name__):
335 0
                    module = getattr(cls, "_make_"+cls.__name__)(self, name)
336
                else:
337 0
                    module = cls(self, name)
338 0
            except BaseException as e:
339 0
                self.logger.error('Something went wrong when loading the software module "%s": %s',
340
                                  name, e)
341 0
                raise e
342
            else:
343 0
                setattr(self, module.name, module)
344 0
                self.software_modules.append(module)
345 0
                self.logger.debug("Created software module %s", name)
346

347 3
    @property
348
    def hardware_modules(self):
349
        """
350
        List of all hardware modules loaded in this configuration.
351
        """
352 0
        if self.rp is not None:
353 0
            return list(self.rp.modules.values())
354
        else:
355 0
            return []
356

357 3
    @property
358
    def modules(self):
359 0
        return self.hardware_modules + self.software_modules
360

361 3
    def _create_widget(self):
362
        """
363
        Creates the top-level widget
364
        """
365 0
        widget = PyrplWidget(self)
366 0
        self.widgets.append(widget)
367 0
        return widget
368

369 3
    def _clear(self):
370
        """
371
        kill all timers and closes the connection to the redpitaya
372
        """
373 0
        for module in self.modules:
374 0
            module._clear()
375 0
        for widget in self.widgets:
376 0
            widget._clear()
377 0
        while len(self.widgets)>0:  # Close all widgets
378 0
            w = self.widgets.pop()
379 0
            del w
380
        # do the job of actually destroying the widgets
381 0
        async_sleep(0.1)
382
        # make sure the save timer of the config file is not running and
383
        # all data are written to the harddisk
384 0
        self.c._write_to_file()
385
        # end redpitatya communication
386 0
        self.rp.end_all()
387 0
        async_sleep(0.1)

Read our documentation on viewing source code .

Loading