Files
OpticsTools/OpticsTools.py
T

273 lines
11 KiB
Python

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_())