Skip to content

Commit

Permalink
already a solid skeleton for lockbox (gui for output is almost complete)
Browse files Browse the repository at this point in the history
Finally, the implementation choice is the following
Lockbox is a SoftwareModule...
Model, Inputs and Outputs are child SoftwareModules of Lockbox (concept just added now). This way, different states for each of these can be saved and loaded independently from each other...
+ all the gui comes for free.
  • Loading branch information
SamuelDeleglise committed Dec 2, 2016
1 parent 084a930 commit 08eb8ea
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 1,611 deletions.
40 changes: 37 additions & 3 deletions pyrpl/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class BaseAttribute(object):
- a function get_value(instance, owner) that reads the value from wherever it is stored internally
"""
widget_class = None
widget = None

def __init__(self, default=None, doc=""):
"""
Expand Down Expand Up @@ -107,8 +108,8 @@ def create_widget(self, module, name=None):
"""
if name is None:
name = self.name # attributed by the metaclass of module
return self.widget_class(name, module)

self.widget = self.widget_class(name, module)
return self.widget

class NumberAttribute(BaseAttribute):
"""
Expand All @@ -120,6 +121,7 @@ def create_widget(self, module, name=None):
widget.set_increment(self.increment)
widget.set_maximum(self.max)
widget.set_minimum(self.min)
self.widget = widget
return widget

def validate_and_normalize(self, value, module):
Expand Down Expand Up @@ -223,7 +225,13 @@ def options(self, obj):
else:
return self._options
"""

def change_options(self, new_options):
"""
Replace (dynamically) options by new_options.
"""
self.options = new_options
if self.widget is not None:
self.widget.change_options(new_options)

def create_widget(self, module, name=None):
"""
Expand Down Expand Up @@ -255,6 +263,32 @@ def validate_and_normalize(self, value, module):
return min([opt for opt in options], key=lambda x: abs(x - value))


#class DynamicSelectAttribute(BaseAttribute):
# """
# An attribute for a multiple choice value.
# The options are evaluated at runtime by the function options(self, instance).
# Validation is strict (value should be one of the options())
# """
# widget_class = DynamicSelectAttributeWidget
#
# def options(self, instance):
# """
# options are evaluated at run time. To be reimplemented in base class.
# """
# raise NotImplementedError("This function should be implemented in derived class.")
#
# def validate_and_normalize(self, value, module):
# """
# value should evaluate to a string present in self.options(instance) at evaluation time.
# """
# value = str(value)
# if not (value in self.options(module)):
# raise ValueError("value %s is not an option for SelectAttribute %s of %s" % (value,
# self.name,
# module.name))
# return value


