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
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)
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
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
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
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
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)
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
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)
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
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
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)
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")