class PulserParameterModel(CategoryTreeModel):
    expression = Expression()
    def __init__(self, parameterList, parent=None):
        super(PulserParameterModel, self).__init__(parameterList, parent)
        self.headerLookup.update({
            (QtCore.Qt.Horizontal, QtCore.Qt.DisplayRole, 0): 'Name',
            (QtCore.Qt.Horizontal, QtCore.Qt.DisplayRole, 1): 'Value'
            })
        self.dataLookup.update({
            (QtCore.Qt.DisplayRole, 0): lambda node: node.content.name,
            (QtCore.Qt.DisplayRole, 1): lambda node: str(node.content.value),
            (QtCore.Qt.EditRole, 1): lambda node: node.content.string,
            (QtCore.Qt.BackgroundRole, 1): self.dependencyBgFunction,
            (QtCore.Qt.ToolTipRole, 1): self.toolTipFunction
            })
        self.setDataLookup.update({
            (QtCore.Qt.EditRole, 1): lambda index, value: self.setValue(index, value),
            (QtCore.Qt.UserRole, 1): lambda index, value: self.setStrValue(index, value)
            })
        self.flagsLookup.update({
            0: QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable,
            1: QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable
            })
        self.numColumns = 2

    def setValue(self, index, value):
        node = self.nodeFromIndex(index)
        node.content.value = value
        return True
         
    def setStrValue(self, index, strValue):
        node = self.nodeFromIndex(index)
        node.content.string = strValue
        return True
class MeanEvaluation(EvaluationBase):
    name = 'Mean'
    tooltip = "Mean of observed counts" 
    errorBarTypes = ['shotnoise','statistical','min max']
    expression = Expression()
    def __init__(self, globalDict=None, settings=None):
        EvaluationBase.__init__(self, globalDict, settings)
        self.errorBarTypeLookup = {'shotnoise': self.evaluateShotnoise,
                                   'statistical': self.evaluateStatistical,
                                   'min max': self.evaluateMinMax}
        
    def setDefault(self):
        self.settings.setdefault('errorBarType', 'shotnoise')
        self.settings.setdefault('transformation', "")
        if type(self.settings['errorBarType']) in (int, float):
            self.settings['errorBarType'] = self.errorBarTypes[self.settings['errorBarType']]
         
    def evaluateShotnoise(self, countarray ):
        summe = numpy.sum( countarray )
        l = float(len(countarray))
        mean = summe/l
        stderror = math.sqrt( max(summe,1) )/l
        return mean, (stderror/2. if summe>0 else 0, stderror/2. ), summe

    def evaluateStatistical(self, countarray):
        mean = numpy.mean( countarray )
        stderr = numpy.std( countarray, ddof=1 ) / math.sqrt( max( len(countarray)-1, 1) )
        return mean, (stderr/2.,stderr/2.), numpy.sum( countarray )
    
    def evaluateMinMax(self, countarray):
        mean = numpy.mean( countarray )
        return mean, (mean-numpy.min(countarray), numpy.max(countarray)-mean), numpy.sum(countarray)
    
    def evaluate(self, data, evaluation, expected=None, ppDict=None, globalDict=None):
        countarray = evaluation.getChannelData(data)
        if not countarray:
            return 0, (0,0), 0
        mean, (minus, plus), raw =  self.errorBarTypeLookup[self.settings['errorBarType']](countarray)
        if self.settings['transformation']!="":
            mydict = { 'y': mean }
            if ppDict:
                mydict.update( ppDict )
            mean = float(self.expression.evaluate(self.settings['transformation'], mydict))
            mydict['y'] = mean+plus
            plus = float(self.expression.evaluate(self.settings['transformation'], mydict))
            mydict['y'] = mean-minus
            minus = float(self.expression.evaluate(self.settings['transformation'], mydict))
            return mean, (mean-minus, plus-mean), raw
        return mean, (minus, plus), raw

    def parameters(self):
        parameterDict = super(MeanEvaluation, self).parameters()
        if isinstance(self.settings['errorBarType'], int): #preserve backwards compatibility -- previously errorBarType was an int, now it's a string, so we map it over
            self.settings['errorBarType'] = self.errorBarTypes[self.settings['errorBarType']]
        parameterDict['errorBarType'] = Parameter(name='errorBarType', dataType='select',
                                                  choices=self.errorBarTypes, value=self.settings['errorBarType'])
        parameterDict['transformation'] = Parameter(name='transformation', dataType='str',
                                                    value=self.settings['transformation'], tooltip="use y for the result in a mathematical expression")
        return parameterDict
Beispiel #3
0
 def evaluate(self, globalDict):
     for parameter in list(self.parameterDict.values()):
         if parameter.text is not None:
             value = Expression().evaluateAsMagnitude(
                 parameter.text, globalDict)
             parameter.value = value  # set saved value to match new parameter text
             modelIndex = self.createIndex(
                 self.parameterDict.index(parameter.name),
                 self.column.value)
             self.dataChanged.emit(modelIndex, modelIndex)
             self.valueChanged.emit(parameter)
Beispiel #4
0
class VarAsOutputChannel(object):
    """This is a class that makes the AWG parameters work as an external parameter output channel in a parameter scan.

    The AWG variables are not external parameter output channels, but the external parameter scan method needs the scan
    parameter to have several specific methods and attributes (as OutputChannel does). This class provides those attributes."""
    expression = Expression()

    def __init__(self, awgUi, name, globalDict):
        self.awgUi = awgUi
        self.name = name
        self.useExternalValue = False
        self.savedValue = None
        self.globalDict = globalDict

    @property
    def value(self):
        return self.awgUi.settings.varDict[self.name]['value']

    @property
    def strValue(self):
        return self.awgUi.settings.varDict[self.name]['text']

    @property
    def device(self):
        return self.awgUi.device

    def saveValue(self, overwrite=True):
        """save current value"""
        if self.savedValue is None or overwrite:
            self.savedValue = self.value
        return self.savedValue

    def setValue(self, targetValue):
        """set the variable to targetValue"""
        if targetValue is not None:
            self.awgUi.settings.varDict[self.name]['value'] = targetValue
            modelIndex = self.awgUi.tableModel.createIndex(
                self.awgUi.settings.varDict.index(self.name),
                self.awgUi.tableModel.column.value)
            self.awgUi.tableModel.dataChanged.emit(modelIndex, modelIndex)
            for channelUi in self.awgUi.awgChannelUiList:
                channelUi.replot()
            self.device.program()
        return True

    def restoreValue(self):
        """restore the value saved previously, if any, then clear the saved value."""
        value = self.savedValue if self.strValue is None else self.expression.evaluateAsMagnitude(
            self.strValue, self.globalDict)
        if value is not None:
            self.setValue(value)
            self.savedValue = None
        return True
Beispiel #5
0
class DACChannelSetting(object):
    expression = Expression()

    def __init__(self, globalDict=None):
        self._globalDict = None
        self._voltage = ExpressionValue(None, self._globalDict)
        self.enabled = False
        self.name = ""
        self.resetAfterPP = False

    def __setstate__(self, state):
        self.__dict__ = state
        self.__dict__.setdefault('resetAfterPP', False)
        self.__dict__.setdefault('_globalDict', dict())

    def __getstate__(self):
        dictcopy = dict(self.__dict__)
        dictcopy.pop('_globalDict', None)
        return dictcopy

    @property
    def outputVoltage(self):
        return self._voltage.value if self.enabled else Q(0, 'V')

    @property
    def globalDict(self):
        return self._globalDict

    @globalDict.setter
    def globalDict(self, globalDict):
        self._globalDict = globalDict
        self._voltage.globalDict = globalDict

    @property
    def voltage(self):
        return self._voltage.value

    @voltage.setter
    def voltage(self, v):
        self._voltage.value = v

    @property
    def voltageText(self):
        return self._voltage.string

    @voltageText.setter
    def voltageText(self, s):
        self._voltage.string = s

    @SetterProperty
    def onChange(self, onChange):
        self._voltage.valueChanged.connect(onChange)
class AWGSegment(AWGSegmentNode):
    expression = Expression()

    def __init__(self, parent, *args, **kwds):
        super(AWGSegment, self).__init__(parent)
        self.nodeType = nodeTypes.segment
        self.stack = None
        self.equation = kwds.get('equation', 'V0')
        self.duration = kwds.get('duration', 'T0')

    def __setstate__(self, state):
        self.__dict__ = state
        #self.__dict__['expression'] = Expression()
        self.__dict__['stack'] = None