class StringAttribute(BaseAttribute):
"""
An attribute for string (in practice, there is no StringRegister at this stage).
Expand Down
31 changes: 17 additions & 14 deletions pyrpl/config/tests_temp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ pyrpl: # General configuration of the software:
- NetworkAnalyzer
- NetworkAnalyzer
window_position:
- -8
- -8
- -7
- 0
- 1916
- 1017
- 1008
dock_positions: !!binary |
AAAA/wAAAAD9AAAAAQAAAAIAAAd8AAAD0PwBAAAAAfwAAAAAAAAHfAAABy8A/////AIAAAAD/AAA
ABUAAAHTAAABXwD////8AQAAAAL8AAAAAAAAA5AAAANDAP////oAAAACAQAAAAP7AAAAIgBzAHAA
AAAA/wAAAAD9AAAAAQAAAAIAAAd8AAADx/wBAAAAAfwAAAAAAAAHfAAAA0MA/////AIAAAAD/AAA
ABUAAAPHAAABSgD////8AQAAAAL8AAAAAAAAB3wAAANDAP////oAAAABAQAAAAP7AAAAIgBzAHAA
ZQBjAHQAcgB1AG0AXwBhAG4AYQBsAHkAegBlAHIBAAAAAP////8AAAAAAAAAAPsAAAAGAGkAcQBz
AQAAAAD/////AAADQwD////7AAAACABhAHMAZwBzAQAAAAAAAAd8AAACMQD////8AAADlAAAA+gA
AAPoAP////oAAAAAAgAAAAL7AAAABgBuAGEAMgEAAAAA/////wAAAO0A////+wAAAAwAcwBjAG8A
cABlAHMBAAAAFQAAAdMAAADBAP////wAAAHsAAAB+QAAAcMA/////AEAAAAC+wAAAAgAaQBpAHIA
cwEAAAAAAAADjAAAAxQA/////AAAA5AAAAPsAAAD6AD////6AAAAAgIAAAAD+wAAAAYAbgBhADEB
AAAAAP////8AAADtAP////sAAAAEAG4AYQEAAAAA/////wAAAAAAAAAA+wAAAAgAcABpAGQAcwEA
AAJbAAABrgAAAa4A////+wAAAAQAbgBhAAAAA3UAAADoAAAAAAAAAAAAAAd8AAAAAAAAAAQAAAAE
AQAAAAD/////AAADQwD////7AAAACABhAHMAZwBzAAAAAAAAAAd8AAAAFgD////8AAAAAAAAB3wA
AAAAAP////r/////AgAAAAL7AAAABgBuAGEAMgAAAAAA/////wAAAFAA////+wAAAAwAcwBjAG8A
cABlAHMAAAAAFQAAAdMAAABQAP////wAAAAVAAADxwAAAAAA/////AEAAAAC+wAAAAgAaQBpAHIA
cwAAAAAAAAAHfAAAABYA/////AAAAAAAAAd8AAAAAAD////6AAAAAQIAAAAD+wAAAAYAbgBhADEA
AAAAAP////8AAABQAP////sAAAAEAG4AYQEAAAAA/////wAAAAAAAAAA+wAAAAgAcABpAGQAcwAA
AAJbAAABrgAAAFAA////+wAAAAQAbgBhAAAAA3UAAADoAAAAAAAAAAAAAAd8AAAAAAAAAAQAAAAE
AAAACAAAAAj8AAAAAA==
redpitaya:
hostname: 10.214.1.28
Expand Down Expand Up @@ -72,8 +72,9 @@ pwms:
pwm1: {}
pwm2: {}
iqs:
iq1: {output_signal: quadrature, bandwidth: [0, 0], input: adc1, frequency: 0, acbandwidth: [
0], phase: 180.0, quadrature_factor: 0.0, output_direct: off, gain: 0.0, amplitude: 0.0}
iq1: {output_signal: quadrature, bandwidth: [1214, 2428], input: adc1, frequency: 9999.98883344233,
acbandwidth: [0], phase: 180.0, quadrature_factor: 0.1015625, output_direct: out1,
gain: 0.0, amplitude: 0.09999847412109375}
states: {yoyo: {output_direct: off, bandwidth: [0, 0], input: adc1, amplitude: 0.0,
gain: 0.0, phase: 0.0, acbandwidth: 0, quadrature_factor: 0.0, output_signal: quadrature,
frequency: 0.0}, phase90: {output_signal: quadrature, bandwidth: [0, 0], quadrature_factor: 0.0,
Expand Down Expand Up @@ -108,7 +109,7 @@ iqs:
pids:
pid3: {ival: 0.0}
pid2: {ival: 0.0}
pid4: {ival: 0.0}
pid4: {ival: 0.0, p: 0.114501953125}
pid1: {ival: 0.0, output_direct: off, d: 0.0, setpoint: 0.0, p: 0.0, inputfilter: [
0, 0, 0, 0], input: adc1, i: 0.0}
states: {iui: {output_direct: off, d: 0.0, setpoint: 0.0, p: 0.0, inputfilter: [
Expand All @@ -129,3 +130,5 @@ iirs:
input: adc1
zeros:
- (-100-947.7629626775897j)
lockboxs:
lockbox: {dummy_outputs: {dummy_output: {coucou: 3, p: 0.044885369873046886}}}
47 changes: 31 additions & 16 deletions pyrpl/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,11 @@ def setup(self, **kwds):
self._callback_active = True
return setup


class ModuleMetaClass(type):
class NameAttributesMetaClass(type):
'''
1. Magic to retrieve the name of the attributes in the attributes themselves.
Magic to retrieve the name of the attributes in the attributes themselves.
see http://code.activestate.com/recipes/577426-auto-named-decriptors/
2. Builds the setup docstring by aggregating _setup's and setup_attributes's docstrings.
'''

def __new__(cls, classname, bases, classDict):
"""
Iterate through the new class' __dict__ and update the .name of all recognised BaseAttribute.
Expand All @@ -70,6 +66,11 @@ def __new__(cls, classname, bases, classDict):
attr.name = name
return type.__new__(cls, classname, bases, classDict)


