From 3e858452d3cb5768d1bc12a20a0e5fab45bfd186 Mon Sep 17 00:00:00 2001 From: reiche Date: Wed, 7 May 2025 16:20:11 +0200 Subject: [PATCH] First round of matching --- OpticsTools.py | 5 +- issues.txt | 15 ++++ matching.py | 24 ++++++ matchingmanager.py | 68 ++++++++++++++++ model.py | 52 +++++++++--- reference.py | 11 ++- ui/OpticsToolsGui.py | 106 ++++++++++++++++++++++++ ui/OpticsToolsGui.ui | 189 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 453 insertions(+), 17 deletions(-) create mode 100644 issues.txt create mode 100644 matching.py create mode 100644 matchingmanager.py diff --git a/OpticsTools.py b/OpticsTools.py index 091c5b1..a156121 100644 --- a/OpticsTools.py +++ b/OpticsTools.py @@ -11,7 +11,7 @@ from model import Model from machine import Machine from reference import ReferenceManager from sandbox import Sandbox - +from matching import Matching class OpticsTools(QtWidgets.QMainWindow, Ui_OpticsGUI): def __init__(self,phase=0, office = False): @@ -26,11 +26,11 @@ class OpticsTools(QtWidgets.QMainWindow, Ui_OpticsGUI): # initialize online model self.model = Model(phase=phase,parent=self) + self.matching = Matching(parent=self,model = self.model) if phase > 0: office = True self.machine = Machine(parent = True, office = office) self.machine.initPVs(self.model.getElements()) - self.reference = ReferenceManager(parent = self) self.sandbox = Sandbox(parent = self, machine = self.machine) title = "SwissFEL Optics Tools - Lattice %s (Phase %d)" % (self.model.getLatticeVersion(),phase) @@ -41,6 +41,7 @@ class OpticsTools(QtWidgets.QMainWindow, Ui_OpticsGUI): # initialization self.loadSettingsdirect("/sf/data/applications/BD-OpticsTools/reference/settings/ReferenceSetting.json") self.sandbox.updateSandbox() + self.reference = ReferenceManager(parent=self) # events handling self.actionOpen_2.triggered.connect(self.loadSettings) diff --git a/issues.txt b/issues.txt new file mode 100644 index 0000000..ca3cc90 --- /dev/null +++ b/issues.txt @@ -0,0 +1,15 @@ +1) Energy update in model from Sandbox or machine +2) Full update from machine +3) export to machine for magnet settings +4) export elegant lattice +5) support of other reference files -> fill up the selection list +6) matching +7) when writing magnets do snapshot? +8) Elegant support? +9) initial beta-function saved, also to settings file +10) Prepare several settings files +11) About and website support +12) install cpymax on machine network +13) save/load settings : Initial energy and initial twiss +14) right energy for tracking +15) set initial condition for tracking cpymadx diff --git a/matching.py b/matching.py new file mode 100644 index 0000000..93c76c8 --- /dev/null +++ b/matching.py @@ -0,0 +1,24 @@ +from matchingmanager import MatchManager + +class Matching: + def __init__(self,parent=None,model=None): + self.parent = parent + self.model = model + self.manager = MatchManager(parent=parent) + self.parent.UIMFodoMatchSingle.clicked.connect(self.matchingFodo) + + def matchingFodo(self): + print('matching') + par = self.manager.getFODOParameters() + if par is None: + return + quads=par['Quads'] + vals=par['Init'] + if par['FlipPol']: + vals = [-val for val in vals] + var = {name:vals[i] for i,name in enumerate(quads)} + cond = {'range':'#e','mux':par['mu'],'muy':par['mu']} + twiss,tar = self.model.match(sequence=par['Sequence'], destination=par['Destination'],variables=var, conditions=cond, periodic=True) + par['Result']=tar + par['Twiss']=twiss + self.manager.setFODOParameters(par) diff --git a/matchingmanager.py b/matchingmanager.py new file mode 100644 index 0000000..6c3c5be --- /dev/null +++ b/matchingmanager.py @@ -0,0 +1,68 @@ +class MatchManager: + def __init__(self,parent=None): + self.parent=parent + self.FODO={} + self.defineFODO() + self.updateFODOWidget() + self.parent.UIMFodoList.currentIndexChanged.connect(self.updateFODOWidget) + + def getFODOInfo(self,reference): + """ + retrieve if a seciton is matched and if yes get initial fodo values + :param reference: + :return: + """ + if reference == 'Injector': + a = 1 + elif reference == 'Linac 1': + a = 1 + elif reference == 'Linac 2': + a = 1 + elif reference == 'Linac 3': + a = 1 + elif reference == 'Aramis Undulator': + a = 1 + elif reference == 'Athos Undulator': + a = 1 + + ########################### + ##### generic FODO matching + + def updateFODOWidget(self): + reference = self.parent.UIMFodoList.currentText() + if not reference in self.FODO.keys(): + print('Not Supported') + return + self.parent.UIMFodoPhase.setText('%7.3f' % self.FODO[reference]['mu']) + self.parent.UIMFodoFlip.setChecked(self.FODO[reference]['FlipPol']) + if self.FODO[reference]['Result'] is None: + self.parent.UIMFodoResult.setText('Not matched yet') + else: + self.parent.UIMFodoResult.setText('%7.3e' % self.FODO[reference]['Result']) + + def setFODOParameters(self,par): + reference = self.parent.UIMFodoList.currentText() + if not reference in self.FODO.keys(): + print('Not Supported') + return None + self.FODO[reference]=par + + def getFODOParameters(self): + reference = self.parent.UIMFodoList.currentText() + if not reference in self.FODO.keys(): + print('Not Supported') + return None + self.FODO[reference]['mu'] = float(str(self.parent.UIMFodoPhase.text())) + self.FODO[reference]['FlipPol'] = self.parent.UIMFodoFlip.isChecked() + return self.FODO[reference] + + def defineFODO(self): + self.FODO.clear() + self.FODO['Injector']={'Sequence':'SINSB04','Destination': 'SARBD01', + 'Quads':['sinsb04.mqua130.k1','sinsb04.mqua230.k1'], + 'Init':[0.722,-0.7156],'FlipPol':False,'mu':0.2,'Result':None,'Twiss':None} + + self.FODO['Linac 1']={'Sequence':'S10CB01','Destination': 'SARBD01', + 'Quads':['s10cb01.mqua230.k1','s10cb01.mqua430.k1'], + 'Init':[-1.491,1.4905],'FlipPol':False,'mu':0.1883,'Result':None,'Twiss':None} + diff --git a/model.py b/model.py index 6f36bfa..d6bbcd3 100644 --- a/model.py +++ b/model.py @@ -1,5 +1,5 @@ import json - +import copy import numpy as np from onlinemodel.core import Facility from onlinemodel.madx import CMadX @@ -13,6 +13,10 @@ class Model: self.order = None self.madx = CMadX() + self.startTwiss = None + self.startEnergy = None + self.energyReference ='SINLH02.MBND100' + # hook up events self.eventHandling() @@ -20,10 +24,11 @@ class Model: return self.om.Version def updateEnergy(self,E0): - self.om.forceEnergyAt('SINLH02.MBND100', E0*1e6) + if isinstance(E0,list): + E0=E0[0] + self.om.forceEnergyAt(self.energyReference, E0*1e6) def updateElement(self,name,val): - if 'MQUA' in name or 'MQSK' in name: L = self.om.getRegExpElement(name[0:7], name[8:15],'Length')[0] self.om.setRegExpElement(name[0:7], name[8:15], 'k1', float(val[0])/L) @@ -70,8 +75,8 @@ class Model: rf={} undulators={} kicker={} - loc = 'SINLH02.MBND100' - energy={'location': loc, 'energy':self.om.EnergyAt(loc)[0]} + energy={'location': self.energyReference, 'energy' : 1e-6*self.om.EnergyAt(self.energyReference)[0]} + for ele in elements: if 'MQUA' in ele.Name or 'MQSK' in ele.Name: quadrupoles[ele.Name]={'k1':ele.k1,'k1L':ele.k1*ele.Length} @@ -86,7 +91,8 @@ class Model: rf[ele.Name]={'Gradient':ele.Gradient*ele.Length,'Phase':ele.Phase} elif 'MKAC' in ele.Name or 'MKDC' in ele.Name: kicker[ele.Name] = {'cory': ele.cory} - return {'Quadrupole':quadrupoles,'Sextupole':sextupoles,'Dipole':dipoles,'RF':rf,'Undulator':undulators,'Kicker':kicker,'Energy':energy} + return {'Quadrupole':quadrupoles,'Sextupole':sextupoles,'Dipole':dipoles,'RF':rf,'Undulator':undulators, + 'Kicker':kicker,'Energy':energy, 'InitialCondition':self.startTwiss} def loadSettingsGroup(self,group,fields): for key in group.keys(): @@ -101,7 +107,11 @@ class Model: self.loadSettingsGroup(settings['RF'], ['Gradient','Phase']) self.loadSettingsGroup(settings['Undulator'], ['K','kx','ky']) self.loadSettingsGroup(settings['Kicker'], ['cory']) - self.om.forceEnergyAt(settings['Energy']['location'],settings['Energy']['energy'][0]) + self.startEnergy = settings['Energy']['energy'] + self.energyReference = settings['Energy']['location'] + self.startTwiss = settings['InitialCondition'] + print('initial condition:',self.startTwiss) + self.updateEnergy(self.startEnergy) @@ -146,6 +156,18 @@ class Model: def eventHandling(self): self.parent.UITrack.clicked.connect(self.track) + def match(self,sequence,destination, variables,conditions,periodic=False,plot=True): + self.setBranch(destination.upper()) + twiss={'energy0':150.} + self.madx.updateVariables(twiss) + res,twiss,tar=self.madx.match(sequence,variables,conditions,periodic) + energy = self.calcEnergyProfile(twiss) + matchtwiss={'betax':twiss.betx[0],'betay':twiss.betay[0],'alphax':twiss.alfx[0],'alphay':twiss.alfy[0]} + if plot: + self.parent.plot.newData(twiss, energy) + return matchtwiss,tar + + def track(self): start = str(self.parent.UITrackStart.text()).upper() end = str(self.parent.UITrackEnd.text()).upper() @@ -155,7 +177,9 @@ class Model: end = end[0:7] twiss0 = self.parent.reference.getReferencePoint() refloc = self.parent.reference.getReferenceLocation().upper() - twiss0['energy'] = 150. + if refloc == 'START': + refloc = start.upper() + twiss0['energy'] = self.startEnergy start, end = self.checkRange(start, end, refloc[0:7]) if start is None: return @@ -168,13 +192,17 @@ class Model: self.setBranch(end.upper()) if not refloc == start: twiss0 = self.doBackTrack(refloc,start,twiss0) + self.startTwiss=copy.deepcopy(twiss0) self.madx.updateVariables(twiss0) twiss = self.madx.track(start+'$START',end+'$END') + energy = self.calcEnergyProfile(twiss) + if plot: + self.parent.plot.newData(twiss,energy) + + def calcEnergyProfile(self,twiss): energy = np.array([0. for i in range(len(twiss.betx))]) - e0 = 0. - de = 0 for i, name in enumerate(twiss.name): if len(name) > 15: elename = name[0:15] @@ -185,8 +213,8 @@ class Model: energy[:i]+=erg[0]*1e-6 e0 = (erg[0]+erg[1])*1e-6 energy[i]=e0 - if plot: - self.parent.plot.newData(twiss,energy) + return energy + def doBackTrack(self,start=None,end=None,twiss0=None): twiss0['alphax0'] = -twiss0['alphax0'] # revert particle trajectories diff --git a/reference.py b/reference.py index 7f8e9ac..027945e 100644 --- a/reference.py +++ b/reference.py @@ -36,7 +36,7 @@ class ReferenceManager: value = 0 if 'beta' in key: value = 30 - self.twisswidget[key].setText(str(value)) + self.twisswidget[key].setText('%7.3f' % value) def getReferencePoint(self): self.updateReferenceWidgets() # enforce that the data is consistent @@ -51,13 +51,18 @@ class ReferenceManager: key = str(self.parent.UITrackReference.currentText()) if key == 'User Defined': return - twiss = self.reference['Reference'][key]['twiss'] - name = self.reference['Reference'][key]['location'] + elif key == 'Start': + twiss = {key[0:-1]:self.parent.model.startTwiss[key] for key in self.parent.model.startTwiss.keys()} # strip of the zero + name = 'Start' + else: + twiss = self.reference['Reference'][key]['twiss'] + name = self.reference['Reference'][key]['location'] self.updateReferenceLocation(name) self.updateReferencePoint(twiss) def updateReferenceComboBox(self): self.parent.UITrackReference.clear() + self.parent.UITrackReference.addItem('Start') for ref in self.reference['Reference'].keys(): self.parent.UITrackReference.addItem(ref) self.parent.UITrackReference.addItem('User Defined') diff --git a/ui/OpticsToolsGui.py b/ui/OpticsToolsGui.py index 6d74334..d15c59d 100644 --- a/ui/OpticsToolsGui.py +++ b/ui/OpticsToolsGui.py @@ -346,6 +346,95 @@ class Ui_OpticsGUI(object): self.verticalLayout_17.addWidget(self.SB2ModUnd) self.horizontalLayout_5.addWidget(self.groupBox_9) self.TabMaster.addTab(self.tab_8, "") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.tab) + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.verticalLayout_5 = QtWidgets.QVBoxLayout() + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.groupBox = QtWidgets.QGroupBox(self.tab) + self.groupBox.setObjectName("groupBox") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.UIMFodoList = QtWidgets.QComboBox(self.groupBox) + self.UIMFodoList.setObjectName("UIMFodoList") + self.UIMFodoList.addItem("") + self.UIMFodoList.addItem("") + self.UIMFodoList.addItem("") + self.UIMFodoList.addItem("") + self.UIMFodoList.addItem("") + self.UIMFodoList.addItem("") + self.verticalLayout_2.addWidget(self.UIMFodoList) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_11 = QtWidgets.QLabel(self.groupBox) + self.label_11.setObjectName("label_11") + self.horizontalLayout_2.addWidget(self.label_11) + self.UIMFodoPhase = QtWidgets.QLineEdit(self.groupBox) + self.UIMFodoPhase.setObjectName("UIMFodoPhase") + self.horizontalLayout_2.addWidget(self.UIMFodoPhase) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.UIMFodoFlip = QtWidgets.QCheckBox(self.groupBox) + self.UIMFodoFlip.setObjectName("UIMFodoFlip") + self.verticalLayout_2.addWidget(self.UIMFodoFlip) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_12 = QtWidgets.QLabel(self.groupBox) + self.label_12.setObjectName("label_12") + self.horizontalLayout_3.addWidget(self.label_12) + self.UIMFodoResult = QtWidgets.QLineEdit(self.groupBox) + self.UIMFodoResult.setObjectName("UIMFodoResult") + self.horizontalLayout_3.addWidget(self.UIMFodoResult) + self.verticalLayout_2.addLayout(self.horizontalLayout_3) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.UIMFodoMatchSingle = QtWidgets.QPushButton(self.groupBox) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.UIMFodoMatchSingle.setFont(font) + self.UIMFodoMatchSingle.setStyleSheet("background-color: rgb(255, 255, 127);") + self.UIMFodoMatchSingle.setObjectName("UIMFodoMatchSingle") + self.horizontalLayout_4.addWidget(self.UIMFodoMatchSingle) + self.UIMFodoMatchAll = QtWidgets.QPushButton(self.groupBox) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.UIMFodoMatchAll.setFont(font) + self.UIMFodoMatchAll.setStyleSheet("background-color: rgb(255, 255, 127);") + self.UIMFodoMatchAll.setObjectName("UIMFodoMatchAll") + self.horizontalLayout_4.addWidget(self.UIMFodoMatchAll) + self.verticalLayout_2.addLayout(self.horizontalLayout_4) + self.verticalLayout_5.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(self.tab) + self.groupBox_2.setObjectName("groupBox_2") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.comboBox = QtWidgets.QComboBox(self.groupBox_2) + self.comboBox.setObjectName("comboBox") + self.verticalLayout_3.addWidget(self.comboBox) + self.checkBox = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox.setObjectName("checkBox") + self.verticalLayout_3.addWidget(self.checkBox) + self.horizontalLayout_6 = QtWidgets.QHBoxLayout() + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.label_13 = QtWidgets.QLabel(self.groupBox_2) + self.label_13.setObjectName("label_13") + self.horizontalLayout_6.addWidget(self.label_13) + self.lineEdit = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout_6.addWidget(self.lineEdit) + self.verticalLayout_3.addLayout(self.horizontalLayout_6) + self.verticalLayout_5.addWidget(self.groupBox_2) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_5.addItem(spacerItem2) + self.horizontalLayout_7.addLayout(self.verticalLayout_5) + spacerItem3 = QtWidgets.QSpacerItem(380, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_7.addItem(spacerItem3) + self.TabMaster.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.TabMaster.addTab(self.tab_2, "") self.verticalLayout_4.addWidget(self.TabMaster) OpticsGUI.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(OpticsGUI) @@ -526,6 +615,23 @@ class Ui_OpticsGUI(object): self.Mach2ModUnd.setText(_translate("OpticsGUI", "Model <- Sandbox <- Machine")) self.SB2ModUnd.setText(_translate("OpticsGUI", "Model <- Sandbox")) self.TabMaster.setTabText(self.TabMaster.indexOf(self.tab_8), _translate("OpticsGUI", "Sandbox")) + self.groupBox.setTitle(_translate("OpticsGUI", "FODO")) + self.UIMFodoList.setItemText(0, _translate("OpticsGUI", "Injector")) + self.UIMFodoList.setItemText(1, _translate("OpticsGUI", "Linac 1")) + self.UIMFodoList.setItemText(2, _translate("OpticsGUI", "Linac 2")) + self.UIMFodoList.setItemText(3, _translate("OpticsGUI", "Linac 3")) + self.UIMFodoList.setItemText(4, _translate("OpticsGUI", "Aramis Undulator")) + self.UIMFodoList.setItemText(5, _translate("OpticsGUI", "Athos Undulator")) + self.label_11.setText(_translate("OpticsGUI", "Phase Advance")) + self.UIMFodoFlip.setText(_translate("OpticsGUI", "Flipped Polarity")) + self.label_12.setText(_translate("OpticsGUI", "Result")) + self.UIMFodoMatchSingle.setText(_translate("OpticsGUI", "Match")) + self.UIMFodoMatchAll.setText(_translate("OpticsGUI", "Match All")) + self.groupBox_2.setTitle(_translate("OpticsGUI", "Reference Point")) + self.checkBox.setText(_translate("OpticsGUI", "Random Initialization")) + self.label_13.setText(_translate("OpticsGUI", "Result")) + self.TabMaster.setTabText(self.TabMaster.indexOf(self.tab), _translate("OpticsGUI", "Absolute Matching")) + self.TabMaster.setTabText(self.TabMaster.indexOf(self.tab_2), _translate("OpticsGUI", "Relative Matching")) self.menuFile.setTitle(_translate("OpticsGUI", "File")) self.menuHelp.setTitle(_translate("OpticsGUI", "Help")) self.actionOpen_2.setText(_translate("OpticsGUI", "Open Settings...")) diff --git a/ui/OpticsToolsGui.ui b/ui/OpticsToolsGui.ui index 862b7cc..211ff92 100644 --- a/ui/OpticsToolsGui.ui +++ b/ui/OpticsToolsGui.ui @@ -583,6 +583,195 @@ + + + Absolute Matching + + + + + + + + FODO + + + + + + + Injector + + + + + Linac 1 + + + + + Linac 2 + + + + + Linac 3 + + + + + Aramis Undulator + + + + + Athos Undulator + + + + + + + + + + Phase Advance + + + + + + + + + + + + Flipped Polarity + + + + + + + + + Result + + + + + + + + + + + + + + + 75 + true + + + + background-color: rgb(255, 255, 127); + + + Match + + + + + + + + 75 + true + + + + background-color: rgb(255, 255, 127); + + + Match All + + + + + + + + + + + + Reference Point + + + + + + + + + Random Initialization + + + + + + + + + Result + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 380 + 20 + + + + + + + + + Relative Matching + +