Beispiel #7
0
class FreerunningGenerator:
    expression = Expression()

    def __init__(self, scan):
        self.scan = scan

    def prepare(self, pulseProgramUi, maxUpdatesToWrite=None):
        if self.scan.gateSequenceUi.settings.enabled:
            _, data, self.gateSequenceSettings = self.scan.gateSequenceUi.gateSequenceScanData(
            )
        else:
            data = []
        return ([], data)  # write 5 points to the fifo queue at start,
        # this prevents the Step in Place from stopping in case the computer lags behind evaluating by up to 5 points

    def restartCode(self, currentIndex):
        return []

    def dataNextCode(self, experiment):
        return None

    def xValue(self, index, data):
        return self.expression.evaluate(
            self.scan.xExpression,
            {'x': data.scanvalue if data.scanvalue else 0
             }) if self.scan.xExpression else data.scanvalue

    def xRange(self):
        return []

    def appendData(self, traceList, x, evaluated, timeinterval):
        if evaluated and traceList:
            traceList[0].x = numpy.append(traceList[0].x, x)
            traceList[0].timeintervalAppend(timeinterval)
            for trace, (y, error, raw) in zip(traceList, evaluated):
                trace.y = numpy.append(trace.y, y)
                trace.raw = numpy.append(trace.raw, raw)
                if error is not None:
                    trace.bottom = numpy.append(trace.bottom, error[0])
                    trace.top = numpy.append(trace.top, error[1])

    def dataOnFinal(self, experiment, currentState):
        experiment.onStop()

    def expected(self, index):
        return None
Beispiel #8
0
class DDSChannelSettings(object):
    expression = Expression()

    def __init__(self):
        self.frequency = Q(0, 'MHz')
        self.phase = Q(0)
        self.amplitude = Q(0)
        self.frequencyText = None
        self.phaseText = None
        self.amplitudeText = None
        self.enabled = False
        self.name = ""
        self.squareEnabled = False
        self.shutter = None
        self.channel = None

    def __setstate__(self, state):
        self.__dict__ = state
        self.__dict__.setdefault('name', '')
        self.__dict__.setdefault('squareEnabled', False)
        self.__dict__.setdefault('shutter', None)
        self.__dict__.setdefault('channel', None)

    def evaluateFrequency(self, globalDict):
        if self.frequencyText:
            oldfreq = self.frequency
            self.frequency = self.expression.evaluateAsMagnitude(
                self.frequencyText, globalDict)
            return self.frequency != oldfreq
        return False

    def evaluateAmplitude(self, globalDict):
        if self.amplitudeText:
            oldamp = self.amplitude
            self.amplitude = self.expression.evaluateAsMagnitude(
                self.amplitudeText, globalDict)
            return oldamp != self.amplitude
        return False

    def evaluatePhase(self, globalDict):
        if self.phaseText:
            oldphase = self.phase
            self.phase = self.expression.evaluateAsMagnitude(
                self.phaseText, globalDict)
            return oldphase != self.phase
        return False
Beispiel #9
0
class GateSequenceCompiler(object):
    expression = Expression()

    def __init__(self, pulseProgram):
        self.pulseProgram = pulseProgram
        self.compiledGates = dict()

    """Compile all gate sequences into binary representation
        returns tuple of start address list and bytearray data"""

    def gateSequencesCompile(self, gatesets):
        logger = logging.getLogger(__name__)
        logger.info("compiling {0} gateSequences.".format(
            len(gatesets.GateSequenceDict)))
        self.gateCompile(gatesets.gateDefinition)
        addresses = list()
        data = list()
        index = 0
        for gateset in list(gatesets.GateSequenceDict.values()):
            gatesetdata = self.gateSequenceCompile(gateset)
            addresses.append(index)
            data.extend(gatesetdata)
            index += len(gatesetdata) * 8
        return addresses, data

    """Compile one gateset into its binary representation"""

    def gateSequenceCompile(self, gateset):
        data = list()
        length = 0
        for gate in gateset:
            thisCompiledGate = self.compiledGates[gate]
            data.extend(thisCompiledGate)
            length += len(thisCompiledGate) // self.pulseListLength
        return [length] + data

    """Compile each gate definition into its binary representation"""

    def gateCompile(self, gateDefinition):
        logger = logging.getLogger(__name__)
        variables = self.pulseProgram.variables()
        pulseList = list(gateDefinition.PulseDefinition.values())
        self.pulseListLength = len(pulseList)
        for gatename, gate in gateDefinition.Gates.items(
        ):  # for all defined gates
            data = list()
            gateLength = 0
            for name, strvalue in gate.pulsedict:
                result = self.expression.evaluate(strvalue, variables)
                if name != pulseList[gateLength % self.pulseListLength].name:
                    raise GateSequenceCompilerException(
                        "In gate {0} entry {1} found '{2}' expected '{3}'".
                        format(gatename, gateLength, name,
                               pulseList[gateLength % self.pulseListLength]))
                encoding = gateDefinition.PulseDefinition[name].encoding
                data.append(
                    self.pulseProgram.convertParameter(result, encoding))
                gateLength += 1
            if gateLength % self.pulseListLength != 0:
                raise GateSequenceCompilerException(
                    "In gate {0} number of entries ({1}) is not a multiple of the pulse definition length ({2})"
                    .format(gatename, gateLength, self.pulseListLength))
            self.compiledGates[gatename] = data
            logger.info("compiled {0} to {1}".format(gatename, data))
# *****************************************************************
# IonControl:  Copyright 2016 Sandia Corporation
# This Software is released under the GPL license detailed
# in the file "license.txt" in the top-level IonControl directory
# *****************************************************************
import unittest
from modules.Expression import Expression
import math
from modules.quantity import Q
ExprEval = Expression()


def e(expr, vars=dict(), useFloat=False):
    return ExprEval.evaluate(expr, vars, useFloat=useFloat)


class TestExpression(unittest.TestCase):
    def test_literals(self):
        self.assertEqual(e("9"), 9)
        self.assertEqual(e('-9'), -9)
        self.assertEqual(e('--9'), 9)
        self.assertEqual(e('-E'), -math.e)
        self.assertEqual(e('9 + 3 + 6 + 25'), 9 + 3 + 6 + 25)
        self.assertEqual(e("9 + 3 / 11"), 9 + 3 / 11)
        self.assertEqual(e('9 * (7 + 28) / 12'), 9 * (7 + 28) / 12)
        self.assertEqual(e("9 - 12 - 6"), 9 - 12 - 6)
        self.assertEqual(e("9 - (12 - 6)"), 9 - (12 - 6))
        self.assertEqual(e("2 * 3.14159"), 2 * 3.14159)
        self.assertEqual(e("3.1415926535 * 3.1415926535 / 10"),
                         3.1415926535 * 3.1415926535 / 10)
        self.assertEqual(e("PI * PI / 10"), math.pi * math.pi / 10)
class CounterSumMeanEvaluation(EvaluationBase):
    """Evaluate the mean of a sum of counters"""
    name = 'Counter Sum Mean'
    tooltip = "Mean of sum of observed counts"
    hasChannel = False
    errorBarTypes = ['shotnoise', 'statistical', 'min max']
    expression = Expression()

    def __init__(self, globalDict=None, settings=None):
        EvaluationBase.__init__(self, globalDict, settings)
        self.errorBarTypeLookup = {'shotnoise': self.evaluateShotnoise,
                                   'statistical': self.evaluateStatistical,
                                   'min max': self.evaluateMinMax}

    def setDefault(self):
        self.settings.setdefault('errorBarType', 'shotnoise')
        self.settings.setdefault('transformation', "")
        self.settings.setdefault('counters', [])
        self.settings.setdefault('id', 0)

    def evaluateShotnoise(self, countarray):
        summe = numpy.sum(countarray)
        l = float(len(countarray))
        mean = summe / l
        stderror = math.sqrt(max(summe, 1)) / l
        return mean, (stderror / 2. if summe > 0 else 0, stderror / 2.), summe

    def evaluateStatistical(self, countarray):
        mean = numpy.mean(countarray)
        stderr = numpy.std(countarray, ddof=1) / math.sqrt(max(len(countarray) - 1, 1))
        return mean, (stderr / 2., stderr / 2.), numpy.sum(countarray)

    def evaluateMinMax(self, countarray):
        mean = numpy.mean(countarray)
        return mean, (mean - numpy.min(countarray), numpy.max(countarray) - mean), numpy.sum(countarray)

    def getCountArray(self, data):
        counters = [((self.settings['id']&0xff)<<8) | (int(counter) & 0xff) for counter in self.settings['counters']]
        listOfCountArrays = [data.count[counter] for counter in counters if counter in data.count.keys()]
        countarray = [sum(sublist) for sublist in zip(*listOfCountArrays)]
        return countarray

    def evaluate(self, data, evaluation, expected=None, ppDict=None, globalDict=None):
        countarray = self.getCountArray(data)
        if not countarray:
            return 0, (0, 0), 0
        mean, (minus, plus), raw = self.errorBarTypeLookup[self.settings['errorBarType']](countarray)
        if self.settings['transformation'] != "":
            mydict = {'y': mean}
            if ppDict:
                mydict.update(ppDict)
            mean = float(self.expression.evaluate(self.settings['transformation'], mydict))
            mydict['y'] = mean + plus
            plus = float(self.expression.evaluate(self.settings['transformation'], mydict))
            mydict['y'] = mean - minus
            minus = float(self.expression.evaluate(self.settings['transformation'], mydict))
            return mean, (mean - minus, plus - mean), raw
        return mean, (minus, plus), raw

    def histogram(self, data, evaluation, histogramBins=50 ):
        countarray = self.getCountArray(data)
        y, x = numpy.histogram( countarray, range=(0, histogramBins), bins=histogramBins)
        return y, x, None   # third parameter is optional function

    def parameters(self):
        parameterDict = super(CounterSumMeanEvaluation, self).parameters()
        parameterDict['id'] = Parameter(name='id', dataType='magnitude', value=self.settings['id'],
                                        text=self.settings.get( ('id', 'text') ), tooltip='id of counters to sum')
        parameterDict['counters'] = Parameter(name='counters', dataType='multiselect',
                                              value=self.settings['counters'], choices=list(map(str, range(16))),
                                              tooltip='counters to sum')
        parameterDict['errorBarType'] = Parameter(name='errorBarType', dataType='select',
                                                  choices=self.errorBarTypes, value=self.settings['errorBarType'])
        parameterDict['transformation'] = Parameter(name='transformation', dataType='str',
                                                    value=self.settings['transformation'],
                                                    tooltip="use y for the result in a mathematical expression")
        return parameterDict