class ModuleMetaClass(NameAttributesMetaClass):
'''
Builds the setup docstring by aggregating _setup's and setup_attributes's docstrings.
'''
def __init__(self, classname, bases, classDict):
"""
Takes care of creating the module's 'setup' function.
Expand Down Expand Up @@ -163,9 +164,9 @@ def c_states(self):
"""
Returns the config file branch corresponding to the "states" section
"""
if not "states" in self.c._parent._keys():
self.c._parent["states"] = dict()
return self.c._parent.states
if not "states" in self.parent.c._keys():
self.parent.c["states"] = dict()
return self.parent.c.states

def save_state(self, name):
"""Saves the current state under the name "name" in the config file"""
Expand Down Expand Up @@ -224,9 +225,9 @@ def c(self):
section of the config file.
"""
manager_section = self.__class__.name + "s" # for instance, iqs
if not manager_section in self.pyrpl_config._keys():
self.pyrpl_config[manager_section] = dict()
manager_section = getattr(self.pyrpl_config, manager_section)
if not manager_section in self.parent.c._keys():
self.parent.c[manager_section] = dict()
manager_section = getattr(self.parent.c, manager_section)
if not self.name in manager_section._keys():
manager_section[self.name] = dict()
return getattr(manager_section, self.name)
Expand Down Expand Up @@ -375,19 +376,33 @@ class SoftwareModule(BaseModule):
ready for acquisition/output with the current setup_attributes' values.
"""

def __init__(self, pyrpl, name=None):
def __init__(self, parent, name=None):
"""
Creates a module with given name. If name is None, uses cls.name.
parent is either a pyrpl instance, or another SoftwareModule.
- First case: config file entry is in self.__class__.name + 's'--> self.name
- Second case: config file entry is in parent_entry-->self.__class__.name + 's'-->self.name
"""
self._autosave_active = False # attribute values are not overwritten in the config file
if name is not None:
self.name = name
self.pyrpl = pyrpl
self._parent = pyrpl
self.pyrpl_config = pyrpl.c
self.parent = parent
#self._parent = pyrpl
#self.pyrpl_config = pyrpl.c
self.init_module()
self._autosave_active = True

@property
def pyrpl(self):
"""
Recursively looks through patent modules untill pyrpl instance is reached.
"""
from pyrpl.pyrpl import Pyrpl
parent = self.parent
while(not isinstance(parent, Pyrpl)):
parent = parent.parent
return parent

def init_module(self):
"""
To be reimplemented in child class.
Expand Down
130 changes: 52 additions & 78 deletions pyrpl/software_modules/lockbox/lockbox.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,59 @@
###############################################################################
# pyrpl - DSP servo controller for quantum optics with the RedPitaya
# Copyright (C) 2014-2016 Leonhard Neuhaus (neuhaus@spectro.jussieu.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###############################################################################
from pyrpl.modules import SoftwareModule
from pyrpl.attributes import SelectProperty
from .model import Model
from .signals import OutputSignal

# buglist: in lock_opt, it is inconvenient to always specify sof and pdh. unlocks when only pdh is changed
# unspecified parameters should rather be left unchanged instead of being
# set to 0 or 1
from collections import OrderedDict

from pyrpl.software_modules import SoftwareModule
import logging
from .signal import logger
import os

def getmodel(modeltype):
try:
m = globals()[modeltype]
if type(m) == type:
return m
except KeyError:
pass
# try to find a similar model with lowercase spelling
for k in globals():
if k.lower() == modeltype.lower():
m = globals()[k]
if type(m) == type:
return m
logger.error("Model %s not found in model definition file %s",
modeltype, __file__)

class Lockbox(SoftwareModule):
"""generic lockbox object, no implementation-dependent details here
A lockbox defines one model of the physical system that is controlled."""
all_models = OrderedDict([(model.name, model) for model in Model.__subclasses__()])


def __init__(self, rp):
self.logger = logging.getLogger(name=__name__)
self.rp = rp
# make input and output signals
self._makesignals()
# find and setup the model
self.model = getmodel(self.c.model.modeltype)(self)
self.model.setup()

def _makesignals(self, *args, **kwargs):
""" Instantiates all signals from config file.
Optional arguments are passed to the signal class initialization. """
signalclasses, signalparameters = self._signalinit
for signaltype, signalclass in signalclasses.items():
# generalized version of: self.inputs = [reflection, transmission]
signaldict = OrderedDict()
self.__setattr__(signaltype, signaldict)
for k in self.c[signaltype].keys():
self.logger.debug("Creating %s signal %s...", signaltype, k)
# generalization of:
# self.reflection = Signal(self.c, "inputs.reflection")
signal = signalclass(self.c,
signaltype+"."+k,
**signalparameters)
signaldict[k] = signal
self.__setattr__(k, signal)
class Lockbox(SoftwareModule):
"""
A Module that allows to perform feedback on systems that are well described by a physical model.
"""
name = 'lockbox'
gui_attributes = ["model", "default_sweep_output"]
model = SelectProperty(options=all_models.keys())
default_sweep_output = SelectProperty(options=["dummy"])

def init_module(self):
self.outputs = []
self._asg = None

@property
def signals(self):
""" returns a dictionary containing all signals, i.e. all inputs and
outputs """
sigdict = dict()
signals, _ = self._signalinit
for s in signals.keys():
sigdict.update(self.__getattribute__(s))
return sigdict
def asg(self):
if self._asg==None:
self._asg = self.pyrpl.asgs.pop(self.name)
return self._asg

def sweep(self, output=None):
"""
Performs a sweep of one of the output. If no output is specified, the default sweep_output is used.
"""
self.unlock
if output is None:
output = self.default_sweep_output
self._asg.output = output

def add_output(self):
"""
Outputs of the lockbox are added dynamically (for now, inputs are defined by the model).
"""
output = OutputSignal(self)
self.outputs.append(output)
setattr(self, output.name, output)
self.__class__.default_sweep_output.change_options([output.name for output in self.outputs])

def unlock(self):
for output in self.outputs:
output.unlock()

def get_model(self):
return all_models[self.model](self)

def model_changed(self):
for output in self.outputs:
output.update_for_model()
self.inputs = self.get_model()
Loading

0 comments on commit 08eb8ea

Please sign in to comment.