import sys import json import webbrowser import subprocess from argparse import ArgumentParser from PyQt5 import QtWidgets,QtGui,QtCore from ui.OpticsToolsGui import Ui_OpticsGUI from plot import OpticsPlot from model import Model from machine import Machine from reference import ReferenceManager from sandbox import Sandbox from matchmaker import MatchMaker from elegant import Elegant class OpticsTools(QtWidgets.QMainWindow, QtCore.QObject, Ui_OpticsGUI): sigStatus = QtCore.pyqtSignal(str) def __init__(self,phase=0, office= 1): super(OpticsTools, self).__init__() self.setupUi(self) office = office == 1 if phase > 0: office = True self.version = '1.1.1' self.setWindowIcon(QtGui.QIcon("rsc/iconoptics.png")) self.plot = OpticsPlot(parent=self) self.plot.show() # initialize online model self.model = Model(phase=phase,parent=self) self.elegant=Elegant(onlinemodel=self.model,parent=self) # initialize modeling self.match = MatchMaker(signal=self.sigStatus,phase=phase) self.UIMatchOpticsSelect.clear() for key in self.match.matchlist.keys(): self.UIMatchOpticsSelect.addItem(key) self.UIMatchOpticsSelect.setCurrentIndex(0) self.updateMatchingCase() self.machine = Machine(parent = True, office = office) self.machine.initPVs(self.model.getElements()) self.sandbox = Sandbox(parent = self, machine = self.machine) self.sandbox.updateSandbox() self.reference = ReferenceManager(parent=self) self.reference.initReferencePoints(self.match) self.updateMatchVariables() title = "SwissFEL Optics Tools - Lattice %s (Phase %d)" % (self.model.getLatticeVersion(),phase) if office: title += " - offline" self.setWindowTitle(title) # events handling self.UIMatchOpticsSelect.currentIndexChanged.connect(self.updateMatchingCase) self.UIMatchSelected.clicked.connect(self.doMatch) self.actionOpen_2.triggered.connect(self.loadSettings) self.actionSave.triggered.connect(self.saveSettings) self.actionExport_Elegant.triggered.connect(self.exportElegant) self.UIUpdateFromMachine.clicked.connect(self.fullUpdate) self.actionHelp.triggered.connect(self.openGit) self.actionAbout.triggered.connect(self.about) self.actionOpenScriptEditor.triggered.connect(self.editMatchingScript) self.sigStatus.connect(self.status) def doMatch(self): """ match the lattice for the given matching scripts. These can be selected by the individual check boxed. The matching is in the order: Injector -> Athos -> Porthos -> Aramis. The online model is updated with the updated match values. :return: None """ fullOutput = self.UIFullOutput.isChecked() injector = self.UIMatchInjector.isChecked() aramis = self.UIMatchAramis.isChecked() athos = self.UIMatchAthos.isChecked() porthos = self.UIMatchPorthos.isChecked() if self.UIInitAllMagnets.isChecked(): self.model.initializeMagnets() if self.UIModifyKnobs.isChecked(): vars = self.getMatchVariables() else: vars = None if self.UIDoInitialEnergy.isChecked(): erg0 =float(str(self.UIInitialEnergy.text())) else: erg0=-1 inittwiss={} inittwiss['betax']=float(str(self.UIInitBetax.text())) inittwiss['betay'] = float(str(self.UIInitBetay.text())) inittwiss['alphax'] = float(str(self.UIInitAlphax.text())) inittwiss['alphay'] = float(str(self.UIInitAlphay.text())) inittwiss['energy'] = erg0 twiss = self.match.match(self.model.om, variables = vars, initCond = inittwiss, Injector = injector,Athos = athos, Aramis = aramis, Porthos = porthos, full = fullOutput) self.updateMatchResult(self.match.matchresult) energy = self.model.calcEnergyProfile(twiss) #enfore the writing of a new lattice so that the magnet settings are in the new lattice self.model.forceLat = True self.plot.newData(twiss, energy) self.model.checkMagnetLimit() if self.UISaveMatchSettings.isChecked(): fileName = self.match.scriptdir+'/settings.json' self.saveSettingsdirect(fileName) def updateMatchResult(self,result): self.UIReportMatchResult.clear() for ele in result: label = '%s (%6.2e)' % (ele['Location'],ele['Error']) listitem = QtWidgets.QListWidgetItem(label) color = QtGui.QColor(100, 255, 100) # white if ele['Error'] > 1e-5: color = QtGui.QColor(255, 255, 100) if ele['Error'] > 1: color = QtGui.QColor(255, 100, 100) listitem.setBackground(color) self.UIReportMatchResult.addItem(listitem) def updateMatchingCase(self): """ Update the check box for selecting the different matching steps and initial settings if reference file is present :return: None """ target = self.UIMatchOpticsSelect.currentText() self.match.initScripts(target) self.updateMatchingCaseScript(self.UIMatchInjector,self.match.scriptInjector) self.updateMatchingCaseScript(self.UIMatchAthos, self.match.scriptAthos) self.updateMatchingCaseScript(self.UIMatchAramis, self.match.scriptAramis) self.updateMatchingCaseScript(self.UIMatchPorthos, self.match.scriptPorthos) if not self.match.settings is None: self.loadSettingsdirect(self.match.settings) def updateMatchingCaseScript(self,widget,state): """ Generalized routine to select and enable checkbox widgets :param widget: checkbox widget :param state: True or False :return: None """ widget.setChecked(state) widget.setEnabled(state) def updateMatchVariables(self): nrow = len(self.match.variables.keys()) self.UIMatchKnobs.setRowCount(nrow) for irow,key in enumerate(self.match.variables.keys()): self.UIMatchKnobs.setItem(irow, 0, QtWidgets.QTableWidgetItem(key)) self.UIMatchKnobs.setItem(irow, 1, QtWidgets.QTableWidgetItem('%f' % self.match.variables[key]['Value'])) self.UIMatchKnobs.item(irow, 0).setToolTip(self.match.variables[key]['Description']) self.UIMatchKnobs.resizeColumnsToContents() def getMatchVariables(self): variables={} nrow = self.UIMatchKnobs.rowCount() for irow in range(nrow): name = str(self.UIMatchKnobs.item(irow,0).text()) val = float(str(self.UIMatchKnobs.item(irow, 1).text())) variables[name]={'Val':val} return variables def exportElegant(self): self.elegant.export() def closeEvent(self, event): self.plot.close() event.accept() def about(self): QtWidgets.QMessageBox.about(self, "Optics Tool", "Version:%s\nContact: Sven Reiche\nEmail: sven.reiche@psi.ch" % self.version) def openGit(self): webbrowser.open("https://gitea.psi.ch/reiche/opticstool") def editMatchingScript(self): options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Matching Script File", "Scripts/switchyard.madx", "MadX Files (*.madx)", options=options) if not fileName: return subprocess.Popen(["emacs", fileName]) ################################################## def saveSettings(self): options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save Settings", "Settings/newSetting.json", "Json Files (*.json)", options=options) if not fileName: return self.saveSettingsdirect(fileName) def loadSettings(self): options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Settings", "Settings/ReferenceSetting.json", "Json Files (*.json)", options=options) if not fileName: return self.loadSettingsdirect(fileName) self.match.referencePoints.clear() self.match.referencePoints['swissfel$start'] = {'Twiss': self.model.startTwiss,'Label': 'Start'} self.reference.initReferencePoints(self.match) def loadSettingsdirect(self,fileName): print('Loading settings from %s ...' % fileName) with open(fileName, 'r', encoding='utf-8') as f: settings = json.load(f) status=self.model.loadSettings(settings) if 'InitialCondition' in settings.keys(): self.UIInitBetax.setText('%7.3f' % settings['InitialCondition']['betx']) self.UIInitBetay.setText('%7.3f' % settings['InitialCondition']['bety']) self.UIInitAlphax.setText('%7.3f' % settings['InitialCondition']['alfx']) self.UIInitAlphay.setText('%7.3f' % settings['InitialCondition']['alfy']) if status: print('Reference loaded from file %s' % fileName) self.status('Reference file loaded') else: print('Reference cannot be loaded from file %s' % fileName) print('Missing or mismatched phase of settings file with model') self.status('Phase Mismatch in Setting') def saveSettingsdirect(self,fileName): settings = self.model.getSettings() with open(fileName, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) #################################################### def fullUpdate(self): machine = self.machine.getMachineStatus() self.model.updateFromMachine(machine) self.sandbox.updateSandbox() self.status('Machine Settings') @QtCore.pyqtSlot(str) def status(self,msg): self.UIStatus.setText(msg) # -------------------------------- # Main routine if __name__ == '__main__': QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("plastique")) parser = ArgumentParser() parser.add_argument('-phase', type=int, help='Select Phase of the Lattice', default=2) parser.add_argument('-offline', type=int, help='Excludes any connection to control system', default=1) args = parser.parse_args() app = QtWidgets.QApplication(sys.argv) main = OpticsTools(phase = args.phase, office = args.offline) if main: main.show() sys.exit(app.exec_())