class Adjust(object):
    expression = Expression()
    dataChangedObject = DataChangedS()
    dataChanged = dataChangedObject.dataChanged

    def __init__(self, globalDict=dict()):
        self._globalDict = globalDict
        self._line = ExpressionValue(name="line",
                                     globalDict=globalDict,
                                     value=Q(0.0))
        self._lineValue = self._line.value
        self._lineGain = ExpressionValue(name="lineGain",
                                         globalDict=globalDict,
                                         value=Q(1.0))
        self._globalGain = ExpressionValue(name="globalGain",
                                           globalDict=globalDict,
                                           value=Q(1.0))
        self._line.valueChanged.connect(self.onLineExpressionChanged)
        self._lineGain.valueChanged.connect(self.onLineExpressionChanged)
        self._globalGain.valueChanged.connect(self.onLineExpressionChanged)

    @property
    def globalDict(self):
        return self._globalDict

    @globalDict.setter
    def globalDict(self, globalDict):
        self._globalDict = globalDict
        self._lineGain.globalDict = globalDict
        self._globalGain.globalDict = globalDict
        self._line.globalDict = globalDict

    @property
    def line(self):
        return self._lineValue

    @line.setter
    def line(self, value):
        self._lineValue = value

    @property
    def lineGain(self):
        return self._lineGain.value

    @lineGain.setter
    def lineGain(self, value):
        self._lineGain.value = value

    @property
    def globalGain(self):
        return self._globalGain.value

    @globalGain.setter
    def globalGain(self, value):
        self._globalGain.value = value

    @property
    def lineString(self):
        return self._line.string

    @lineString.setter
    def lineString(self, s):
        self._line.string = s

    @property
    def lineGainString(self):
        return self._lineGain.string

    @lineGainString.setter
    def lineGainString(self, s):
        self._lineGain.string = s

    @property
    def globalGainString(self):
        return self._globalGain.value

    @globalGainString.setter
    def globalGainString(self, s):
        self._globalGain.string = s

    def __getstate__(self):
        dictcopy = dict(self.__dict__)
        dictcopy.pop('_globalDict', None)
        return dictcopy

    def __setstate__(self, state):
        state.setdefault('_globalDict', dict())
        state.pop('line', None)
        self.__dict__ = state
        if not isinstance(self._line, ExpressionValue):
            self._line = ExpressionValue(name="line",
                                         globalDict=self._globalDict,
                                         value=Q(self._line))
        if not isinstance(self._lineGain, ExpressionValue):
            self._lineGain = ExpressionValue(name='lineGain',
                                             globalDict=self._globalDict,
                                             value=Q(self._lineGain))
        if not isinstance(self._globalGain, ExpressionValue):
            self._globalGain = ExpressionValue(name='globalGain',
                                               globalDict=self._globalDict,
                                               value=Q(self._globalGain))
        self._lineValue = self._line.value
        self._line.valueChanged.connect(self.onLineExpressionChanged)
        self._lineGain.valueChanged.connect(self.onLineExpressionChanged)
        self._globalGain.valueChanged.connect(self.onLineExpressionChanged)

    def onLineExpressionChanged(self, name, value, string, origin):
        if name == 'line':
            self._lineValue = value
        self.dataChanged.emit(self)
Beispiel #13
0
class ParameterScanGenerator:
    expression = Expression()

    def __init__(self, scan):
        self.scan = scan
        self.nextIndexToWrite = 0
        self.numUpdatedVariables = 1

    def prepare(self, pulseProgramUi, maxUpdatesToWrite=None):
        self.maxUpdatesToWrite = maxUpdatesToWrite
        if self.scan.gateSequenceUi.settings.enabled:
            _, data, self.gateSequenceSettings = self.scan.gateSequenceUi.gateSequenceScanData(
            )
        else:
            data = []
        parameterName = self.scan.scanParameter if self.scan.scanTarget == 'Internal' else self.scan.parallelInternalScanParameter
        if parameterName == "None":
            self.scan.code, self.numVariablesPerUpdate = NoneScanCode * len(
                self.scan.list), 1
        else:
            self.scan.code, self.numVariablesPerUpdate = pulseProgramUi.variableScanCode(
                parameterName, self.scan.list, extendedReturn=True)
        self.numUpdatedVariables = len(self.scan.code) // 2 // len(
            self.scan.list)
        maxWordsToWrite = MaxWordsInFifo if maxUpdatesToWrite is None else 2 * self.numUpdatedVariables * maxUpdatesToWrite
        if len(self.scan.code) > maxWordsToWrite:
            self.nextIndexToWrite = maxWordsToWrite
            return self.scan.code[:maxWordsToWrite], data
        self.nextIndexToWrite = len(self.scan.code)
        return self.scan.code, data

    def restartCode(self, currentIndex):
        maxWordsToWrite = MaxWordsInFifo if self.maxUpdatesToWrite is None else 2 * self.numUpdatedVariables * self.maxUpdatesToWrite
        currentWordCount = 2 * self.numUpdatedVariables * currentIndex
        if len(self.scan.code) - currentWordCount > maxWordsToWrite:
            self.nextIndexToWrite = maxWordsToWrite + currentWordCount
            return (self.scan.code[currentWordCount:self.nextIndexToWrite])
        self.nextIndexToWrite = len(self.scan.code)
        return self.scan.code[currentWordCount:]

    def xValue(self, index, data):
        value = self.scan.list[index]
        if self.scan.xExpression:
            value = self.expression.evaluate(self.scan.xExpression,
                                             {"x": value})
        if not is_Q(value):
            return value
        if (not self.scan.xUnit
                and not value.dimensionless) or not value.dimensionality == Q(
                    1, self.scan.xUnit).dimensionality:
            self.scan.xUnit = value.to_compact()
        return value.m_as(self.scan.xUnit)

    def dataNextCode(self, experiment):
        if self.nextIndexToWrite < len(self.scan.code):
            start = self.nextIndexToWrite
            self.nextIndexToWrite = min(
                len(self.scan.code) + 1,
                self.nextIndexToWrite + 2 * self.numUpdatedVariables)
            return self.scan.code[start:self.nextIndexToWrite]
        return []

    def dataOnFinal(self, experiment, currentState):
        experiment.onStop()

    def xRange(self):
        return self.scan.start.m_as(self.scan.xUnit), self.scan.stop.m_as(
            self.scan.xUnit)

    def appendData(self, traceList, x, evaluated, timeinterval):
        if evaluated and traceList:
            traceList[0].x = numpy.append(traceList[0].x, x)
            traceList[0].timeintervalAppend(timeinterval)
        for trace, (y, error, raw) in zip(traceList, evaluated):
            trace.y = numpy.append(trace.y, y)
            trace.raw = numpy.append(trace.raw, raw)
            if error is not None:
                trace.bottom = numpy.append(trace.bottom, error[0])
                trace.top = numpy.append(trace.top, error[1])

    def expected(self, index):
        return None
