def __init__(self, parent = None): QtGui.QWidget.__init__(self, parent) if parent == None: self.parent = self self.parent.lastUsedDirectory = os.path.expanduser('~') else: self.parent = parent self.ui = Ui_Form() self.ui.setupUi(self) self.setWindowTitle("RadTrack FEL Calculator") # Renames self.ui.charge.setObjectName("Charge") self.ui.charge.unit = 'C' self.ui.charge.dictName = 'charge' self.ui.slicemit.setObjectName("Normalized slice emittance") self.ui.slicemit.unit = 'm*rad' self.ui.slicemit.dictName = 'slicemit' self.ui.ebeamenergy.setObjectName("Beam energy") self.ui.ebeamenergy.unit = 'eV' self.ui.ebeamenergy.dictName = 'ebeamenergy' self.ui.energyspread.setObjectName("Sliced energy spread") self.ui.energyspread.unit = '' self.ui.energyspread.dictName = 'energyspread' self.ui.bunchlen.setObjectName("Bunch length (time)") self.ui.bunchlen.unit = 's' self.ui.bunchlen.dictName = 'bunchLengthFWHM_sec' self.ui.reprate.setObjectName("Repetition rate") self.ui.reprate.unit = 'Hz' self.ui.reprate.dictName = 'reprate' self.ui.uperiod.setObjectName("Undulator period") self.ui.uperiod.unit = 'm' self.ui.uperiod.dictName = 'undulatorPeriod' self.ui.ufield.setObjectName("Undulator field") self.ui.ufield.unit = 'T' self.ui.ufield.dictName = 'ufield' self.ui.beta.setObjectName("Average beta function") self.ui.beta.unit = 'm' self.ui.beta.dictName = 'beta' self.ui.bunlen.setObjectName("Bunch length (distance)") self.ui.bunlen.unit = 'm' self.ui.bunlen.dictName = 'bunchLengthFWHM_m' self.ui.gamma.setObjectName("Relativistic gamma") self.ui.gamma.unit = '' self.ui.gamma.dictName = 'gamma' self.ui.edensity.setObjectName("Peak electron density") self.ui.edensity.unit = 'm^-3' self.ui.edensity.dictName = 'peakElectronDensity' self.ui.geoemit.setObjectName("Geometric emittance") self.ui.geoemit.unit = 'm*rad' self.ui.geoemit.dictName = 'geometricEmittance' self.ui.peakamp.setObjectName("Peak current") self.ui.peakamp.unit = 'A' self.ui.peakamp.dictName = 'peakamp' self.ui.rmssize.setObjectName("RMS beam size (average)") self.ui.rmssize.unit = 'm' self.ui.rmssize.dictName = 'rmsBeamSize' self.ui.uparam.setObjectName("Undulator parameter") self.ui.uparam.unit = '' self.ui.uparam.dictName = 'undulatorParameter' self.ui.uwave.setObjectName("Undulator wavenumber") self.ui.uwave.unit = 'm^-1' self.ui.uwave.dictName = 'undulatorWaveNumber' self.ui.averagepower.setObjectName("Average power") self.ui.averagepower.unit = 'W' self.ui.averagepower.dictName = 'averagePower' self.ui.radiatedwavelength.setObjectName("Radiated wavelength") self.ui.radiatedwavelength.unit = 'm' self.ui.radiatedwavelength.dictName = 'radiatedwavelength' self.ui.saturation.setObjectName("Saturation length") self.ui.saturation.unit = 'm' self.ui.saturation.dictName = 'saturationLength' self.ui.raleigh.setObjectName("Rayleigh range") self.ui.raleigh.unit = 'm' self.ui.raleigh.dictName = 'RayleighRange' self.ui.photonemit.setObjectName("Photon emittance") self.ui.photonemit.unit = 'm*rad' self.ui.photonemit.dictName = 'photonEmittance' self.ui.fel_1d.setObjectName("1D FEL parameter") self.ui.fel_1d.unit = '' self.ui.fel_1d.dictName = 'oneDFELParameter' self.ui.gain_1d.setObjectName("1D gain length") self.ui.gain_1d.unit = '' self.ui.gain_1d.dictName = 'oneDGainLength' self.ui.dfactor.setObjectName("Diffraction factor") self.ui.dfactor.unit = '' self.ui.dfactor.dictName = 'diffractionFactor' self.ui.efactor.setObjectName("Emittance factor") self.ui.efactor.unit = '' self.ui.efactor.dictName = 'emitanceFactor' self.ui.espreadfactor.setObjectName("Energy spread factor") self.ui.espreadfactor.unit = '' self.ui.espreadfactor.dictName = 'energySpreadFactor' self.ui.threedfel.setObjectName("3D FEL parameter") self.ui.threedfel.unit = '' self.ui.threedfel.dictName = 'threeDFELParameter' self.ui.gain_3d.setObjectName("3D gain length") self.ui.gain_3d.unit = 'm' self.ui.gain_3d.dictName = 'threeDGainLength' self.ui.total.setObjectName("3D effect total") self.ui.total.unit = '' self.ui.total.dictName = 'threeDEffectTotal' self.ui.sasepower.setObjectName("SASE power at saturation") self.ui.sasepower.unit = 'W' self.ui.sasepower.dictName = 'SASEpowerAtSaturation' self.ui.saseenergy.setObjectName("SASE pulsed energy") self.ui.saseenergy.unit = 'J' self.ui.saseenergy.dictName = 'pulsedSASEenergy' self.ui.x.setObjectName("X Axis") self.ui.y.setObjectName("Y Axis") self.ui.z.setObjectName("Z Axis") self.ui.xmin.setObjectName("Min X Value") self.ui.xmax.setObjectName("Max X Value") self.ui.ymin.setObjectName("Min Y Value") self.ui.ymax.setObjectName("Max Y Value") self.textBox = dict() self.valueFromTextBox = dict() self.textBoxFromDictName = dict() self.userInputBoxes = [] maxLength = 0 for thing in [getattr(self.ui, name) for name in sorted(dir(self.ui))]: if not hasattr(thing, 'unit'): continue length = QtGui.QFontMetrics(self.ui.x.font()).boundingRect(thing.objectName()).width() if length > maxLength: maxLength = length self.textBox[thing.objectName()] = thing self.textBoxFromDictName[thing.dictName] = thing if hasattr(thing, 'isReadOnly'): if thing.isReadOnly(): if thing.objectName().startswith("Bunch length"): thing.setToolTip("FWHM") else: thing.setToolTip('') self.ui.z.addItem(thing.objectName()) self.ui.target.addItem(thing.objectName()) else: self.ui.x.addItem(thing.objectName()) self.ui.y.addItem(thing.objectName()) self.ui.vary.addItem(thing.objectName()) thing.textEdited.connect(self.calculateAll) thing.setToolTip(thing.unit) self.userInputBoxes.append(thing) scrollBarWidth = self.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent) extraSpace = 10 comboWidth = maxLength + scrollBarWidth + extraSpace comboHeight = 22 for thing in [self.ui.x, self.ui.y, self.ui.z]: self.textBox[thing.objectName()] = thing thing.setGeometry(thing.x(), thing.y(), comboWidth, comboHeight) thing.setCurrentIndex(-1) for thing in [self.ui.xmin, self.ui.xmax, self.ui.ymin, self.ui.ymax]: self.textBox[thing.objectName()] = thing # Connections self.ui.plotButton.clicked.connect(self.plot) self.ui.solve.clicked.connect(self.goalSeek) # Default/example values self.ui.charge.setText('300 pC') self.ui.slicemit.setText('1 mm*mrad') self.ui.ebeamenergy.setText('600 MeV') self.ui.energyspread.setText('.01%') self.ui.peakamp.setText('200 A') self.ui.reprate.setText('150 kHz') self.ui.radiatedwavelength.setText('13.5 nm') self.ui.ufield.setText('0.9 T') self.ui.beta.setText('1 m') self.calculateAll() for box in self.textBox.values(): try: box.setCursorPosition(0) except AttributeError: pass self.container = self
class RbFEL(QtGui.QWidget): acceptsFileTypes = ['fel'] defaultTitle = 'FEL Calculator' task = 'Analyze FEL parameters' category = 'tools' def __init__(self, parent = None): QtGui.QWidget.__init__(self, parent) if parent == None: self.parent = self self.parent.lastUsedDirectory = os.path.expanduser('~') else: self.parent = parent self.ui = Ui_Form() self.ui.setupUi(self) self.setWindowTitle("RadTrack FEL Calculator") # Renames self.ui.charge.setObjectName("Charge") self.ui.charge.unit = 'C' self.ui.charge.dictName = 'charge' self.ui.slicemit.setObjectName("Normalized slice emittance") self.ui.slicemit.unit = 'm*rad' self.ui.slicemit.dictName = 'slicemit' self.ui.ebeamenergy.setObjectName("Beam energy") self.ui.ebeamenergy.unit = 'eV' self.ui.ebeamenergy.dictName = 'ebeamenergy' self.ui.energyspread.setObjectName("Sliced energy spread") self.ui.energyspread.unit = '' self.ui.energyspread.dictName = 'energyspread' self.ui.bunchlen.setObjectName("Bunch length (time)") self.ui.bunchlen.unit = 's' self.ui.bunchlen.dictName = 'bunchLengthFWHM_sec' self.ui.reprate.setObjectName("Repetition rate") self.ui.reprate.unit = 'Hz' self.ui.reprate.dictName = 'reprate' self.ui.uperiod.setObjectName("Undulator period") self.ui.uperiod.unit = 'm' self.ui.uperiod.dictName = 'undulatorPeriod' self.ui.ufield.setObjectName("Undulator field") self.ui.ufield.unit = 'T' self.ui.ufield.dictName = 'ufield' self.ui.beta.setObjectName("Average beta function") self.ui.beta.unit = 'm' self.ui.beta.dictName = 'beta' self.ui.bunlen.setObjectName("Bunch length (distance)") self.ui.bunlen.unit = 'm' self.ui.bunlen.dictName = 'bunchLengthFWHM_m' self.ui.gamma.setObjectName("Relativistic gamma") self.ui.gamma.unit = '' self.ui.gamma.dictName = 'gamma' self.ui.edensity.setObjectName("Peak electron density") self.ui.edensity.unit = 'm^-3' self.ui.edensity.dictName = 'peakElectronDensity' self.ui.geoemit.setObjectName("Geometric emittance") self.ui.geoemit.unit = 'm*rad' self.ui.geoemit.dictName = 'geometricEmittance' self.ui.peakamp.setObjectName("Peak current") self.ui.peakamp.unit = 'A' self.ui.peakamp.dictName = 'peakamp' self.ui.rmssize.setObjectName("RMS beam size (average)") self.ui.rmssize.unit = 'm' self.ui.rmssize.dictName = 'rmsBeamSize' self.ui.uparam.setObjectName("Undulator parameter") self.ui.uparam.unit = '' self.ui.uparam.dictName = 'undulatorParameter' self.ui.uwave.setObjectName("Undulator wavenumber") self.ui.uwave.unit = 'm^-1' self.ui.uwave.dictName = 'undulatorWaveNumber' self.ui.averagepower.setObjectName("Average power") self.ui.averagepower.unit = 'W' self.ui.averagepower.dictName = 'averagePower' self.ui.radiatedwavelength.setObjectName("Radiated wavelength") self.ui.radiatedwavelength.unit = 'm' self.ui.radiatedwavelength.dictName = 'radiatedwavelength' self.ui.saturation.setObjectName("Saturation length") self.ui.saturation.unit = 'm' self.ui.saturation.dictName = 'saturationLength' self.ui.raleigh.setObjectName("Rayleigh range") self.ui.raleigh.unit = 'm' self.ui.raleigh.dictName = 'RayleighRange' self.ui.photonemit.setObjectName("Photon emittance") self.ui.photonemit.unit = 'm*rad' self.ui.photonemit.dictName = 'photonEmittance' self.ui.fel_1d.setObjectName("1D FEL parameter") self.ui.fel_1d.unit = '' self.ui.fel_1d.dictName = 'oneDFELParameter' self.ui.gain_1d.setObjectName("1D gain length") self.ui.gain_1d.unit = '' self.ui.gain_1d.dictName = 'oneDGainLength' self.ui.dfactor.setObjectName("Diffraction factor") self.ui.dfactor.unit = '' self.ui.dfactor.dictName = 'diffractionFactor' self.ui.efactor.setObjectName("Emittance factor") self.ui.efactor.unit = '' self.ui.efactor.dictName = 'emitanceFactor' self.ui.espreadfactor.setObjectName("Energy spread factor") self.ui.espreadfactor.unit = '' self.ui.espreadfactor.dictName = 'energySpreadFactor' self.ui.threedfel.setObjectName("3D FEL parameter") self.ui.threedfel.unit = '' self.ui.threedfel.dictName = 'threeDFELParameter' self.ui.gain_3d.setObjectName("3D gain length") self.ui.gain_3d.unit = 'm' self.ui.gain_3d.dictName = 'threeDGainLength' self.ui.total.setObjectName("3D effect total") self.ui.total.unit = '' self.ui.total.dictName = 'threeDEffectTotal' self.ui.sasepower.setObjectName("SASE power at saturation") self.ui.sasepower.unit = 'W' self.ui.sasepower.dictName = 'SASEpowerAtSaturation' self.ui.saseenergy.setObjectName("SASE pulsed energy") self.ui.saseenergy.unit = 'J' self.ui.saseenergy.dictName = 'pulsedSASEenergy' self.ui.x.setObjectName("X Axis") self.ui.y.setObjectName("Y Axis") self.ui.z.setObjectName("Z Axis") self.ui.xmin.setObjectName("Min X Value") self.ui.xmax.setObjectName("Max X Value") self.ui.ymin.setObjectName("Min Y Value") self.ui.ymax.setObjectName("Max Y Value") self.textBox = dict() self.valueFromTextBox = dict() self.textBoxFromDictName = dict() self.userInputBoxes = [] maxLength = 0 for thing in [getattr(self.ui, name) for name in sorted(dir(self.ui))]: if not hasattr(thing, 'unit'): continue length = QtGui.QFontMetrics(self.ui.x.font()).boundingRect(thing.objectName()).width() if length > maxLength: maxLength = length self.textBox[thing.objectName()] = thing self.textBoxFromDictName[thing.dictName] = thing if hasattr(thing, 'isReadOnly'): if thing.isReadOnly(): if thing.objectName().startswith("Bunch length"): thing.setToolTip("FWHM") else: thing.setToolTip('') self.ui.z.addItem(thing.objectName()) self.ui.target.addItem(thing.objectName()) else: self.ui.x.addItem(thing.objectName()) self.ui.y.addItem(thing.objectName()) self.ui.vary.addItem(thing.objectName()) thing.textEdited.connect(self.calculateAll) thing.setToolTip(thing.unit) self.userInputBoxes.append(thing) scrollBarWidth = self.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent) extraSpace = 10 comboWidth = maxLength + scrollBarWidth + extraSpace comboHeight = 22 for thing in [self.ui.x, self.ui.y, self.ui.z]: self.textBox[thing.objectName()] = thing thing.setGeometry(thing.x(), thing.y(), comboWidth, comboHeight) thing.setCurrentIndex(-1) for thing in [self.ui.xmin, self.ui.xmax, self.ui.ymin, self.ui.ymax]: self.textBox[thing.objectName()] = thing # Connections self.ui.plotButton.clicked.connect(self.plot) self.ui.solve.clicked.connect(self.goalSeek) # Default/example values self.ui.charge.setText('300 pC') self.ui.slicemit.setText('1 mm*mrad') self.ui.ebeamenergy.setText('600 MeV') self.ui.energyspread.setText('.01%') self.ui.peakamp.setText('200 A') self.ui.reprate.setText('150 kHz') self.ui.radiatedwavelength.setText('13.5 nm') self.ui.ufield.setText('0.9 T') self.ui.beta.setText('1 m') self.calculateAll() for box in self.textBox.values(): try: box.setCursorPosition(0) except AttributeError: pass self.container = self def calculateAll(self): self.updateBoxes(calculate(self.userInputDict())) def updateBoxes(self, boxDict): for name in boxDict: self.setResultBox(self.textBoxFromDictName[name], boxDict[name]) def userInputDict(self): userDict = dict() for box in self.userInputBoxes: userDict[box.dictName] = self.getValue(box) return userDict def unsetValue(self, textBox): textBox.clear() if textBox in self.valueFromTextBox: del self.valueFromTextBox[textBox] def getValue(self, textBox): try: self.valueFromTextBox[textBox] = util.convertUnitsStringToNumber(textBox.text(), textBox.unit) return self.valueFromTextBox[textBox] except ValueError: if textBox in self.valueFromTextBox: del self.valueFromTextBox[textBox] def setResultBox(self, textBox, value): if value is not None: self.valueFromTextBox[textBox] = value textBox.setText(util.displayWithUnitsNumber(util.roundSigFig(value, 5), textBox.unit)) textBox.setCursorPosition(0) else: self.unsetValue(textBox) def plot(self): try: xTextBox = self.textBox[self.ui.x.currentText()] xOrginalValue = xTextBox.text() yTextBox = self.textBox[self.ui.y.currentText()] yOrginalValue = yTextBox.text() zTextBox = self.textBox[self.ui.z.currentText()] except KeyError: return # Combo box choice was left blank try: xmin = util.convertUnitsStringToNumber(self.ui.xmin.text(), xTextBox.unit) xmax = util.convertUnitsStringToNumber(self.ui.xmax.text(), xTextBox.unit) if xmin > xmax: xmin, xmax = xmax, xmin ymin = util.convertUnitsStringToNumber(self.ui.ymin.text(), yTextBox.unit) ymax = util.convertUnitsStringToNumber(self.ui.ymax.text(), yTextBox.unit) if ymin > ymax: ymin, ymax = ymax, ymin except ValueError: # text box left blank return numPoints = 32 # per variable, 32*32 = 1,024 points total xRange = numpy.linspace(xmin, xmax, numPoints) _, xAxisLabel, xRangeUnits = rangeUnits(xTextBox, xRange) yRange = numpy.linspace(ymin, ymax, numPoints) _, yAxisLabel, yRangeUnits = rangeUnits(yTextBox, yRange) Z = numpy.zeros((numPoints, numPoints)) plotProgress = QtGui.QProgressDialog("Plotting ...", None, 0, (numPoints**2)-1) plotProgress.setMinimumDuration(0) try: for j, x in enumerate(xRange): for i, y in enumerate(yRange): plotProgress.setValue(plotProgress.value()+1) xTextBox.setText(str(x)) yTextBox.setText(str(y)) results = calculate(self.userInputDict()) try: Z[i,j] = results[zTextBox.dictName] except KeyError: Z[i,j] = float('nan') zUnit, zAxisLabel, _ = rangeUnits(zTextBox, Z.flat) for z in numpy.nditer(Z, op_flags=['readwrite']): z[...] = util.convertUnitsNumber(z, zTextBox.unit, zUnit) # Plotting self.ui.plotWidget.canvas.fig.clear() self.ui.plotWidget.canvas.ax = self.ui.plotWidget.canvas.fig.add_subplot(111) c = self.ui.plotWidget.canvas.ax.imshow(numpy.flipud(Z), cmap = 'hot', extent = [min(xRangeUnits), max(xRangeUnits), min(yRangeUnits), max(yRangeUnits)], aspect = 'auto') self.ui.plotWidget.canvas.ax.set_xlabel(xAxisLabel) self.ui.plotWidget.canvas.ax.set_ylabel(yAxisLabel) cb = self.ui.plotWidget.canvas.fig.colorbar(c) cb.set_label(zAxisLabel) self.ui.plotWidget.canvas.ax.set_xlim(min(xRangeUnits), max(xRangeUnits)) self.ui.plotWidget.canvas.ax.set_ylim(min(yRangeUnits), max(yRangeUnits)) self.ui.plotWidget.canvas.fig.tight_layout() self.ui.plotWidget.canvas.draw() finally: # Restore text boxes to original state xTextBox.setText(xOrginalValue) xTextBox.setCursorPosition(0) yTextBox.setText(yOrginalValue) yTextBox.setCursorPosition(0) self.calculateAll() def goalSeek(self): # Newton's method variableTextBox = self.textBox[self.ui.vary.currentText()] resultTextBox = self.textBox[self.ui.target.currentText()] self.ui.solverResult.setText('Searching...') maximumIterations = 1000 success = False dxFactor = 1e-9 try: x0 = self.valueFromTextBox[variableTextBox] except KeyError: x0 = dxFactor bestX = x0 try: goal = util.convertUnitsStringToNumber(self.ui.lineEdit.text(), resultTextBox.unit) y0 = self.calculateValue(variableTextBox, x0, resultTextBox) bestError = abs((y0-goal)/goal) for i in range(maximumIterations): try: y0 = self.calculateValue(variableTextBox, x0, resultTextBox) error = abs((y0-goal)/goal) if error < 1e-6: success = True break else: if error < bestError: bestX = x0 bestError = error dx = abs(dxFactor*x0) slope = self.calculateSlope(variableTextBox, x0, dx, resultTextBox) x0 = x0 - (y0-goal)/slope if x0 < 0: x0 = dx except (ZeroDivisionError, KeyError, OverflowError): dxFactor = 2*dxFactor x0 = x0+dx except ValueError: # "goal" is blank -> find extrema by Newton's method on slope dx = abs(dxFactor*x0) bestSlope = abs(self.calculateSlope(variableTextBox, x0, dx, resultTextBox)) for i in range(maximumIterations): try: dx = abs(dxFactor*x0) slope = self.calculateSlope(variableTextBox, x0, dx, resultTextBox) if abs(slope) < 1e-6: success = True break else: if slope < bestSlope: bestX = x0 bestSlope = slope secondDerivitive = self.calculateSecondDerivitive(variableTextBox, x0, dx, resultTextBox) x0 = x0 - (slope)/secondDerivitive if x0 < 0: x0 = dx except (ZeroDivisionError, KeyError, OverflowError): dxFactor = 2*dxFactor x0 = x0+dx if success: value = x0 self.ui.solverResult.setText('Success.') else: value = bestX self.ui.solverResult.setText('Failed. Could not find a solution.') variableTextBox.setText(util.displayWithUnitsNumber(util.roundSigFig(value, 5), variableTextBox.unit)) variableTextBox.setCursorPosition(0) self.calculateAll() def calculateValue(self, inputBox, inputValue, resultBox): inputBox.setText(str(inputValue)) result = calculate(self.userInputDict()) return result[resultBox.dictName] def calculateSlope(self, inputBox, inputValue, inputStep, resultBox): return (self.calculateValue(inputBox, inputValue + inputStep, resultBox) - self.calculateValue(inputBox, inputValue, resultBox))/inputStep def calculateSecondDerivitive(self, inputBox, inputValue, inputStep, resultBox): return (self.calculateSlope(inputBox, inputValue + inputStep, inputStep, resultBox) - self.calculateSlope(inputBox, inputValue, inputStep, resultBox))/inputStep def exportToFile(self, fileName = None): if not fileName: fileName = util.getSaveFileName(self) if not fileName: return fileLines = [] for box in self.textBox.values(): try: try: _, unit = util.separateNumberUnit(box.text()) value = util.convertUnitsNumberToString(self.valueFromTextBox[box], box.unit, unit) except (ValueError, KeyError): value = box.text() fileLines.append(box.objectName() + ':' + value) # text box except AttributeError: fileLines.append(box.objectName() + ':' + box.currentText()) # combo box with open(fileName, 'w') as f: f.write('\n'.join(sorted(fileLines))) def importFile(self, fileName = None): if not fileName: fileName = QtGui.QFileDialog.getOpenFileName(self, 'Open file', self.parent.lastUsedDirectory, util.fileTypeLists(self.acceptsFileTypes)) if not fileName: return self.parent.lastUsedDirectory = os.path.dirname(fileName) with open(fileName, 'r') as f: for line in f: name, value = line.strip().split(':') box = self.textBox[name] try: box.setText(value) # text box except AttributeError: box.setCurrentIndex(box.findText(value)) # combo box self.calculateAll() self.plot()