Beispiel #14
0
class PushVariable(object):
    expression = Expression()
    XMLTagName = "PushVariable"

    def __init__(self):
        self.push = False
        self.destinationName = None
        self.variableName = None
        self.definition = ""
        self.value = None
        self.minimum = ""
        self.maximum = ""
        self.strMinimum = None
        self.strMaximum = None
        self.valueValid = True
        self.minValid = True
        self.maxValid = True

    def __setstate__(self, s):
        self.__dict__ = s
        self.__dict__.setdefault('destinationName', None)
        self.__dict__.setdefault('variableName', None)
        self.__dict__.setdefault('strMinimum', None)
        self.__dict__.setdefault('strMaximum', None)
        self.__dict__.setdefault('valueValid', True)
        self.__dict__.setdefault('minValid', True)
        self.__dict__.setdefault('maxValid', True)

    stateFields = [
        'push', 'definition', 'destinationName', 'variableName', 'value',
        'minimum', 'maximum', 'strMinimum', 'strMaximum', 'valueValid',
        'minValid', 'maxValid'
    ]

    def exportXml(self, element):
        myElement = ElementTree.SubElement(element, self.XMLTagName)
        xmlEncodeAttributes(self.__dict__, myElement)
        return myElement

    @staticmethod
    def fromXmlElement(element, flat=False):
        myElement = element if flat else element.find(PushVariable.XMLTagName)
        v = PushVariable()
        v.__dict__.update(xmlParseAttributes(myElement))
        return v

    def __eq__(self, other):
        return isinstance(other, self.__class__) and tuple(
            getattr(self, field) for field in self.stateFields) == tuple(
                getattr(other, field) for field in self.stateFields)

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(tuple(getattr(self, field) for field in self.stateFields))

    def evaluate(self, variables=dict(), useFloat=False):
        if self.definition:
            try:
                self.value = self.expression.evaluate(self.definition,
                                                      variables,
                                                      useFloat=useFloat)
                self.valueValid = True
            except Exception as e:
                logging.getLogger(__name__).warning(str(e))
                self.valueValid = False
        if self.strMinimum:
            try:
                self.minimum = self.expression.evaluate(self.strMinimum,
                                                        variables,
                                                        useFloat=useFloat)
                self.minValid = True
            except Exception as e:
                logging.getLogger(__name__).warning(str(e))
                self.minValid = False
        if self.strMaximum:
            try:
                self.maximum = self.expression.evaluate(self.strMaximum,
                                                        variables,
                                                        useFloat=useFloat)
                self.maxValid = True
            except Exception as e:
                logging.getLogger(__name__).warning(str(e))
                self.maxValid = False

    def pushRecord(self, variables=None):
        if variables is not None:
            self.evaluate(variables)
        if (self.push and self.destinationName is not None
                and self.destinationName != 'None'
                and self.variableName is not None
                and self.variableName != 'None' and self.value is not None):
            if ((not self.minimum or self.value >= self.minimum)
                    and (not self.maximum or self.value <= self.maximum)):
                return [(self.destinationName, self.variableName, self.value)
                        ], []
            else:
                logging.getLogger(__name__).warning(
                    "Result out of range, Not pushing {0} to {1}: {2} <= {3} <= {4}"
                    .format(self.variableName, self.destinationName,
                            self.minimum, self.value, self.maximum))
                return [], [(self.destinationName, self.variableName)]
        else:
            if (self.push):
                logging.getLogger(__name__).warning(
                    "Not pushing {0} to {1}: {2} <= {3} <= {4}, push not fully specified"
                    .format(self.variableName, self.destinationName,
                            self.minimum, self.value, self.maximum))
        return [], []

    @property
    def key(self):
        return (self.destinationName, self.variableName)

    @property
    def hasStrMinimum(self):
        return (
            1 if self.minValid else -1) if self.strMinimum is not None else 0

    @property
    def hasStrMaximum(self):
        return (
            1 if self.maxValid else -1) if self.strMaximum is not None else 0

    @property
    def valueStatus(self):
        return 1 if self.valueValid else -1
class ExternalParameterControlModel(CategoryTreeModel):
    valueChanged = QtCore.pyqtSignal(str, object)
    expression = Expression()

    def __init__(self, controlUi, parameterList=[], parent=None):
        super(ExternalParameterControlModel,
              self).__init__(parameterList, parent, nodeNameAttr='displayName')
        self.parameterList = parameterList
        self.controlUi = controlUi
        for parameter in parameterList:
            parameter.categories = parameter.device.name
        self.headerLookup.update({
            (QtCore.Qt.Horizontal, QtCore.Qt.DisplayRole, 0):
            'Name',
            (QtCore.Qt.Horizontal, QtCore.Qt.DisplayRole, 1):
            'Control',
            (QtCore.Qt.Horizontal, QtCore.Qt.DisplayRole, 2):
            'External'
        })
        self.dataLookup.update({
            (QtCore.Qt.DisplayRole, 0):
            lambda node: node.content.displayName,
            (QtCore.Qt.DisplayRole, 1):
            lambda node: str(node.content.targetValue),
            (QtCore.Qt.EditRole, 1):
            lambda node: firstNotNone(node.content.string,
                                      str(node.content.targetValue)),
            (QtCore.Qt.UserRole, 1):
            lambda node: node.content.dimension,
            (QtCore.Qt.DisplayRole, 2):
            lambda node: str(node.content.value),
            (QtCore.Qt.BackgroundRole, 1):
            self.dependencyBgFunction,
            (QtCore.Qt.ToolTipRole, 1):
            self.toolTipFunction
        })
        self.setDataLookup.update({
            (QtCore.Qt.EditRole, 1):
            lambda index, value: self.setValue(index, value),
            (QtCore.Qt.UserRole, 1):
            lambda index, value: self.setStrValue(index, value),
        })
        self.flagsLookup = {
            1:
            QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
            | QtCore.Qt.ItemIsSelectable
        }
        self.numColumns = 3
        self.allowReordering = True
        self.signalConnections = list()

    def setParameterList(self, outputChannelDict):
        self.beginResetModel()
        self.parameterList = list(outputChannelDict.values())
        # first disconnect all old signals, they may point to the wrong index
        for inst, valueSlot, targetValueSlot in self.signalConnections:
            inst.valueChanged.disconnect(valueSlot)
            inst.targetChanged.disconnect(targetValueSlot)
        del self.signalConnections[:]  # clear the list
        # now we can add new connections
        for listIndex, inst in enumerate(self.parameterList):
            inst.multipleChannels = len(inst.device._outputChannels) > 1
            inst.categories = inst.device.name if inst.multipleChannels else None
            inst.displayName = inst.channelName if inst.multipleChannels else inst.device.name
            inst.toolTip = None
            valueSlot = functools.partial(self.showValue, listIndex)
            targetValueSlot = functools.partial(self.onTargetChanged,
                                                listIndex)
            inst.valueChanged.connect(valueSlot)
            inst.targetChanged.connect(targetValueSlot)
            self.signalConnections.append((inst, valueSlot, targetValueSlot))
        self.clear()
        self.endResetModel()
        self.addNodeList(self.parameterList)

    def showValue(self, listIndex, value, tooltip=None):
        inst = self.parameterList[listIndex]
        inst.toolTip = tooltip
        id = inst.name if inst.multipleChannels else inst.device.name
        node = self.nodeDict[id]
        modelIndex = self.indexFromNode(node, 2)
        self.dataChanged.emit(modelIndex, modelIndex)

    def onTargetChanged(self, listIndex, value):
        inst = self.parameterList[listIndex]
        id = inst.name if inst.multipleChannels else inst.device.name
        node = self.nodeDict[id]
        modelIndex = self.indexFromNode(node, 1)
        self.dataChanged.emit(modelIndex, modelIndex)

    def setValue(self, index, value):
        node = self.nodeFromIndex(index)
        self._setValue(node.content, value)

    def _setValue(self, inst, value):
        logger = logging.getLogger(__name__)
        logger.debug("setValue {0}".format(value))
        inst.targetValue = value
        self.setValueFollowup(inst)
        return True

    def setStrValue(self, index, strValue):
        node = self.nodeFromIndex(index)
        node.content.string = strValue
        return True

    def setValueFollowup(self, inst):
        try:
            logger = logging.getLogger(__name__)
            logger.debug("setValueFollowup {0}".format(inst.value))
            if not inst.setValue(inst.targetValue):
                delay = int(inst.settings.delay.m_as('ms'))
                QtCore.QTimer.singleShot(
                    delay, functools.partial(self.setValueFollowup, inst))
        except Exception as e:
            logger.exception(e)
            logger.warning(
                "Exception during setValueFollowup, number of adjusting devices likely to be faulty"
            )

    def update(self, iterable):
        for destination, name, value in iterable:
            if destination == 'External':
                for inst in self.parameterList:
                    if inst.name == name:
                        break
                inst.savedValue = value  # set saved value to make this new value the default
                node = self.nodeFromContent(inst)
                self.setValue(self.indexFromNode(node, 1), value)
                inst.strValue = None
                logging.info(
                    "Pushed to external parameter {0} value {1}".format(
                        name, value))

    def evaluate(self, name):
        for inst in self.parameterList:
            if inst.hasDependency:
                value = self.expression.evaluateAsMagnitude(
                    inst.string, self.controlUi.globalDict)
                self._setValue(inst, value)
                inst.savedValue = value  # set saved value to make this new value the default
                node = self.nodeFromContent(inst)
                index = self.indexFromNode(node, 1)
                self.dataChanged.emit(index, index)
Beispiel #16
0
class FitFunctionBase(object, metaclass=FitFunctionMeta):
    expression = Expression()
    name = 'None'
    parameterNames = list()

    def __init__(self):
        numParameters = len(self.parameterNames)
        self.epsfcn = 0.0
        self.parameters = [0] * numParameters
        self.startParameters = [1] * numParameters
        self.startParameterExpressions = None  # will be initialized by FitUiTableModel if values are available
        self.parameterEnabled = [True] * numParameters
        self.parametersConfidence = [None] * numParameters
        self.units = None
        self.results = SequenceDict({'RMSres': ResultRecord(name='RMSres')})
        self.useSmartStartValues = False
        self.hasSmartStart = not hasattr(self.smartStartValues, 'isNative')
        self.parametersUpdated = Observable()
        self.parameterBounds = [[None, None] for _ in range(numParameters)]
        self.parameterBoundsExpressions = None
        self.useErrorBars = True

    def __setstate__(self, state):
        state.pop('parameterNames', None)
        state.pop('cov_x', None)
        state.pop('infodict', None)
        state.pop('laguerreCacheEta', None)
        state.pop('laguerreTable', None)
        state.pop('pnCacheBeta', None)
        state.pop('pnTable', None)
        self.__dict__ = state
        self.__dict__.setdefault('useSmartStartValues', False)
        self.__dict__.setdefault('startParameterExpressions', None)
        self.__dict__.setdefault('parameterBounds',
                                 [[None, None]
                                  for _ in range(len(self.parameterNames))])
        self.__dict__.setdefault('parameterBoundsExpressions', None)
        self.__dict__.setdefault('useErrorBars', True)
        self.hasSmartStart = not hasattr(self.smartStartValues, 'isNative')

    def allFitParameters(self, p):
        """return a list where the disabled parameters are added to the enabled parameters given in p"""
        pindex = 0
        params = list()
        for index, enabled in enumerate(self.parameterEnabled):
            if enabled:
                params.append(p[pindex])
                pindex += 1
            else:
                params.append(float(self.startParameters[index]))
        return params

    @staticmethod
    def coercedValue(val, bounds):
        if bounds[1] is not None and val >= bounds[1]:
            val = float(0.95 * bounds[1] + 0.05 *
                        bounds[0] if bounds[0] is not None else bounds[1] -
                        0.01)
        if bounds[0] is not None and val <= bounds[0]:
            val = float(0.95 * bounds[0] + 0.05 *
                        bounds[1] if bounds[1] is not None else bounds[0] +
                        0.01)
        return val

    def enabledStartParameters(self, parameters=None, bounded=False):
        """return a list of only the enabled start parameters"""
        if parameters is None:
            parameters = self.startParameters
        params = list()
        if bounded:
            for enabled, param, bounds in zip(self.parameterEnabled,
                                              parameters,
                                              self.parameterBounds):
                if enabled:
                    params.append(self.coercedValue(float(param), bounds))
        else:
            for enabled, param in zip(self.parameterEnabled, parameters):
                if enabled:
                    params.append(float(param))
        return params

    def enabledFitParameters(self, parameters=None):
        """return a list of only the enabled fit parameters"""
        if parameters is None:
            parameters = self.parameters
        params = list()
        for enabled, param in zip(self.parameterEnabled, parameters):
            if enabled:
                params.append(float(param))
        return params

    def enabledParameterNames(self):
        """return a list of only the enabled fit parameters"""
        params = list()
        for enabled, param in zip(self.parameterEnabled, self.parameterNames):
            if enabled:
                params.append(param)
        return params

    def setEnabledFitParameters(self, parameters):
        """set the fitted parameters if enabled"""
        pindex = 0
        for index, enabled in enumerate(self.parameterEnabled):
            if enabled:
                self.parameters[index] = parameters[pindex]
                pindex += 1
            else:
                self.parameters[index] = float(self.startParameters[index])

    def setEnabledConfidenceParameters(self, confidence):
        """set the parameter confidence values for the enabled parameters"""
        pindex = 0
        for index, enabled in enumerate(self.parameterEnabled):
            if enabled:
                self.parametersConfidence[index] = confidence[pindex]
                pindex += 1
            else:
                self.parametersConfidence[index] = None

    @native
    def smartStartValues(self, x, y, parameters, enabled):
        return None

    def enabledSmartStartValues(self, x, y, parameters):
        smartParameters = self.smartStartValues(x, y, parameters,
                                                self.parameterEnabled)
        return [
            smartparam if enabled else param for enabled, param, smartparam in
            zip(self.parameterEnabled, parameters, smartParameters)
        ] if smartParameters is not None else None

    def evaluate(self, globalDict):
        myReplacementDict = self.replacementDict()
        if globalDict is not None:
            myReplacementDict.update(globalDict)
        if self.startParameterExpressions is not None:
            self.startParameters = [
                param if expr is None else self.expression.evaluateAsMagnitude(
                    expr, myReplacementDict) for param, expr in zip(
                        self.startParameters, self.startParameterExpressions)
            ]
        if self.parameterBoundsExpressions is not None:
            self.parameterBounds = [[
                bound[0]
                if expr[0] is None else self.expression.evaluateAsMagnitude(
                    expr[0], myReplacementDict),
                bound[1] if expr[1] is None else
                self.expression.evaluateAsMagnitude(expr[0], myReplacementDict)
            ] for bound, expr in zip(self.parameterBounds,
                                     self.parameterBoundsExpressions)]

    def enabledBounds(self):
        result = [[
            float(bounds[0]) if bounds[0] is not None else None,
            float(bounds[1]) if bounds[1] is not None else None
        ] for enabled, bounds in zip(self.parameterEnabled,
                                     self.parameterBounds) if enabled]
        enabled = any((any(bounds) for bounds in result))
        return result if enabled else None

    def leastsq(self, x, y, parameters=None, sigma=None):
        logger = logging.getLogger(__name__)
        # Ensure all values of sigma or non zero by replacing with the minimum nonzero value
        if sigma is not None and self.useErrorBars:
            nonzerosigma = sigma[sigma > 0]
            sigma[sigma == 0] = numpy.min(
                nonzerosigma) if len(nonzerosigma) > 0 else 1.0
        else:
            sigma = None
        if parameters is None:
            parameters = [float(param) for param in self.startParameters]
        if self.useSmartStartValues:
            smartParameters = self.smartStartValues(x, y, parameters,
                                                    self.parameterEnabled)
            if smartParameters is not None:
                parameters = [
                    smartparam if enabled else param
                    for enabled, param, smartparam in zip(
                        self.parameterEnabled, parameters, smartParameters)
                ]

        myEnabledBounds = self.enabledBounds()
        if myEnabledBounds:
            enabledOnlyParameters, cov_x, infodict, self.mesg, self.ier = leastsqbound(
                self.residuals,
                self.enabledStartParameters(parameters, bounded=True),
                args=(y, x, sigma),
                epsfcn=self.epsfcn,
                full_output=True,
                bounds=myEnabledBounds)
        else:
            enabledOnlyParameters, cov_x, infodict, self.mesg, self.ier = leastsq(
                self.residuals,
                self.enabledStartParameters(parameters),
                args=(y, x, sigma),
                epsfcn=self.epsfcn,
                full_output=True)
        self.setEnabledFitParameters(enabledOnlyParameters)
        self.update(self.parameters)
        logger.info("chisq {0}".format(sum(infodict["fvec"] *
                                           infodict["fvec"])))

        # calculate final chi square
        self.chisq = sum(infodict["fvec"] * infodict["fvec"])

        self.dof = max(len(x) - len(parameters), 1)
        RMSres = Q(sqrt(self.chisq / self.dof))
        RMSres.significantDigits = 3
        self.results['RMSres'].value = RMSres
        # chisq, sqrt(chisq/dof) agrees with gnuplot
        logger.info("success {0} {1}".format(self.ier, self.mesg))
        logger.info("Converged with chi squared {0}".format(self.chisq))
        logger.info("degrees of freedom, dof {0}".format(self.dof))
        logger.info(
            "RMS of residuals (i.e. sqrt(chisq/dof)) {0}".format(RMSres))
        logger.info("Reduced chisq (i.e. variance of residuals) {0}".format(
            self.chisq / self.dof))

        # uncertainties are calculated as per gnuplot, "fixing" the result
        # for non unit values of the reduced chisq.
        # values at min match gnuplot
        enabledParameterNames = self.enabledParameterNames()
        if cov_x is not None:
            enabledOnlyParametersConfidence = numpy.sqrt(
                numpy.diagonal(cov_x)) * sqrt(self.chisq / self.dof)
            self.setEnabledConfidenceParameters(
                enabledOnlyParametersConfidence)
            logger.info("Fitted parameters at minimum, with 68% C.I.:")
            for i, pmin in enumerate(enabledOnlyParameters):
                logger.info(
                    "%2i %-10s %12f +/- %10f" %
                    (i, enabledParameterNames[i], pmin,
                     sqrt(max(cov_x[i, i], 0)) * sqrt(self.chisq / self.dof)))

            logger.info("Correlation matrix")
            # correlation matrix close to gnuplot
            messagelist = ["               "]
            for i in range(len(enabledOnlyParameters)):
                messagelist.append("%-10s" % (enabledParameterNames[i], ))
            logger.info(" ".join(messagelist))
            messagelist = []
            for i in range(len(enabledOnlyParameters)):
                messagelist.append("%10s" % enabledParameterNames[i])
                for j in range(i + 1):
                    messagelist.append(
                        "%10f" %
                        (cov_x[i, j] / sqrt(abs(cov_x[i, i] * cov_x[j, j])), ))
                logger.info(" ".join(messagelist))

                #-----------------------------------------------
        else:
            self.parametersConfidence = [None] * len(self.parametersConfidence)

        return self.parameters

    def __str__(self):
        return "; ".join([
            ", ".join([self.name, self.functionString] + [
                "{0}={1}".format(name, value)
                for name, value in zip(self.parameterNames, self.parameters)
            ])
        ])

    def setConstant(self, name, value):
        setattr(self, name, value)

    def update(self, parameters=None):
        self.parametersUpdated.fire(values=self.replacementDict())

    def toXmlElement(self, parent):
        myroot = ElementTree.SubElement(parent, 'FitFunction', {
            'name': self.name,
            'functionString': self.functionString
        })
        for name, value, confidence, enabled, startExpression, bounds, boundsexpression in zip_longest(
                self.parameterNames, self.parameters,
                self.parametersConfidence, self.parameterEnabled,
                self.startParameterExpressions, self.parameterBounds,
                self.parameterBoundsExpressions):
            e = ElementTree.SubElement(
                myroot, 'Parameter', {
                    'name': name,
                    'confidence': repr(confidence),
                    'enabled': str(enabled),
                    'startExpression': str(startExpression),
                    'bounds': ",".join(map(str, bounds)),
                    'boundsExpression': ",".join(map(str, boundsexpression))
                })
            e.text = str(value)
        for result in list(self.results.values()):
            e = ElementTree.SubElement(myroot, 'Result', {
                'name': result.name,
                'definition': str(result.definition)
            })
            e.text = str(result.value)
        return myroot

    def toHdf5(self, group):
        fitfunction_group = group.require_group('fitfunction')
        fitfunction_group.attrs['name'] = self.name
        fitfunction_group.attrs['functionString'] = self.functionString
        parameter_group = fitfunction_group.require_group('parameters')
        for index, (name, value, confidence, enabled, startExpression, bounds,
                    boundsexpression) in enumerate(
                        zip_longest(self.parameterNames, self.parameters,
                                    self.parametersConfidence,
                                    self.parameterEnabled,
                                    self.startParameterExpressions,
                                    self.parameterBounds,
                                    self.parameterBoundsExpressions)):
            g = parameter_group.require_group(name)
            g.attrs['confidence'] = confidence
            g.attrs['enabled'] = enabled
            g.attrs['startExpression'] = str(startExpression)
            g.attrs['bounds'] = bounds
            g.attrs['boundsExpression'] = ",".join(map(str, boundsexpression))
            g.attrs['value'] = value
            g.attrs['index'] = index
        results_group = fitfunction_group.require_group('results')
        for result in list(self.results.values()):
            g = results_group.requie_group(result.name)
            g.attrs['definition'] = str(result.definition)
            g.attrs['value'] = repr(result.value)

    def residuals(self, p, y, x, sigma):
        p = self.allFitParameters(p)
        if sigma is not None:
            return (y - self.functionEval(x, *p)) / sigma
        else:
            return y - self.functionEval(x, *p)

    def value(self, x, p=None):
        p = self.parameters if p is None else p
        return self.functionEval(x, *p)

    def replacementDict(self):
        replacement = dict(list(zip(self.parameterNames, self.parameters)))
        replacement.update(
            dict(((v.name, v.value) for v in list(self.results.values()))))
        return replacement
Beispiel #17
0
class AWGWaveform(object):
    """waveform object for AWG channels. Responsible for parsing and evaluating waveforms.

    Attributes:
       settings (Settings): main settings
       channel (int): which channel this waveform belongs to
       dependencies (set): The variable names that this waveform depends on
       waveformCache (OrderedDict): cache of evaluated waveforms. The key in the waveform cache is a waveform, with all
       variables evaluated except 't' (e.g. 7*sin(3*t)+4). The value is a dict. The key to that dict is a range (min, max), and the
       value is a numpy array of samples, e.g. {(0, 2): numpay.array([1,2,31]), (12, 15): numpy.array([6,16,23,5])}
       """
    expression = Expression()

    def __init__(self, channel, settings, waveformCache):
        self.settings = settings
        self.channel = channel
        self.waveformCache = waveformCache
        self.dependencies = set()
        self.updateDependencies()

    @property
    def sampleRate(self):
        return self.settings.deviceProperties['sampleRate']

    @property
    def minSamples(self):
        return self.settings.deviceProperties['minSamples']

    @property
    def maxSamples(self):
        return self.settings.deviceProperties['maxSamples']

    @property
    def sampleChunkSize(self):
        return self.settings.deviceProperties['sampleChunkSize']

    @property
    def padValue(self):
        return self.calibrateInvIfNecessary(
            self.settings.deviceProperties['padValue'])

    @property
    def minAmplitude(self):
        return self.calibrateInvIfNecessary(
            self.settings.deviceProperties['minAmplitude'])

    @property
    def maxAmplitude(self):
        return self.calibrateInvIfNecessary(
            self.settings.deviceProperties['maxAmplitude'])

    @property
    def stepsize(self):
        return 1 / self.sampleRate

    @property
    def segmentDataRoot(self):
        return self.settings.channelSettingsList[
            self.channel]['segmentDataRoot']

    def calibrateInvIfNecessary(self, p):
        """Converts raw to volts if useCalibration is True"""
        if not self.settings.deviceSettings.get('useCalibration'):
            return p
        else:
            return self.settings.deviceProperties['calibrationInv'](p)

    def updateDependencies(self):
        """Determine the set of variables that the waveform depends on"""
        logger = logging.getLogger(__name__)
        self.dependencies = set()
        self.updateSegmentDependencies(self.segmentDataRoot.children)
        self.dependencies.discard('t')

    def updateSegmentDependencies(self, nodeList):
        for node in nodeList:
            if node.nodeType == nodeTypes.segment:
                node.stack = node.expression._parse_expression(node.equation)
                nodeDependencies = node.expression.findDependencies(node.stack)
                self.dependencies.update(nodeDependencies)
                if isIdentifier(node.duration):
                    self.dependencies.add(node.duration)
            elif node.nodeType == nodeTypes.segmentSet:
                if isIdentifier(node.repetitions):
                    self.dependencies.add(node.repetitions)
                self.updateSegmentDependencies(node.children)  #recursive

    def evaluate(self):
        """evaluate the waveform"""
        _, sampleList = self.evaluateSegments(self.segmentDataRoot.children)
        return self.compliantSampleList(sampleList)

    def evaluateSegments(self, nodeList, startStep=0):
        """Evaluate the list of nodes in nodeList.
        Args:
            nodeList: list of nodes to evaluate
            startStep: the step number at which evaluation starts
        Returns:
            startStep, sampleList: The step at which the next waveform begins, together with a list of samples
        """
        sampleList = numpy.array([])
        for node in nodeList:
            if node.enabled:
                if node.nodeType == nodeTypes.segment:
                    duration = self.settings.varDict[
                        node.duration]['value'] if isIdentifier(
                            node.duration
                        ) else self.expression.evaluateAsMagnitude(
                            node.duration)
                    startStep, newSamples = self.evaluateEquation(
                        node, duration, startStep)
                    sampleList = numpy.append(sampleList, newSamples)
                elif node.nodeType == nodeTypes.segmentSet:
                    repMag = self.settings.varDict[
                        node.repetitions]['value'] if isIdentifier(
                            node.repetitions
                        ) else self.expression.evaluateAsMagnitude(
                            node.repetitions)
                    repetitions = int(repMag.to_base_units().val
                                      )  #convert to float, then to integer
                    for n in range(repetitions):
                        startStep, newSamples = self.evaluateSegments(
                            node.children, startStep)  #recursive
                        sampleList = numpy.append(sampleList, newSamples)
        return startStep, sampleList

    def evaluateEquation(self, node, duration, startStep):
        """Evaluate the waveform of the specified node's equation.

        The waveform caching works as follows: if self.settings.cacheDepth is greater than zero, waveform values are saved
        to self.waveformCache as they are calculated, to speed up future waveform computations. self.waveformCache is an OrderedDict,
        and the length is constrained to self.settings.cacheDepth. A key to the OrderedDict is an equation string, i.e. "7*sin(5*t)+3".
        The value is a dict. A key to that dict is a sample range (start, stop), and the value is the values that equation takes over
        that range.

        When a waveform is requested, this function first looks to see if that waveform has a cache entry. If it does, it iterates
        through the dict associated with that waveform, looking for a (start, stop) range that overlaps with the requested range.
        If it finds an overlap, it computes only the non-overlapping part, and uses the cache for the remainder. It then updates
        the cache entry. If it does not find an overlap, it calculates the entire waveform, and then adds an entry to the waveform's
        dict of saved values.

        A cacheDepth value less than zero indicates an unbounded cache.

        Args:
            node: The node whose equation is to be evaluated
            duration: the length of time for which to evaluate the equation
            startStep: the step at which to start evaluation

        Returns:
            sampleList: list of values to program to the AWG.
        """
        #calculate number of samples
        numSamples = duration * self.sampleRate
        numSamples = int(round(numSamples))  #convert to float, then to integer
        numSamples = max(
            0, min(numSamples, self.maxSamples - startStep)
        )  #cap at maxSamples-startStep, so that the last sample does not exceed maxSamples
        stopStep = numSamples + startStep - 1
        nextSegmentStartStep = stopStep + 1
        # first test expression with dummy variable to see if units match up, so user is warned otherwise
        try:
            node.expression.variabledict = {
                varName: varValueTextDict['value']
                for varName, varValueTextDict in self.settings.varDict.items()
            }
            node.expression.variabledict.update({'t': Q(1, 'us')})
            node.expression.evaluateWithStack(
                node.stack[:])  # TODO: does not exist anymore
            error = False
        except ValueError:
            logging.getLogger(__name__).warning("Must be dimensionless!")
            error = True
            nextSegmentStartStep = startStep
            sampleList = numpy.array([])
        if not error:
            varValueDict = {
                varName: varValueTextDict['value'].to_base_units().val
                for varName, varValueTextDict in self.settings.varDict.items()
            }
            varValueDict['t'] = sympy.Symbol('t')
            sympyExpr = parse_expr(node.equation,
                                   varValueDict)  #parse the equation
            key = str(sympyExpr)

            if self.settings.cacheDepth != 0:  #meaning, use the cache
                if key in self.waveformCache:
                    self.waveformCache[key] = self.waveformCache.pop(
                        key)  #move key to the most recent position in cache
                    for (sampleStartStep, sampleStopStep
                         ), samples in self.waveformCache[key].items():
                        if startStep >= sampleStartStep and stopStep <= sampleStopStep:  #this means the required waveform is contained within the cached waveform
                            sliceStart = startStep - sampleStartStep
                            sliceStop = stopStep - sampleStartStep + 1
                            sampleList = samples[sliceStart:sliceStop]
                            break
                        elif max(startStep, sampleStartStep) > min(
                                stopStep, sampleStopStep
                        ):  #this means there is no overlap
                            continue
                        else:  #This means there is some overlap, but not an exact match
                            if startStep < sampleStartStep:  #compute the first part of the sampleList
                                sampleListStart = self.computeFunction(
                                    sympyExpr, varValueDict['t'], startStep,
                                    sampleStartStep - 1)
                                if stopStep <= sampleStopStep:  #use the cached part for the rest
                                    sliceStop = stopStep - sampleStartStep + 1
                                    sampleList = numpy.append(
                                        sampleListStart, samples[:sliceStop])
                                    self.waveformCache[key].pop(
                                        (sampleStartStep, sampleStopStep)
                                    )  #update cache entry with new samples
                                    self.waveformCache[key][(
                                        startStep,
                                        sampleStopStep)] = numpy.append(
                                            sampleListStart, samples)
                                else:  #compute the end of the sampleList, then use the cached part for the middle
                                    sampleListEnd = self.computeFunction(
                                        sympyExpr, varValueDict['t'],
                                        sampleStopStep + 1, stopStep)
                                    sampleList = numpy.append(
                                        numpy.append(sampleListStart, samples),
                                        sampleListEnd)
                                    self.waveformCache[key].pop(
                                        (sampleStartStep, sampleStopStep))
                                    self.waveformCache[key][(
                                        startStep, stopStep)] = sampleList
                            else:  #compute the end of the sampleList, and use the cached part for the beginning
                                sampleListEnd = self.computeFunction(
                                    sympyExpr, varValueDict['t'],
                                    sampleStopStep + 1, stopStep)
                                sliceStart = startStep - sampleStartStep
                                sampleList = numpy.append(
                                    samples[sliceStart:], sampleListEnd)
                                self.waveformCache[key].pop(
                                    (sampleStartStep, sampleStopStep))
                                self.waveformCache[key][(
                                    sampleStartStep, stopStep)] = numpy.append(
                                        samples, sampleListEnd)
                            break
                    else:  #This is an else on the for loop, it executes if there is no break (i.e. if there are no computed samples with overlap)
                        sampleList = self.computeFunction(
                            sympyExpr, varValueDict['t'], startStep, stopStep)
                        self.waveformCache[key][(startStep,
                                                 stopStep)] = sampleList
                else:  #if the waveform is not in the cache
                    sampleList = self.computeFunction(sympyExpr,
                                                      varValueDict['t'],
                                                      startStep, stopStep)
                    self.waveformCache[key] = {
                        (startStep, stopStep): sampleList
                    }
                    if self.settings.cacheDepth > 0 and len(
                            self.waveformCache) > self.settings.cacheDepth:
                        self.waveformCache.popitem(
                            last=False
                        )  #remove the least recently used cache item
            else:  #if we're not using the cache at all
                sampleList = self.computeFunction(sympyExpr, varValueDict['t'],
                                                  startStep, stopStep)
        return nextSegmentStartStep, sampleList

    def computeFunction(self, sympyExpr, tVar, startStep, stopStep):
        """Compute the value of a function over a specified range.
        Args:
            sympyExpr (str): A string containing a function of 't' (e.g. '7*sin(3*t)')
            tVar (Symbol): sympy symbol for 't'
            startStep (int): where to start computation
            stopStep (int): the last sample to compute
        Returns:
            sampleList (numpy.array): list of function values
        """
        numSamples = stopStep - startStep + 1
        if numSamples <= 0:
            sampleList = numpy.array([])
        else:
            func = sympy.lambdify(tVar, sympyExpr,
                                  "numpy")  #turn string into a python function
            clippedFunc = lambda t: max(self.minAmplitude,
                                        min(self.maxAmplitude, func(t))
                                        )  #clip at min and max amplitude
            vectorFunc = numpy.vectorize(clippedFunc,
                                         otypes=[numpy.float64
                                                 ])  #vectorize the function
            step = self.stepsize.m_as('s')
            sampleList = vectorFunc(
                (numpy.arange(numSamples) + startStep) *
                step)  #apply the function to each time step value
        return sampleList

    def compliantSampleList(self, sampleList):
        """Make the sample list compliant with the capabilities of the AWG
        Args:
            sampleList (numpy array): calculated list of samples, might be impossible for AWG to output
        Returns:
            sampleList (numpy array): output list of samples, within AWG capabilities
            """
        numSamples = len(sampleList)
        if numSamples > self.maxSamples:
            sampleList = sampleList[:self.maxSamples]
        if numSamples < self.minSamples:
            extraNumSamples = self.minSamples - numSamples  #make sure there are at least minSamples
            if self.minSamples % self.sampleChunkSize != 0:  #This should always be False if minSamples and sampleChunkSize are internally consistent
                extraNumSamples += self.sampleChunkSize - (
                    self.minSamples % self.sampleChunkSize)
        elif numSamples % self.sampleChunkSize != 0:
            extraNumSamples = self.sampleChunkSize - (numSamples %
                                                      self.sampleChunkSize)
        else:
            extraNumSamples = 0
        sampleList = numpy.append(sampleList,
                                  [self.padValue] * extraNumSamples)
        return sampleList
class ExpressionValue(QtCore.QObject):
    expression = Expression()
    valueChanged = QtCore.pyqtSignal(object, object, object, object)

    def __init__(self, name=None, globalDict=None, value=Q(0)):
        super(ExpressionValue, self).__init__()
        self._globalDict = globalDict
        self.name = name
        self._string = None
        self._value = value
        self._updateFloatValue()
        self.registrations = list()        # subscriptions to global variable values

    def _updateFloatValue(self):
        try:
           self.floatValue = float(self._value)  # cached value as float
        except (DimensionalityError, TypeError):
            self.floatValue = None

    def __getstate__(self):
        return self.name, self._string, self._value
    
    def __setstate__(self, state):
        self.__init__( state[0] )
        self._string = state[1]
        self._value = state[2]
        self._updateFloatValue()

    def __eq__(self, other):
        return other is not None and isinstance(other, ExpressionValue) and (self.name, self._string, self._value) == (other.name, other._string, other._value)

    def __ne__(self, other):
        return not self.__eq__(other)
        
    @property
    def globalDict(self):
        return self._globalDict
    
    @globalDict.setter
    def globalDict(self, d):
        self._globalDict = d
        self.string = self._string 
        
    @property
    def value(self):
        return self._value
    
    @value.setter
    def value(self, v):
        if isinstance(v, ExpressionValue):
            self._value = v._value
            self.string = v._string
            #raise ExpressionValueException('cannot assign ExpressionValue value to ExpressionValue')
            #logging.getLogger(__name__).error('cannot assign ExpressionValue value to ExpressionValue')
            #v = mg(0)
        else:
            self._value = v
        self._updateFloatValue()
        self.valueChanged.emit(self.name, self._value, self._string, 'value')
        
    @property 
    def string(self):
        return self._string if self._string is not None else str(self._value)
    
    @string.setter
    def string(self, s):
        if self._globalDict is None:
            raise ExpressionValueException("Global dictionary is not set in {0}".format(self.name))
        self._string = s
        for name, reference in self.registrations:
            self._globalDict.valueChanged(name).disconnect(reference)
        self.registrations[:] = []
        if self._string:
            self._value, dependencies = self.expression.evaluateAsMagnitude(self._string, self._globalDict, listDependencies=True)
            self._updateFloatValue()
            for dep in dependencies:
                reference = WeakMethod.ref(self.recalculate)
                self._globalDict.valueChanged(dep).connect(reference)
                self.registrations.append((dep, reference))
                       
    @property
    def hasDependency(self):
        #return self._string is not None
        return len(self.registrations)>0
    
    @property
    def data(self):
        return (self.name, self._value, self._string )
    
    @data.setter
    def data(self, val):
        self.name, self.value, self.string = val
    
    def recalculate(self, name=None, value=None, origin=None):
        if self._globalDict is None:
            raise ExpressionValueException("Global dictionary is not set in {0}".format(self.name))
        if self._string:
            newValue = self.expression.evaluateAsMagnitude(self._string, self._globalDict)
        if newValue != self._value:
            self._value = newValue
            self._updateFloatValue()
            self.valueChanged.emit(self.name, self._value, self._string, 'recalculate')

    def __hash__(self):
        return hash(self._value)

    def __str__(self):
        return str(self._value)

    def __deepcopy__(self, memo):
        result = ExpressionValue(globalDict=self.globalDict)
        memo[id(self)] = result
        result.name = deepcopy(self.name)
        result._string = deepcopy(self._string)
        result._value = deepcopy(self._value)
        return result
Beispiel #19
0
class AWGTableModel(QtCore.QAbstractTableModel):
    """Table model for displaying AWG variables"""
    headerDataLookup = ["Variable", "Value"]
    valueChanged = QtCore.pyqtSignal( object, object )
    expression = Expression()

    def __init__(self, settings, globalDict, parent=None, *args):
        QtCore.QAbstractTableModel.__init__(self, parent, *args)
        self.settings = settings
        self.globalDict = globalDict
        self.defaultBG = QtGui.QColor(QtCore.Qt.white)
        self.textBG = QtGui.QColor(QtCore.Qt.green).lighter(175)
        self.column = enum('variable', 'value')
        self.defaultFontName = "Segoe UI"
        self.defaultFontSize = 9
        self.normalFont = QtGui.QFont(self.defaultFontName, self.defaultFontSize, QtGui.QFont.Normal)
        self.boldFont = QtGui.QFont(self.defaultFontName, self.defaultFontSize, QtGui.QFont.Bold)

        self.dataLookup = {
            (QtCore.Qt.DisplayRole, self.column.variable): lambda row: self.settings.varDict.keyAt(row),
            (QtCore.Qt.DisplayRole, self.column.value): lambda row: str(self.settings.varDict.at(row)['value']),
            (QtCore.Qt.FontRole, self.column.variable): lambda row: self.boldFont if self.settings.varDict.keyAt(row).startswith('Duration') else self.normalFont,
            (QtCore.Qt.FontRole, self.column.value): lambda row: self.boldFont if self.settings.varDict.keyAt(row).startswith('Duration') else self.normalFont,
            (QtCore.Qt.EditRole, self.column.value): lambda row: firstNotNone( self.settings.varDict.at(row)['text'], str(self.settings.varDict.at(row)['value'])),
            (QtCore.Qt.BackgroundColorRole, self.column.value): lambda row: self.defaultBG if self.settings.varDict.at(row)['text'] is None else self.textBG,
            (QtCore.Qt.ToolTipRole, self.column.value): lambda row: self.settings.varDict.at(row)['text'] if self.settings.varDict.at(row)['text'] else None
        }
        self.setDataLookup =  { 
            (QtCore.Qt.EditRole, self.column.value): self.setValue,
            (QtCore.Qt.UserRole, self.column.value): self.setText
        }
        
    def setValue(self, index, value):
        row = index.row()
        name = self.settings.varDict.keyAt(row)
        var = self.settings.varDict.at(row)
        var['value'] = value
        self.valueChanged.emit(name, value)
        return True
    
    def setText(self, index, value):
        row = index.row()
        self.settings.varDict.at(row)['text'] = value
        return True
    
    def rowCount(self, parent=QtCore.QModelIndex()): 
        return len(self.settings.varDict)
        
    def columnCount(self, parent=QtCore.QModelIndex()): 
        return 2
    
    def data(self, index, role): 
        if index.isValid():
            return self.dataLookup.get((role, index.column()), lambda row: None)(index.row())
        return None

    def setData(self, index, value, role):
        return self.setDataLookup.get((role, index.column()), lambda index, value: False )(index, value)
        
    def flags(self, index):
        return QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled if index.column()==self.column.variable else QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable

    def headerData(self, section, orientation, role):
        if (role == QtCore.Qt.DisplayRole):
            if (orientation == QtCore.Qt.Horizontal): 
                return self.headerDataLookup[section]
            elif (orientation == QtCore.Qt.Vertical):
                return str(section)
        return None  # QtCore.QVariant()
    
    def evaluate(self, name):
        for (varName, varValueTextDict) in list(self.settings.varDict.items()):
            expr = varValueTextDict['text']
            if expr is not None:
                value = self.expression.evaluateAsMagnitude(expr, self.globalDict)
                self.settings.varDict[varName]['value'] = value   # set saved value to make this new value the default
                modelIndex = self.createIndex(self.settings.varDict.index(varName), self.column.value)
                self.dataChanged.emit(modelIndex, modelIndex)
                self.valueChanged.emit(varName, value)
Beispiel #20
0
class AWGDeviceBase(object):
    """base class for AWG Devices"""
    expression = Expression()

    def __init__(self, settings):
        self.settings = settings
        self.settings.deviceSettings.setdefault('programOnScanStart', False)
        self.settings.deviceSettings.setdefault('useCalibration', False)
        self.waveforms = []
        for channel in range(self.deviceProperties['numChannels']):
            self.waveforms.append(None)
            if channel >= len(self.settings.channelSettingsList
                              ):  #create new channels if it's necessary
                self.settings.channelSettingsList.append({
                    'segmentDataRoot':
                    AWGSegmentNode(None, ''),
                    'segmentTreeState':
                    None,
                    'plotEnabled':
                    True,
                    'plotStyle':
                    self.settings.plotStyles.lines
                })
        self.project = getProject()
        awgConfigDict = list(
            self.project.hardware[self.displayName].values())[0]
        sampleRateText = awgConfigDict['sampleRate']
        sampleRate = self.expression.evaluateAsMagnitude(sampleRateText)
        self.deviceProperties['sampleRate'] = sampleRate
        sample = 1 / self.deviceProperties['sampleRate']
        #new_mag('sample', sample)  # TODO: check whether the added samples = count / second in modules/quantity_units.txt
        #new_mag('samples', sample)  # replaces this
        if not self.project.isEnabled('hardware', self.displayName):
            self.enabled = False
        else:
            self.open()

    def parameters(self):
        """return the parameter definitions used by the parameterTable to show the gui"""
        return SequenceDict([
            ('Use Calibration',
             Parameter(name='Use Calibration',
                       dataType='bool',
                       value=self.settings.deviceSettings['useCalibration'],
                       key='useCalibration')),
            ('Program on scan start',
             Parameter(
                 name='Program on scan start',
                 dataType='bool',
                 value=self.settings.deviceSettings['programOnScanStart'],
                 key='programOnScanStart')),
            ('Program now',
             Parameter(name='Program now', dataType='action',
                       value='program')),
            ('Trigger now',
             Parameter(name='Trigger now', dataType='action', value='trigger'))
        ])

    def update(self, parameter):
        """update the parameter, called by the parameterTable"""
        if parameter.dataType != 'action':
            self.settings.deviceSettings[parameter.key] = parameter.value
            self.settings.saveIfNecessary()
            if parameter.key == 'useCalibration':
                self.settings.replot()
        else:
            getattr(self, parameter.value)()

    #functions and attributes that must be defined by inheritors
    def open(self):
        raise NotImplementedError(
            "'open' method must be implemented by specific AWG device class")

    def program(self):
        raise NotImplementedError(
            "'program' method must be implemented by specific AWG device class"
        )

    def trigger(self):
        raise NotImplementedError(
            "'trigger' method must be implemented by specific AWG device class"
        )

    def close(self):
        raise NotImplementedError(
            "'close' method must be implemented by specific AWG device class")

    @property
    def deviceProperties(self):
        raise NotImplementedError(
            "'deviceProperties' must be set by specific AWG device class")

    @property
    def displayName(self):
        raise NotImplementedError(
            "'displayName' must be set by specific AWG device class")