From cd143921711a8233727510dd137b689f1ba0fe12 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 17 Sep 2025 16:16:39 -0300 Subject: [PATCH 01/61] WIP: copy "measure_bba" and first mods --- apsuite/commisslib/measure_pbba.py | 574 +++++++++++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 apsuite/commisslib/measure_pbba.py diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py new file mode 100644 index 000000000..2f11ca6bb --- /dev/null +++ b/apsuite/commisslib/measure_pbba.py @@ -0,0 +1,574 @@ +"""Main module.""" + +import datetime as _datetime +import time as _time +from copy import deepcopy as _dcopy + +import matplotlib.cm as _cmap +import matplotlib.gridspec as _mpl_gs +import matplotlib.pyplot as _plt +import numpy as _np +import pyaccel as _pyacc +from scipy.optimize import least_squares as _least_squares +from siriuspy.clientconfigdb import ConfigDBClient +from siriuspy.devices import CurrInfoSI as _CurrInfoSI, \ + PowerSupply as _PowerSupply, SOFB as _SOFB +from siriuspy.namesys import SiriusPVName as _PVName + +from ..utils import ParamsBaseClass as _ParamsBaseClass, \ + ThreadedMeasBaseClass as _BaseClass +from .measure_bba import DoBBA as _DoBBA + + +class ParallelBBAParams(_ParamsBaseClass): + """.""" + + BPMNAMES = ( + "SI-01M2:DI-BPM", + "SI-01C1:DI-BPM-1", + "SI-01C1:DI-BPM-2", + "SI-01C2:DI-BPM", + "SI-01C3:DI-BPM-1", + "SI-01C3:DI-BPM-2", + "SI-01C4:DI-BPM", + "SI-02M1:DI-BPM", + "SI-02M2:DI-BPM", + "SI-02C1:DI-BPM-1", + "SI-02C1:DI-BPM-2", + "SI-02C2:DI-BPM", + "SI-02C3:DI-BPM-1", + "SI-02C3:DI-BPM-2", + "SI-02C4:DI-BPM", + "SI-03M1:DI-BPM", + "SI-03M2:DI-BPM", + "SI-03C1:DI-BPM-1", + "SI-03C1:DI-BPM-2", + "SI-03C2:DI-BPM", + "SI-03C3:DI-BPM-1", + "SI-03C3:DI-BPM-2", + "SI-03C4:DI-BPM", + "SI-04M1:DI-BPM", + "SI-04M2:DI-BPM", + "SI-04C1:DI-BPM-1", + "SI-04C1:DI-BPM-2", + "SI-04C2:DI-BPM", + "SI-04C3:DI-BPM-1", + "SI-04C3:DI-BPM-2", + "SI-04C4:DI-BPM", + "SI-05M1:DI-BPM", + "SI-05M2:DI-BPM", + "SI-05C1:DI-BPM-1", + "SI-05C1:DI-BPM-2", + "SI-05C2:DI-BPM", + "SI-05C3:DI-BPM-1", + "SI-05C3:DI-BPM-2", + "SI-05C4:DI-BPM", + "SI-06M1:DI-BPM", + "SI-06M2:DI-BPM", + "SI-06C1:DI-BPM-1", + "SI-06C1:DI-BPM-2", + "SI-06C2:DI-BPM", + "SI-06C3:DI-BPM-1", + "SI-06C3:DI-BPM-2", + "SI-06C4:DI-BPM", + "SI-07M1:DI-BPM", + "SI-07M2:DI-BPM", + "SI-07C1:DI-BPM-1", + "SI-07C1:DI-BPM-2", + "SI-07C2:DI-BPM", + "SI-07C3:DI-BPM-1", + "SI-07C3:DI-BPM-2", + "SI-07C4:DI-BPM", + "SI-08M1:DI-BPM", + "SI-08M2:DI-BPM", + "SI-08C1:DI-BPM-1", + "SI-08C1:DI-BPM-2", + "SI-08C2:DI-BPM", + "SI-08C3:DI-BPM-1", + "SI-08C3:DI-BPM-2", + "SI-08C4:DI-BPM", + "SI-09M1:DI-BPM", + "SI-09M2:DI-BPM", + "SI-09C1:DI-BPM-1", + "SI-09C1:DI-BPM-2", + "SI-09C2:DI-BPM", + "SI-09C3:DI-BPM-1", + "SI-09C3:DI-BPM-2", + "SI-09C4:DI-BPM", + "SI-10M1:DI-BPM", + "SI-10M2:DI-BPM", + "SI-10C1:DI-BPM-1", + "SI-10C1:DI-BPM-2", + "SI-10C2:DI-BPM", + "SI-10C3:DI-BPM-1", + "SI-10C3:DI-BPM-2", + "SI-10C4:DI-BPM", + "SI-11M1:DI-BPM", + "SI-11M2:DI-BPM", + "SI-11C1:DI-BPM-1", + "SI-11C1:DI-BPM-2", + "SI-11C2:DI-BPM", + "SI-11C3:DI-BPM-1", + "SI-11C3:DI-BPM-2", + "SI-11C4:DI-BPM", + "SI-12M1:DI-BPM", + "SI-12M2:DI-BPM", + "SI-12C1:DI-BPM-1", + "SI-12C1:DI-BPM-2", + "SI-12C2:DI-BPM", + "SI-12C3:DI-BPM-1", + "SI-12C3:DI-BPM-2", + "SI-12C4:DI-BPM", + "SI-13M1:DI-BPM", + "SI-13M2:DI-BPM", + "SI-13C1:DI-BPM-1", + "SI-13C1:DI-BPM-2", + "SI-13C2:DI-BPM", + "SI-13C3:DI-BPM-1", + "SI-13C3:DI-BPM-2", + "SI-13C4:DI-BPM", + "SI-14M1:DI-BPM", + "SI-14M2:DI-BPM", + "SI-14C1:DI-BPM-1", + "SI-14C1:DI-BPM-2", + "SI-14C2:DI-BPM", + "SI-14C3:DI-BPM-1", + "SI-14C3:DI-BPM-2", + "SI-14C4:DI-BPM", + "SI-15M1:DI-BPM", + "SI-15M2:DI-BPM", + "SI-15C1:DI-BPM-1", + "SI-15C1:DI-BPM-2", + "SI-15C2:DI-BPM", + "SI-15C3:DI-BPM-1", + "SI-15C3:DI-BPM-2", + "SI-15C4:DI-BPM", + "SI-16M1:DI-BPM", + "SI-16M2:DI-BPM", + "SI-16C1:DI-BPM-1", + "SI-16C1:DI-BPM-2", + "SI-16C2:DI-BPM", + "SI-16C3:DI-BPM-1", + "SI-16C3:DI-BPM-2", + "SI-16C4:DI-BPM", + "SI-17M1:DI-BPM", + "SI-17M2:DI-BPM", + "SI-17C1:DI-BPM-1", + "SI-17C1:DI-BPM-2", + "SI-17C2:DI-BPM", + "SI-17C3:DI-BPM-1", + "SI-17C3:DI-BPM-2", + "SI-17C4:DI-BPM", + "SI-18M1:DI-BPM", + "SI-18M2:DI-BPM", + "SI-18C1:DI-BPM-1", + "SI-18C1:DI-BPM-2", + "SI-18C2:DI-BPM", + "SI-18C3:DI-BPM-1", + "SI-18C3:DI-BPM-2", + "SI-18C4:DI-BPM", + "SI-19M1:DI-BPM", + "SI-19M2:DI-BPM", + "SI-19C1:DI-BPM-1", + "SI-19C1:DI-BPM-2", + "SI-19C2:DI-BPM", + "SI-19C3:DI-BPM-1", + "SI-19C3:DI-BPM-2", + "SI-19C4:DI-BPM", + "SI-20M1:DI-BPM", + "SI-20M2:DI-BPM", + "SI-20C1:DI-BPM-1", + "SI-20C1:DI-BPM-2", + "SI-20C2:DI-BPM", + "SI-20C3:DI-BPM-1", + "SI-20C3:DI-BPM-2", + "SI-20C4:DI-BPM", + "SI-01M1:DI-BPM", + ) + QUADNAMES = ( + "SI-01M2:PS-QS", + "SI-01C1:PS-Q1", + "SI-01C1:PS-QS", + "SI-01C2:PS-QS", + "SI-01C3:PS-Q4", + "SI-01C3:PS-QS", + "SI-01C4:PS-Q1", + "SI-02M1:PS-QDB2", + "SI-02M2:PS-QDB2", + "SI-02C1:PS-Q1", + "SI-02C1:PS-QS", + "SI-02C2:PS-QS", + "SI-02C3:PS-Q4", + "SI-02C3:PS-QS", + "SI-02C4:PS-Q1", + "SI-03M1:PS-QDP2", + "SI-03M2:PS-QDP2", + "SI-03C1:PS-Q1", + "SI-03C1:PS-QS", + "SI-03C2:PS-QS", + "SI-03C3:PS-Q4", + "SI-03C3:PS-QS", + "SI-03C4:PS-Q1", + "SI-04M1:PS-QDB2", + "SI-04M2:PS-QDB2", + "SI-04C1:PS-Q1", + "SI-04C1:PS-QS", + "SI-04C2:PS-QS", + "SI-04C3:PS-Q4", + "SI-04C3:PS-QS", + "SI-04C4:PS-Q1", + "SI-05M1:PS-QS", + "SI-05M2:PS-QS", + "SI-05C1:PS-Q1", + "SI-05C1:PS-QS", + "SI-05C2:PS-QS", + "SI-05C3:PS-Q4", + "SI-05C3:PS-QS", + "SI-05C4:PS-Q1", + "SI-06M1:PS-QDB2", + "SI-06M2:PS-QDB2", + "SI-06C1:PS-Q1", + "SI-06C1:PS-QS", + "SI-06C2:PS-QS", + "SI-06C3:PS-Q4", + "SI-06C3:PS-QS", + "SI-06C4:PS-Q1", + "SI-07M1:PS-QDP2", + "SI-07M2:PS-QDP2", + "SI-07C1:PS-Q1", + "SI-07C1:PS-QS", + "SI-07C2:PS-QS", + "SI-07C3:PS-Q4", + "SI-07C3:PS-QS", + "SI-07C4:PS-Q1", + "SI-08M1:PS-QDB2", + "SI-08M2:PS-QDB2", + "SI-08C1:PS-Q1", + "SI-08C1:PS-QS", + "SI-08C2:PS-QS", + "SI-08C3:PS-Q4", + "SI-08C3:PS-QS", + "SI-08C4:PS-Q1", + "SI-09M1:PS-QS", + "SI-09M2:PS-QS", + "SI-09C1:PS-Q1", + "SI-09C1:PS-QS", + "SI-09C2:PS-QS", + "SI-09C3:PS-Q4", + "SI-09C3:PS-QS", + "SI-09C4:PS-Q1", + "SI-10M1:PS-QDB2", + "SI-10M2:PS-QDB2", + "SI-10C1:PS-Q1", + "SI-10C1:PS-QS", + "SI-10C2:PS-QS", + "SI-10C3:PS-Q4", + "SI-10C3:PS-QS", + "SI-10C4:PS-Q1", + "SI-11M1:PS-QDP2", + "SI-11M2:PS-QDP2", + "SI-11C1:PS-Q1", + "SI-11C1:PS-QS", + "SI-11C2:PS-QS", + "SI-11C3:PS-Q4", + "SI-11C3:PS-QS", + "SI-11C4:PS-Q1", + "SI-12M1:PS-QDB2", + "SI-12M2:PS-QDB2", + "SI-12C1:PS-Q1", + "SI-12C1:PS-QS", + "SI-12C2:PS-QS", + "SI-12C3:PS-Q4", + "SI-12C3:PS-QS", + "SI-12C4:PS-Q1", + "SI-13M1:PS-QS", + "SI-13M2:PS-QS", + "SI-13C1:PS-Q1", + "SI-13C1:PS-QS", + "SI-13C2:PS-QS", + "SI-13C3:PS-Q4", + "SI-13C3:PS-QS", + "SI-13C4:PS-Q1", + "SI-14M1:PS-QDB2", + "SI-14M2:PS-QDB2", + "SI-14C1:PS-Q1", + "SI-14C1:PS-QS", + "SI-14C2:PS-QS", + "SI-14C3:PS-Q4", + "SI-14C3:PS-QS", + "SI-14C4:PS-Q1", + "SI-15M1:PS-QDP2", + "SI-15M2:PS-QDP2", + "SI-15C1:PS-Q1", + "SI-15C1:PS-QS", + "SI-15C2:PS-QS", + "SI-15C3:PS-Q4", + "SI-15C3:PS-QS", + "SI-15C4:PS-Q1", + "SI-16M1:PS-QDB2", + "SI-16M2:PS-QDB2", + "SI-16C1:PS-Q1", + "SI-16C1:PS-QS", + "SI-16C2:PS-QS", + "SI-16C3:PS-Q4", + "SI-16C3:PS-QS", + "SI-16C4:PS-Q1", + "SI-17M1:PS-QS", + "SI-17M2:PS-QS", + "SI-17C1:PS-Q1", + "SI-17C1:PS-QS", + "SI-17C2:PS-QS", + "SI-17C3:PS-Q4", + "SI-17C3:PS-QS", + "SI-17C4:PS-Q1", + "SI-18M1:PS-QDB2", + "SI-18M2:PS-QDB2", + "SI-18C1:PS-Q1", + "SI-18C1:PS-QS", + "SI-18C2:PS-QS", + "SI-18C3:PS-Q4", + "SI-18C3:PS-QS", + "SI-18C4:PS-Q1", + "SI-19M1:PS-QDP2", + "SI-19M2:PS-QDP2", + "SI-19C1:PS-Q1", + "SI-19C1:PS-QS", + "SI-19C2:PS-QS", + "SI-19C3:PS-Q4", + "SI-19C3:PS-QS", + "SI-19C4:PS-Q1", + "SI-20M1:PS-QDB2", + "SI-20M2:PS-QDB2", + "SI-20C1:PS-Q1", + "SI-20C1:PS-QS", + "SI-20C2:PS-QS", + "SI-20C3:PS-Q4", + "SI-20C3:PS-QS", + "SI-20C4:PS-Q1", + "SI-01M1:PS-QS", + ) + + BPMNAMES = tuple([_PVName(bpm) for bpm in BPMNAMES]) + QUADNAMES = tuple([_PVName(quad) for quad in QUADNAMES]) + + def __init__(self): + """.""" + super().__init__() + + self.meas_nrsteps = 6 + self.quad_deltakl = 0.01 # [1/m] + + self.wait_correctors = 0.3 # [s] + self.wait_quadrupole = 0.3 # [s] + self.timeout_wait_orbit = 3 # [s] + + self.sofb_nrpoints = 10 + self.sofb_maxcorriter = 5 + self.sofb_maxorberr = 5 # [um] + + def __str__(self): + """.""" + return "..." + + +class DoParallelBBA(_BaseClass): + """.""" + + def __init__(self, isonline=True): + """.""" + super().__init__( + params=ParallelBBAParams(), target=self._do_bba, isonline=isonline + ) + self._groups2dopbba = list() + self._activegroup_id = None + self.clt_confdb = ConfigDBClient(config_type="si_bbadata") + self.clt_confdb._TIMEOUT_DEFAULT = 20 + self.data["bpmnames"] = list(ParallelBBAParams.BPMNAMES) + self.data["quadnames"] = list(ParallelBBAParams.QUADNAMES) + self.data["scancenterx"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) + self.data["scancentery"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) + self.data["measure"] = dict() + + if self.isonline: + self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) + self.devices["currinfosi"] = _CurrInfoSI() + self.connect_to_quadrupoles() + + def __str__(self): + """.""" + return "..." + + @property + def havebeam(self): + """.""" + haveb = self.devices["currinfosi"] + return haveb.connected and haveb.storedbeam + + @property + def measuredbpms(self): + """.""" + return sorted(self.data["measure"]) + + @property + def groups2dopbba(self): + return self.groups2dopbba + + @groups2dopbba.setter + def groups2dopbba(self, groups): + self._groups2dopbba = [[_PVName(bpm) for bpm in group] for group in groups] + + @property + def active_group_id(self): + return self._activegroup_id + + @active_group_id.setter + def active_group_id(self, group_idx): + if group_idx < len(self._groups2dopbba): + self._activegroup_id = group_idx + + def connect_to_quadrupoles(self): + """.""" + for bpm in self.groups2dopbba[self.active_group_id]: + idx = self.data["bpmnames"].index(bpm) + qname = self.data["quadnames"][idx] + if qname and qname not in self.devices: + self.devices[qname] = _PowerSupply(qname) + + def get_connected_quadnames(self): + qnames = [] + for bpm in self.groups2dopbba[self.active_group_id]: + idx = self.data["bpmnames"].index(bpm) + qname = self.data["quadnames"][idx] + if qname: + qnames.append(qname) + return qnames + + def get_connected_bpmnames(self): + return self.groups2dopbba[self.active_group_id] + + def get_orbit(self): + """.""" + sofb = self.devices["sofb"] + sofb.cmd_reset() + sofb.wait_buffer(self.params.timeout_wait_orbit) + return _np.hstack([sofb.orbx, sofb.orby]) + + def correct_orbit(self): + """.""" + sofb = self.devices["sofb"] + sofb.correct_orbit_manually( + nr_iters=self.params.sofb_maxcorriter, + residue=self.params.sofb_maxorberr, + ) + + @staticmethod + def get_default_quads(model, fam_data): + """.""" + return _DoBBA.get_default_quads(model, fam_data) + + @staticmethod + def list_bpm_subsections(bpms): + """.""" + return _DoBBA.list_bpm_subsections(bpms) + + def save_to_servconf(self, config_name): + """.""" + self.clt_confdb.insert_config(config_name, self.data) + + def load_from_servconf(self, config_name): + """.""" + self.data = self.clt_confdb.get_config_value(config_name) + + # ##### Make Figures ##### + #! empty + + # #### pbba utils ##### + @staticmethod + def get_ios_jacobian(model, group, fam_data, dkl=0.01): + bnames, qnames, qidx = DoParallelBBA.get_default_quads(model, fam_data) + + # #### private methods #### + def _do_pbba(self): + tini = _datetime.datetime.fromtimestamp(_time.time()) + print( + "Starting measurement at {:s}".format( + tini.strftime("%Y-%m-%d %Hh%Mm%Ss") + ) + ) + + sofb = self.devices["sofb"] + sofb.nr_points = self.params.sofb_nrpoints + loop_on = False + if sofb.autocorrsts: + loop_on = True + print("SOFB feedback is enable, disabling it...") + sofb.cmd_turn_off_autocorr() + + for i in range(len(self.groups2dopbba)): + if self._stopevt.is_set(): + print("stopped!") + break + if not self.havebeam: + print("Beam was Lost") + break + print("\nCorrecting Orbit...", end="") + self.correct_orbit() + print("Ok!") + self._dopbba_single_group(i) + + if loop_on: + print("SOFB feedback was enable, restoring original state...") + sofb.cmd_turn_on_autocorr() + + tfin = _datetime.datetime.fromtimestamp(_time.time()) + dtime = str(tfin - tini) + dtime = dtime.split(".")[0] + print("finished! Elapsed time {:s}".format(dtime)) + + def _dopbba_single_group(self, group_id): + """.""" + self.activegroup_id = group_id + bpmnames = self.get_connected_bpmnames() + self.connect_to_quadrupoles() + quadnames = self.get_connected_quadnames() + + tini = _datetime.datetime.fromtimestamp(_time.time()) + strtini = tini.strftime("%Hh%Mm%Ss") + print("{:s} --> Doing PBBA for Group {:d}".format(strtini)) + + sofb = self.devices["sofb"] + + korig = [] + for quadname in quadnames: + _quad = self.devices[quadname] + if not _quad.pwrstate: + print("\n error: quadrupole " + quadname + " is Off.") + self._stopevt.set() + print(" exiting...") + return + korig.append(_quad.strength) + + deltakl = self.groups_delta_kl[self.active_group_id] + ios_jac = self.get_ios_jacobian( + self._model, self.groups2dopbba[self.active_group_id] + ) + + nrsteps = self.params.meas_nrsteps + refx0, refy0 = sofb.refx.copy(), sofb.refy.copy() + + for i in range(nrsteps): + if self._stopevt.is_set() or not self.havebeam: + print(" exiting...") + break + print(" {0:02d}/{1:02d} --> ".format(i + 1, nrsteps), end="") + + res = self.correct_ios_single_iter( + jacobian=ios_jac + ) + + print("restoring initial conditions.") + + tfin = _datetime.datetime.fromtimestamp(_time.time()) + dtime = str(tfin - tini) + dtime = dtime.split(".")[0] + print("Done! Elapsed time: {:s}\n".format(dtime)) From 10e31479d84b13a026ac350d3c2902c68cbe0649 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 19 Sep 2025 15:10:34 -0300 Subject: [PATCH 02/61] save more --- apsuite/commisslib/measure_pbba.py | 176 ++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 2f11ca6bb..a579dd88a 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -18,7 +18,7 @@ from ..utils import ParamsBaseClass as _ParamsBaseClass, \ ThreadedMeasBaseClass as _BaseClass from .measure_bba import DoBBA as _DoBBA - +from ..orbcorr.orbit_correction import OrbitCorr as _OrbitCorr class ParallelBBAParams(_ParamsBaseClass): """.""" @@ -377,7 +377,7 @@ class DoParallelBBA(_BaseClass): def __init__(self, isonline=True): """.""" super().__init__( - params=ParallelBBAParams(), target=self._do_bba, isonline=isonline + params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) self._groups2dopbba = list() self._activegroup_id = None @@ -411,11 +411,11 @@ def measuredbpms(self): @property def groups2dopbba(self): - return self.groups2dopbba + return _dcopy(self._groups2dopbba) @groups2dopbba.setter def groups2dopbba(self, groups): - self._groups2dopbba = [[_PVName(bpm) for bpm in group] for group in groups] + self._groups2dopbba = [[_PVName(bpm) for bpm in group if isinstance(bpm, str)] for group in groups if isinstance(group, (list, tuple, _np.ndarray))] @property def active_group_id(self): @@ -464,28 +464,170 @@ def correct_orbit(self): @staticmethod def get_default_quads(model, fam_data): """.""" - return _DoBBA.get_default_quads(model, fam_data) + quads_idx = _dcopy(fam_data["QN"]["index"]) + qs_idx = [idx for idx in fam_data["QS"]["index"]] + quads_idx.extend(qs_idx) + quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) + quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) + + bpms_idx = _np.array([idx[0] for idx in fam_data["BPM"]["index"]]) + bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) + + diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) + bba_idx = _np.argmin(diff, axis=1) + quads_bba_idx = quads_idx[bba_idx] + bpmnames = list() + qnames = list() + for i, qidx in enumerate(quads_bba_idx): + name = model[qidx].fam_name + idc = fam_data[name]["index"].index([qidx]) + sub = fam_data[name]["subsection"][idc] + inst = fam_data[name]["instance"][idc] + name = "QS" if name.startswith(("S", "F")) else name + qname = "SI-{0:s}:PS-{1:s}-{2:s}".format(sub, name, inst) + qnames.append(qname.strip("-")) + + sub = fam_data["BPM"]["subsection"][i] + inst = fam_data["BPM"]["instance"][i] + bname = "SI-{0:s}:DI-BPM-{1:s}".format(sub, inst) + bname = bname.strip("-") + bpmnames.append(bname.strip("-")) + return bpmnames, bpms_idx, qnames, quads_bba_idx @staticmethod def list_bpm_subsections(bpms): """.""" return _DoBBA.list_bpm_subsections(bpms) - def save_to_servconf(self, config_name): - """.""" - self.clt_confdb.insert_config(config_name, self.data) - - def load_from_servconf(self, config_name): - """.""" - self.data = self.clt_confdb.get_config_value(config_name) - # ##### Make Figures ##### #! empty # #### pbba utils ##### - @staticmethod - def get_ios_jacobian(model, group, fam_data, dkl=0.01): - bnames, qnames, qidx = DoParallelBBA.get_default_quads(model, fam_data) + def get_group_delta_kl(self): + group = self.groups2dopbba[self.active_group_id] + delta_kl = _np.ones(len(group))*self.params.quad_deltakl + delta_kl[1::2] *= -1 + return delta_kl + + def get_group_ordering(self): + group = self.groups2dopbba[self.active_group_id] + return _np.arange(len(group)) + + def set_strengths(self, strengths, model=None, fam_data=None): + order = self.get_group_ordering() + group = self.groups2dopbba[self.active_group_id] + if len(strengths) != len(group): + raise ValueError('dim mismatch between the active group and "strengths".') + if model is not None: + bnames, _, qnames, qindices = self.get_default_quads(model, fam_data) + for _o in order: + _id = bnames.index(group[_o]) + qidx = qindices[_id] + if "QS" in qnames[_id]: + model[qidx].KsL = strengths[_o] + else: + model[qidx].KL = strengths[_o] + else: + qnames = self.data["quadnames"] + bnames = self.data["bpmnames"] + for _o in order: + bname = group[_o] + qname = qnames[bnames.index(bname)] + quad = self.devices[qname] + quad.strength = strengths[_o] + _time.sleep(self.params.wait_quadrupole) + + def get_strengths(self, model=None, fam_data=None): + order = self.get_group_ordering() + group = self.groups2dopbba[self.active_group_id] + strengths = _np.zeros(len(group)) + if model is not None: + bnames, _, qnames, qindices = self.get_default_quads(model, fam_data) + for _o in order: + _id = bnames.index(group[_o]) + qidx = qindices[_id] + if "QS" in qnames[_id]: + strengths[_o] = model[qidx].KsL + else: + strengths[_o] = model[qidx].KL + else: + qnames = self.data["quadnames"] + bnames = self.data["bpmnames"] + for _o in order: + bname = group[_o] + qname = qnames[bnames.index(bname)] + quad = self.devices[qname] + strengths[_o] = quad.strength + return strengths + + def calc_ios(self, model, fam_data): + if any([model.cavity_on, model.radiation_on > 0]): + findorbit = _pyacc.tracking.find_orbit6 + else: + findorbit = _pyacc.tracking.find_orbit4 + bpmidx = _np.array(fam_data['BPM']['index']).ravel() + delta_stren = self.get_group_delta_kl() + strens_orig = self.get_strengths(model, fam_data) + self.set_strengths(strens_orig + delta_stren/2, model, fam_data) + try: + orb_pos = findorbit(model, indices=bpmidx) + except Exception as E: + self.set_strengths(strens_orig, model, fam_data) + print(E) + return + self.set_strengths(strens_orig - delta_stren/2, model, fam_data) + try: + orb_neg = findorbit(model, indices=bpmidx) + except Exception as E: + self.set_strengths(strens_orig, model, fam_data) + print(E) + return + self.set_strengths(strens_orig, model, fam_data) + orb_pos = _np.hstack((orb_pos[0,:], orb_pos[2,:])) + orb_neg = _np.hstack((orb_neg[0,:], orb_neg[2,:])) + return orb_pos - orb_neg + + def meas_ios(self): + def get_orbit(): + orbx = self.devices['soft'].orbx + orby = self.devices['soft'].orby + return _np.hstack((orbx, orby)) + delta_stren = self.get_group_delta_kl() + strens_orig = self.get_strengths() + self.set_strengths(strens_orig + delta_stren/2) + orb_pos = get_orbit() + self.set_strengths(strens_orig + delta_stren/2) + orb_neg = get_orbit() + self.set_strengths(strens_orig) + return orb_pos - orb_neg + + def calc_ios_jacobian(self, model, fam_data): + use6d = any([model.cavity_on, model.radiation_on>0]) + _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) + # _orbcorr.params.enblrf = False + delta_stren = self.get_group_delta_kl() + strens_orig = self.get_strengths() + self.set_strengths(strens_orig + delta_stren/2, model, fam_data) + try: + jac_pos = _orbcorr.get_jacobian_matrix() + except Exception as E: + self.set_strengths(strens_orig, model, fam_data) + print(E) + return + self.set_strengths(strens_orig - delta_stren/2, model, fam_data) + try: + jac_neg = _orbcorr.get_jacobian_matrix() + except Exception as E: + self.set_strengths(strens_orig, model, fam_data) + print(E) + return + self.set_strengths(strens_orig, model, fam_data) + return jac_pos - jac_neg + + def meas_ios_jacobian(self): + raise NotImplementedError('Not available yet. Use \'calc_ios_jacobian\' for a model-base jacobian.') + + # #### private methods #### def _do_pbba(self): @@ -548,7 +690,7 @@ def _dopbba_single_group(self, group_id): return korig.append(_quad.strength) - deltakl = self.groups_delta_kl[self.active_group_id] + deltakl = self.get_group_delta_kl(self.active_group_id) ios_jac = self.get_ios_jacobian( self._model, self.groups2dopbba[self.active_group_id] ) From 77b9c1cdf45938e3b7dc4104a4e5e0f29542e1d3 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 19 Sep 2025 16:27:26 -0300 Subject: [PATCH 03/61] more-mods --- apsuite/commisslib/measure_pbba.py | 42 +++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index a579dd88a..c48c73cbc 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -355,13 +355,14 @@ def __init__(self): """.""" super().__init__() - self.meas_nrsteps = 6 self.quad_deltakl = 0.01 # [1/m] self.wait_correctors = 0.3 # [s] self.wait_quadrupole = 0.3 # [s] self.timeout_wait_orbit = 3 # [s] + self.corr_nr_iters = 6 + self.sofb_nrpoints = 10 self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] @@ -604,29 +605,56 @@ def get_orbit(): def calc_ios_jacobian(self, model, fam_data): use6d = any([model.cavity_on, model.radiation_on>0]) _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) - # _orbcorr.params.enblrf = False delta_stren = self.get_group_delta_kl() - strens_orig = self.get_strengths() + strens_orig = self.get_strengths(model, fam_data) self.set_strengths(strens_orig + delta_stren/2, model, fam_data) try: jac_pos = _orbcorr.get_jacobian_matrix() except Exception as E: self.set_strengths(strens_orig, model, fam_data) - print(E) + print("ggwp") return self.set_strengths(strens_orig - delta_stren/2, model, fam_data) try: jac_neg = _orbcorr.get_jacobian_matrix() except Exception as E: self.set_strengths(strens_orig, model, fam_data) - print(E) + print("ggez") return self.set_strengths(strens_orig, model, fam_data) return jac_pos - jac_neg def meas_ios_jacobian(self): - raise NotImplementedError('Not available yet. Use \'calc_ios_jacobian\' for a model-base jacobian.') + raise NotImplementedError('Not available yet. Use \'calc_ios_jacobian\' for a model-based jacobian.') + @staticmethod + def inverse_matrix(matrix): + return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=1e-8), matrix.T) + + def correct_ios(self, jacobian=None, model=None, fam_data=None): + if jacobian is None: + jacobian = self.calc_ios_jacobian(model, fam_data) + inverse_jacobian = self.inverse_matrix(jacobian) + ios_evo = [] + nr_iters = self.params.corr_nr_iters + for i in range(nr_iters+1): + if model is not None: + ios = self.calc_ios(model, fam_data) + else: + ios = self.meas_ios() + ios_evo.append(ios) + if i >= nr_iters: + break + dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) + if model is not None: + use6d = any([model.cavity_on, model.radiation_on>0]) + _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) + _orbcorr.set_delta_kicks(dkicks) + else: + sofb = self.devices['sofb'] + sofb.APPLY_DELTA_KICKS(dkicks) #!!!!!!!!!!!!!!!!!!!!!!!!!! + self.data[self.active_group_id] = {'ios': ios_evo} + return ios_evo # #### private methods #### @@ -695,7 +723,7 @@ def _dopbba_single_group(self, group_id): self._model, self.groups2dopbba[self.active_group_id] ) - nrsteps = self.params.meas_nrsteps + nrsteps = self.params.corr_nr_iters refx0, refy0 = sofb.refx.copy(), sofb.refy.copy() for i in range(nrsteps): From a136f97a956b6c1a69a901d5645b60f5030a3924 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 19 Sep 2025 16:51:31 -0300 Subject: [PATCH 04/61] after-test-on-model --- apsuite/commisslib/measure_pbba.py | 43 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index c48c73cbc..2c15f4ff4 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -362,6 +362,7 @@ def __init__(self): self.timeout_wait_orbit = 3 # [s] self.corr_nr_iters = 6 + self.ijac_tol = 1e-5 self.sofb_nrpoints = 10 self.sofb_maxcorriter = 5 @@ -628,33 +629,49 @@ def meas_ios_jacobian(self): raise NotImplementedError('Not available yet. Use \'calc_ios_jacobian\' for a model-based jacobian.') @staticmethod - def inverse_matrix(matrix): - return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=1e-8), matrix.T) + def inverse_matrix(matrix, rcond=None): + return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) def correct_ios(self, jacobian=None, model=None, fam_data=None): if jacobian is None: jacobian = self.calc_ios_jacobian(model, fam_data) - inverse_jacobian = self.inverse_matrix(jacobian) + inverse_jacobian = self.inverse_matrix(jacobian, self.params.ijac_tol) + if model is not None: + use6d = any([model.cavity_on, model.radiation_on>0]) + print(use6d) + sofb = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) + apply_dkicks = sofb.set_delta_kicks + get_kicks = sofb.get_kicks + else: + sofb = self.devices['sofb'] + apply_dkicks = sofb.APPLY_DELTA_KICKS + get_kicks = sofb.GET_KICKS ios_evo = [] + dkicks_evo = [] + kicks_ini = _np.array(get_kicks()) nr_iters = self.params.corr_nr_iters for i in range(nr_iters+1): + print(f'iter = {(i+1):1d}/{nr_iters:d}') if model is not None: ios = self.calc_ios(model, fam_data) else: ios = self.meas_ios() + dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) ios_evo.append(ios) + dkicks_evo.append(dkicks) if i >= nr_iters: break - dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) - if model is not None: - use6d = any([model.cavity_on, model.radiation_on>0]) - _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) - _orbcorr.set_delta_kicks(dkicks) - else: - sofb = self.devices['sofb'] - sofb.APPLY_DELTA_KICKS(dkicks) #!!!!!!!!!!!!!!!!!!!!!!!!!! - self.data[self.active_group_id] = {'ios': ios_evo} - return ios_evo + apply_dkicks(dkicks) + kicks_fim = _np.array(get_kicks()) + self.data[self.active_group_id] = { + 'bpms':self.groups2dopbba[self.active_group_id], + 'ios': ios_evo, + 'dkicks_evo': dkicks_evo, + 'kicks_ini': kicks_ini, + 'kicks_fim': kicks_fim, + 'dkicks': kicks_fim - kicks_ini + } + return self.data[self.active_group_id] # #### private methods #### From 38d0ccd30fd8b45f949e26a0d470e1e41792c7b0 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 22 Sep 2025 14:25:41 -0300 Subject: [PATCH 05/61] mods --- apsuite/commisslib/measure_pbba.py | 244 ++++++++++++----------------- 1 file changed, 98 insertions(+), 146 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 2c15f4ff4..7ffc59c9c 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -9,10 +9,11 @@ import matplotlib.pyplot as _plt import numpy as _np import pyaccel as _pyacc +import pymodels as _pymodels from scipy.optimize import least_squares as _least_squares from siriuspy.clientconfigdb import ConfigDBClient from siriuspy.devices import CurrInfoSI as _CurrInfoSI, \ - PowerSupply as _PowerSupply, SOFB as _SOFB + PowerSupply as _PowerSupply, SOFB as _SOFB, Tune as _Tune from siriuspy.namesys import SiriusPVName as _PVName from ..utils import ParamsBaseClass as _ParamsBaseClass, \ @@ -392,9 +393,9 @@ def __init__(self, isonline=True): self.data["measure"] = dict() if self.isonline: + self.devices["tune"] = _Tune(_Tune.DEVICES.SI) self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) self.devices["currinfosi"] = _CurrInfoSI() - self.connect_to_quadrupoles() def __str__(self): """.""" @@ -428,26 +429,26 @@ def active_group_id(self, group_idx): if group_idx < len(self._groups2dopbba): self._activegroup_id = group_idx + def get_active_bpmnames(self): + return self.groups2dopbba[self.active_group_id] + def connect_to_quadrupoles(self): """.""" - for bpm in self.groups2dopbba[self.active_group_id]: + for bpm in self.get_active_bpmnames(): idx = self.data["bpmnames"].index(bpm) qname = self.data["quadnames"][idx] if qname and qname not in self.devices: self.devices[qname] = _PowerSupply(qname) - def get_connected_quadnames(self): + def get_active_quadnames(self): qnames = [] - for bpm in self.groups2dopbba[self.active_group_id]: + for bpm in self.get_active_bpmnames(): idx = self.data["bpmnames"].index(bpm) qname = self.data["quadnames"][idx] if qname: qnames.append(qname) return qnames - def get_connected_bpmnames(self): - return self.groups2dopbba[self.active_group_id] - def get_orbit(self): """.""" sofb = self.devices["sofb"] @@ -506,163 +507,135 @@ def list_bpm_subsections(bpms): # #### pbba utils ##### def get_group_delta_kl(self): - group = self.groups2dopbba[self.active_group_id] - delta_kl = _np.ones(len(group))*self.params.quad_deltakl + bpms = self.get_active_bpmnames() + delta_kl = _np.ones(len(bpms))*self.params.quad_deltakl delta_kl[1::2] *= -1 return delta_kl def get_group_ordering(self): - group = self.groups2dopbba[self.active_group_id] - return _np.arange(len(group)) + bpms = self.get_active_bpmnames() + return _np.arange(len(bpms)) - def set_strengths(self, strengths, model=None, fam_data=None): + def set_strengths(self, strengths): order = self.get_group_ordering() - group = self.groups2dopbba[self.active_group_id] - if len(strengths) != len(group): - raise ValueError('dim mismatch between the active group and "strengths".') - if model is not None: - bnames, _, qnames, qindices = self.get_default_quads(model, fam_data) - for _o in order: - _id = bnames.index(group[_o]) - qidx = qindices[_id] - if "QS" in qnames[_id]: - model[qidx].KsL = strengths[_o] - else: - model[qidx].KL = strengths[_o] - else: - qnames = self.data["quadnames"] - bnames = self.data["bpmnames"] - for _o in order: - bname = group[_o] - qname = qnames[bnames.index(bname)] - quad = self.devices[qname] - quad.strength = strengths[_o] - _time.sleep(self.params.wait_quadrupole) + bpms = self.get_active_bpmnames() + if len(strengths) != len(bpms): + raise ValueError('dim mismatch between the active group and \"strengths\".') + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] + for _o in order: + bpmname = bpms[_o] + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + if not quad.pwrstate: + print("\n error: quadrupole " + quadname + " is Off.") + self._stopevt.set() + print(" exiting...") + return + quad.strength = strengths[_o] + _time.sleep(self.params.wait_quadrupole) - def get_strengths(self, model=None, fam_data=None): + def get_strengths(self): order = self.get_group_ordering() - group = self.groups2dopbba[self.active_group_id] - strengths = _np.zeros(len(group)) - if model is not None: - bnames, _, qnames, qindices = self.get_default_quads(model, fam_data) - for _o in order: - _id = bnames.index(group[_o]) - qidx = qindices[_id] - if "QS" in qnames[_id]: - strengths[_o] = model[qidx].KsL - else: - strengths[_o] = model[qidx].KL - else: - qnames = self.data["quadnames"] - bnames = self.data["bpmnames"] - for _o in order: - bname = group[_o] - qname = qnames[bnames.index(bname)] - quad = self.devices[qname] - strengths[_o] = quad.strength + bpms = self.get_active_bpmnames() + strengths = _np.zeros(len(bpms)) + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] + for _o in order: + bpmname = bpms[_o] + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + if not quad.pwrstate: + print("\n error: quadrupole " + quadname + " is Off.") + self._stopevt.set() + print(" exiting...") + return + strengths[_o] = quad.strength return strengths - def calc_ios(self, model, fam_data): - if any([model.cavity_on, model.radiation_on > 0]): - findorbit = _pyacc.tracking.find_orbit6 - else: - findorbit = _pyacc.tracking.find_orbit4 - bpmidx = _np.array(fam_data['BPM']['index']).ravel() - delta_stren = self.get_group_delta_kl() - strens_orig = self.get_strengths(model, fam_data) - self.set_strengths(strens_orig + delta_stren/2, model, fam_data) - try: - orb_pos = findorbit(model, indices=bpmidx) - except Exception as E: - self.set_strengths(strens_orig, model, fam_data) - print(E) - return - self.set_strengths(strens_orig - delta_stren/2, model, fam_data) - try: - orb_neg = findorbit(model, indices=bpmidx) - except Exception as E: - self.set_strengths(strens_orig, model, fam_data) - print(E) - return - self.set_strengths(strens_orig, model, fam_data) - orb_pos = _np.hstack((orb_pos[0,:], orb_pos[2,:])) - orb_neg = _np.hstack((orb_neg[0,:], orb_neg[2,:])) - return orb_pos - orb_neg - def meas_ios(self): - def get_orbit(): - orbx = self.devices['soft'].orbx - orby = self.devices['soft'].orby - return _np.hstack((orbx, orby)) delta_stren = self.get_group_delta_kl() strens_orig = self.get_strengths() self.set_strengths(strens_orig + delta_stren/2) - orb_pos = get_orbit() + orb_pos = self.get_orbit() self.set_strengths(strens_orig + delta_stren/2) - orb_neg = get_orbit() + orb_neg = self.get_orbit() self.set_strengths(strens_orig) return orb_pos - orb_neg - def calc_ios_jacobian(self, model, fam_data): + def get_ios_jacobian(self, model=None, fam_data=None): + if model is None: + model = _pymodels.si.fitted_models.vertical_dispersion_and_coupling(_pymodels.si.create_accelerator()) + if fam_data is None: + fam_data = _pymodels.si.families.get_family_data(model) use6d = any([model.cavity_on, model.radiation_on>0]) _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) + bpms = self.get_active_bpmnames() + order = self.get_group_ordering() delta_stren = self.get_group_delta_kl() - strens_orig = self.get_strengths(model, fam_data) - self.set_strengths(strens_orig + delta_stren/2, model, fam_data) + bnames, _, qnames, quadindices = self.get_default_quads(model, fam_data) + def _get_strengths(): + strens = [] + for _o in order: + _id = bnames.index(bpms[_o]) + qidx = quadindices[_id] + if "QS" in qnames[_id]: + strens.append(model[qidx].KsL) + else: + strens.append(model[qidx].KL) + return strens + strens_orig = _get_strengths() + def _set_strengths(strengths): + for _o in order: + _id = bnames.index(bpms[_o]) + qidx = quadindices[_id] + if "QS" in qnames[_id]: + model[qidx].KsL = strengths[_o] + else: + model[qidx].KL = strengths[_o] + _set_strengths(strens_orig + delta_stren/2) try: jac_pos = _orbcorr.get_jacobian_matrix() except Exception as E: - self.set_strengths(strens_orig, model, fam_data) - print("ggwp") + _set_strengths(strens_orig) + print(E) return - self.set_strengths(strens_orig - delta_stren/2, model, fam_data) + _set_strengths(strens_orig - delta_stren/2) try: jac_neg = _orbcorr.get_jacobian_matrix() except Exception as E: - self.set_strengths(strens_orig, model, fam_data) - print("ggez") + _set_strengths(strens_orig) + print(E) return - self.set_strengths(strens_orig, model, fam_data) + _set_strengths(strens_orig) return jac_pos - jac_neg - def meas_ios_jacobian(self): - raise NotImplementedError('Not available yet. Use \'calc_ios_jacobian\' for a model-based jacobian.') - @staticmethod def inverse_matrix(matrix, rcond=None): return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) - def correct_ios(self, jacobian=None, model=None, fam_data=None): + def correct_ios(self, jacobian=None): if jacobian is None: - jacobian = self.calc_ios_jacobian(model, fam_data) + jacobian = self.get_ios_jacobian() inverse_jacobian = self.inverse_matrix(jacobian, self.params.ijac_tol) - if model is not None: - use6d = any([model.cavity_on, model.radiation_on>0]) - print(use6d) - sofb = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) - apply_dkicks = sofb.set_delta_kicks - get_kicks = sofb.get_kicks - else: - sofb = self.devices['sofb'] - apply_dkicks = sofb.APPLY_DELTA_KICKS - get_kicks = sofb.GET_KICKS + sofb = self.devices['sofb'] ios_evo = [] dkicks_evo = [] - kicks_ini = _np.array(get_kicks()) + kicks_ini = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! nr_iters = self.params.corr_nr_iters for i in range(nr_iters+1): - print(f'iter = {(i+1):1d}/{nr_iters:d}') - if model is not None: - ios = self.calc_ios(model, fam_data) - else: - ios = self.meas_ios() + if self._stopevt.is_set() or not self.havebeam: + print(" exiting...") + break + print(" {0:02d}/{1:02d} --> ".format(i + 1, nr_iters), end="") + ios = self.meas_ios() dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) ios_evo.append(ios) dkicks_evo.append(dkicks) if i >= nr_iters: break - apply_dkicks(dkicks) - kicks_fim = _np.array(get_kicks()) + sofb.set_delta_kicks(dkicks) #!!!!!!!!!!!!!!!!!!!!!!!! + kicks_fim = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! self.data[self.active_group_id] = { 'bpms':self.groups2dopbba[self.active_group_id], 'ios': ios_evo, @@ -715,46 +688,25 @@ def _do_pbba(self): def _dopbba_single_group(self, group_id): """.""" self.activegroup_id = group_id - bpmnames = self.get_connected_bpmnames() + bpmnames = self.get_active_bpmnames() self.connect_to_quadrupoles() - quadnames = self.get_connected_quadnames() + quadnames = self.get_active_quadnames() tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime("%Hh%Mm%Ss") print("{:s} --> Doing PBBA for Group {:d}".format(strtini)) sofb = self.devices["sofb"] - - korig = [] - for quadname in quadnames: - _quad = self.devices[quadname] - if not _quad.pwrstate: - print("\n error: quadrupole " + quadname + " is Off.") - self._stopevt.set() - print(" exiting...") - return - korig.append(_quad.strength) - - deltakl = self.get_group_delta_kl(self.active_group_id) - ios_jac = self.get_ios_jacobian( - self._model, self.groups2dopbba[self.active_group_id] - ) - - nrsteps = self.params.corr_nr_iters + tune = self.devices["tune"] refx0, refy0 = sofb.refx.copy(), sofb.refy.copy() - for i in range(nrsteps): - if self._stopevt.is_set() or not self.havebeam: - print(" exiting...") - break - print(" {0:02d}/{1:02d} --> ".format(i + 1, nrsteps), end="") + korig = self.get_strengths() - res = self.correct_ios_single_iter( - jacobian=ios_jac - ) + ios_jac = self.get_ios_jacobian() print("restoring initial conditions.") + tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split(".")[0] From acaac8b37dd0cbba7bcbf1c13cccf19e5df70434 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 22 Sep 2025 16:40:24 -0300 Subject: [PATCH 06/61] saving --- apsuite/commisslib/measure_pbba.py | 62 ++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 7ffc59c9c..a97aa786b 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -464,6 +464,10 @@ def correct_orbit(self): residue=self.params.sofb_maxorberr, ) + def get_tunes(self): + tune = self.devices['tune'] + return _np.array([tune.tunex, tune.tuney]) + @staticmethod def get_default_quads(model, fam_data): """.""" @@ -516,13 +520,14 @@ def get_group_ordering(self): bpms = self.get_active_bpmnames() return _np.arange(len(bpms)) - def set_strengths(self, strengths): + def set_strengths(self, strengths, track_tune=False): order = self.get_group_ordering() bpms = self.get_active_bpmnames() if len(strengths) != len(bpms): raise ValueError('dim mismatch between the active group and \"strengths\".') quad_names = self.data["quadnames"] bpm_names = self.data["bpmnames"] + tunes = [] for _o in order: bpmname = bpms[_o] quadname = quad_names[bpm_names.index(bpmname)] @@ -534,6 +539,10 @@ def set_strengths(self, strengths): return quad.strength = strengths[_o] _time.sleep(self.params.wait_quadrupole) + tunes.append(self.get_tunes()) + if track_tune: + return tunes + return def get_strengths(self): order = self.get_group_ordering() @@ -553,15 +562,18 @@ def get_strengths(self): strengths[_o] = quad.strength return strengths - def meas_ios(self): + def meas_ios(self, track_tune=False): delta_stren = self.get_group_delta_kl() strens_orig = self.get_strengths() - self.set_strengths(strens_orig + delta_stren/2) + tune_up = self.set_strengths(strens_orig + delta_stren/2, track_tune) orb_pos = self.get_orbit() - self.set_strengths(strens_orig + delta_stren/2) + tune_down = self.set_strengths(strens_orig + delta_stren/2, track_tune) orb_neg = self.get_orbit() - self.set_strengths(strens_orig) - return orb_pos - orb_neg + tune_back = self.set_strengths(strens_orig, track_tune) + if track_tune: + tune_variation = tune_up + tune_down + tune_back + return orb_pos - orb_neg, tune_variation + return orb_pos - orb_neg, None def get_ios_jacobian(self, model=None, fam_data=None): if model is None: @@ -614,13 +626,12 @@ def _set_strengths(strengths): def inverse_matrix(matrix, rcond=None): return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) - def correct_ios(self, jacobian=None): + def correct_ios(self, jacobian=None, track_tune=False): if jacobian is None: jacobian = self.get_ios_jacobian() inverse_jacobian = self.inverse_matrix(jacobian, self.params.ijac_tol) sofb = self.devices['sofb'] - ios_evo = [] - dkicks_evo = [] + ios_iter, tune_iter, dkicks_iter = [], [], [] kicks_ini = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! nr_iters = self.params.corr_nr_iters for i in range(nr_iters+1): @@ -628,24 +639,25 @@ def correct_ios(self, jacobian=None): print(" exiting...") break print(" {0:02d}/{1:02d} --> ".format(i + 1, nr_iters), end="") - ios = self.meas_ios() - dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) - ios_evo.append(ios) - dkicks_evo.append(dkicks) + ios, tune = self.meas_ios(track_tune) + ios_iter.append(ios) + tune_iter.append(tune) if i >= nr_iters: break + dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) + dkicks_iter.append(dkicks) sofb.set_delta_kicks(dkicks) #!!!!!!!!!!!!!!!!!!!!!!!! kicks_fim = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! - self.data[self.active_group_id] = { + self.data[f'group_{self.active_group_id:0d}'] = { 'bpms':self.groups2dopbba[self.active_group_id], - 'ios': ios_evo, - 'dkicks_evo': dkicks_evo, + 'ios_iter': ios_iter, + 'dkicks_iter': dkicks_iter, 'kicks_ini': kicks_ini, 'kicks_fim': kicks_fim, - 'dkicks': kicks_fim - kicks_ini + 'ordering':self.get_group_ordering(), + 'delta_kl':self.get_group_delta_kl(), } - return self.data[self.active_group_id] - + if track_tune: self.data['tune_iter'] = tune_iter # #### private methods #### def _do_pbba(self): @@ -689,20 +701,22 @@ def _dopbba_single_group(self, group_id): """.""" self.activegroup_id = group_id bpmnames = self.get_active_bpmnames() + bpmnames_all = self.data['bpmnames'] + nbpms = len(bpmnames_all) self.connect_to_quadrupoles() - quadnames = self.get_active_quadnames() tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime("%Hh%Mm%Ss") print("{:s} --> Doing PBBA for Group {:d}".format(strtini)) sofb = self.devices["sofb"] - tune = self.devices["tune"] - refx0, refy0 = sofb.refx.copy(), sofb.refy.copy() - - korig = self.get_strengths() ios_jac = self.get_ios_jacobian() + self.correct_ios(ios_jac) + orbit = self.get_orbit() + for bpm in bpmnames: + idx = bpmnames_all.index(bpm) + self.data['measure'][bpm] = {'x0':orbit[idx], 'y0':orbit[idx+nbpms]} print("restoring initial conditions.") From 1e353c7f9ced830c44b82aa267bde74358dd7137 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 24 Sep 2025 10:24:42 -0300 Subject: [PATCH 07/61] latest --- apsuite/commisslib/measure_pbba.py | 43 ++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index a97aa786b..05a4df88f 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -363,7 +363,7 @@ def __init__(self): self.timeout_wait_orbit = 3 # [s] self.corr_nr_iters = 6 - self.ijac_tol = 1e-5 + self.ijac_tol = 1e-7 self.sofb_nrpoints = 10 self.sofb_maxcorriter = 5 @@ -464,6 +464,33 @@ def correct_orbit(self): residue=self.params.sofb_maxorberr, ) + def get_kicks(self): + sofb = self.devices['sofb'] + return _np.hstack(( + list(sofb.kickch), + list(sofb.kickcv), + list(sofb.kickrf)) + ) + + def set_delta_kicks(self, dkicks): + sofb = self.devices['sofb'] + nch, ncv, nrf = sofb._data.nr_ch, sofb._data.nr_cv, 1 + if len(dkicks) != nch+ncv+nrf: + raise ValueError(f'invalid dim for dkicks, must have shape=({nch+ncv+nrf},)') + dch, dcv, drf = dkicks[:nch], dkicks[nch:ncv], dkicks[ncv:] + + factch, factcv, factrf = sofb.mancorrgainch, sofb.mancorrgaincv, sofb.mancorrgainrf + sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch, dcv, drf + nrsteps = _np.ceil(max(_np.abs(dch).max(), _np.abs(dcv).max()) / 1.0) + for i in range(int(nrsteps)): + sofb.mancorrgainch = (i + 1) / nrsteps * 100 + sofb.mancorrgaincv = (i + 1) / nrsteps * 100 + sofb.mancorrgainrf = (i + 1) / nrsteps * 100 + sofb.cmd_applycorr_all() + _time.sleep(self.params.wait_correctors) + sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch * 0, dcv * 0, drf * 0 + sofb.mancorrgainch, sofb.mancorrgaincv = factch, factcv, factrf + def get_tunes(self): tune = self.devices['tune'] return _np.array([tune.tunex, tune.tuney]) @@ -623,16 +650,15 @@ def _set_strengths(strengths): return jac_pos - jac_neg @staticmethod - def inverse_matrix(matrix, rcond=None): + def inverse_matrix(matrix, rcond=1e-8): return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) def correct_ios(self, jacobian=None, track_tune=False): if jacobian is None: jacobian = self.get_ios_jacobian() inverse_jacobian = self.inverse_matrix(jacobian, self.params.ijac_tol) - sofb = self.devices['sofb'] ios_iter, tune_iter, dkicks_iter = [], [], [] - kicks_ini = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! + kicks_ini = self.get_kicks() nr_iters = self.params.corr_nr_iters for i in range(nr_iters+1): if self._stopevt.is_set() or not self.havebeam: @@ -646,9 +672,10 @@ def correct_ios(self, jacobian=None, track_tune=False): break dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) dkicks_iter.append(dkicks) - sofb.set_delta_kicks(dkicks) #!!!!!!!!!!!!!!!!!!!!!!!! - kicks_fim = _np.array(sofb.get_kicks()) #!!!!!!!!!!!!!!!!!!!!!!!! - self.data[f'group_{self.active_group_id:0d}'] = { + self.set_delta_kicks(dkicks) + kicks_fim = self.get_kicks() + group_name = f'group_{self.active_group_id:0d}' + self.data[group_name] = { 'bpms':self.groups2dopbba[self.active_group_id], 'ios_iter': ios_iter, 'dkicks_iter': dkicks_iter, @@ -657,7 +684,7 @@ def correct_ios(self, jacobian=None, track_tune=False): 'ordering':self.get_group_ordering(), 'delta_kl':self.get_group_delta_kl(), } - if track_tune: self.data['tune_iter'] = tune_iter + if track_tune: self.data[group_name]['tune_iter'] = tune_iter # #### private methods #### def _do_pbba(self): From f220042ec5ee59bcacf7bc1d7e3d03aeb87df314 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 24 Sep 2025 12:00:27 -0300 Subject: [PATCH 08/61] save --- apsuite/commisslib/measure_pbba.py | 124 ++++++++++++++++------------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 05a4df88f..9399daf0a 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -363,12 +363,14 @@ def __init__(self): self.timeout_wait_orbit = 3 # [s] self.corr_nr_iters = 6 - self.ijac_tol = 1e-7 + self.inv_jac_rcond = 1e-5 self.sofb_nrpoints = 10 self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] + self.dotrack_tune = True + def __str__(self): """.""" return "..." @@ -391,6 +393,8 @@ def __init__(self, isonline=True): self.data["scancenterx"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) self.data["scancentery"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) self.data["measure"] = dict() + self._model = None + self._famdata = None if self.isonline: self.devices["tune"] = _Tune(_Tune.DEVICES.SI) @@ -495,47 +499,6 @@ def get_tunes(self): tune = self.devices['tune'] return _np.array([tune.tunex, tune.tuney]) - @staticmethod - def get_default_quads(model, fam_data): - """.""" - quads_idx = _dcopy(fam_data["QN"]["index"]) - qs_idx = [idx for idx in fam_data["QS"]["index"]] - quads_idx.extend(qs_idx) - quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) - quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) - - bpms_idx = _np.array([idx[0] for idx in fam_data["BPM"]["index"]]) - bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) - - diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) - bba_idx = _np.argmin(diff, axis=1) - quads_bba_idx = quads_idx[bba_idx] - bpmnames = list() - qnames = list() - for i, qidx in enumerate(quads_bba_idx): - name = model[qidx].fam_name - idc = fam_data[name]["index"].index([qidx]) - sub = fam_data[name]["subsection"][idc] - inst = fam_data[name]["instance"][idc] - name = "QS" if name.startswith(("S", "F")) else name - qname = "SI-{0:s}:PS-{1:s}-{2:s}".format(sub, name, inst) - qnames.append(qname.strip("-")) - - sub = fam_data["BPM"]["subsection"][i] - inst = fam_data["BPM"]["instance"][i] - bname = "SI-{0:s}:DI-BPM-{1:s}".format(sub, inst) - bname = bname.strip("-") - bpmnames.append(bname.strip("-")) - return bpmnames, bpms_idx, qnames, quads_bba_idx - - @staticmethod - def list_bpm_subsections(bpms): - """.""" - return _DoBBA.list_bpm_subsections(bpms) - - # ##### Make Figures ##### - #! empty - # #### pbba utils ##### def get_group_delta_kl(self): bpms = self.get_active_bpmnames() @@ -554,7 +517,7 @@ def set_strengths(self, strengths, track_tune=False): raise ValueError('dim mismatch between the active group and \"strengths\".') quad_names = self.data["quadnames"] bpm_names = self.data["bpmnames"] - tunes = [] + if track_tune: tunes = [] for _o in order: bpmname = bpms[_o] quadname = quad_names[bpm_names.index(bpmname)] @@ -566,9 +529,8 @@ def set_strengths(self, strengths, track_tune=False): return quad.strength = strengths[_o] _time.sleep(self.params.wait_quadrupole) - tunes.append(self.get_tunes()) - if track_tune: - return tunes + if track_tune: tunes.append(self.get_tunes()) + if track_tune: return tunes return def get_strengths(self): @@ -602,11 +564,63 @@ def meas_ios(self, track_tune=False): return orb_pos - orb_neg, tune_variation return orb_pos - orb_neg, None - def get_ios_jacobian(self, model=None, fam_data=None): + @staticmethod + def get_default_quads(model, fam_data): + """.""" + quads_idx = _dcopy(fam_data["QN"]["index"]) + qs_idx = [idx for idx in fam_data["QS"]["index"]] + quads_idx.extend(qs_idx) + quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) + quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) + + bpms_idx = _np.array([idx[0] for idx in fam_data["BPM"]["index"]]) + bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) + + diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) + bba_idx = _np.argmin(diff, axis=1) + quads_bba_idx = quads_idx[bba_idx] + bpmnames = list() + qnames = list() + for i, qidx in enumerate(quads_bba_idx): + name = model[qidx].fam_name + idc = fam_data[name]["index"].index([qidx]) + sub = fam_data[name]["subsection"][idc] + inst = fam_data[name]["instance"][idc] + name = "QS" if name.startswith(("S", "F")) else name + qname = "SI-{0:s}:PS-{1:s}-{2:s}".format(sub, name, inst) + qnames.append(qname.strip("-")) + + sub = fam_data["BPM"]["subsection"][i] + inst = fam_data["BPM"]["instance"][i] + bname = "SI-{0:s}:DI-BPM-{1:s}".format(sub, inst) + bname = bname.strip("-") + bpmnames.append(bname.strip("-")) + return bpmnames, bpms_idx, qnames, quads_bba_idx + + @property + def model(self): + return self._model + + @model.setter + def model(self, value): + self._model = value + self._famdata = _pymodels.si.families.get_family_data(self._model) + + @property + def fam_data(self): + return self._famdata + + @fam_data.setter + def fam_data(self, value): + raise ValueError('\n Can\'t set fam_data manually, try setting a model') + + def get_ios_jacobian(self): + model = self._model if model is None: + print("\n undefined model... setting a default one") model = _pymodels.si.fitted_models.vertical_dispersion_and_coupling(_pymodels.si.create_accelerator()) - if fam_data is None: - fam_data = _pymodels.si.families.get_family_data(model) + self.model = model + fam_data = self._famdata use6d = any([model.cavity_on, model.radiation_on>0]) _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) bpms = self.get_active_bpmnames() @@ -650,13 +664,13 @@ def _set_strengths(strengths): return jac_pos - jac_neg @staticmethod - def inverse_matrix(matrix, rcond=1e-8): + def inverse_matrix(matrix, rcond=1e-5): return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) def correct_ios(self, jacobian=None, track_tune=False): if jacobian is None: jacobian = self.get_ios_jacobian() - inverse_jacobian = self.inverse_matrix(jacobian, self.params.ijac_tol) + inverse_jacobian = self.inverse_matrix(jacobian, self.params.inv_jac_rcond) ios_iter, tune_iter, dkicks_iter = [], [], [] kicks_ini = self.get_kicks() nr_iters = self.params.corr_nr_iters @@ -734,12 +748,10 @@ def _dopbba_single_group(self, group_id): tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime("%Hh%Mm%Ss") - print("{:s} --> Doing PBBA for Group {:d}".format(strtini)) - - sofb = self.devices["sofb"] + print("{:s} --> Doing PBBA for Group {:d}".format(strtini, group_id)) - ios_jac = self.get_ios_jacobian() - self.correct_ios(ios_jac) + ios_jac = self.get_ios_jacobian(self._model, self._famdata) + self.correct_ios(ios_jac, self.params.dotrack_tune) orbit = self.get_orbit() for bpm in bpmnames: idx = bpmnames_all.index(bpm) From e4d9f453e126d10dfc6ce21abe594a7ed5c93e19 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 24 Sep 2025 14:00:34 -0300 Subject: [PATCH 09/61] save new --- apsuite/commisslib/measure_pbba.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 9399daf0a..4abbfcaaf 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -756,9 +756,10 @@ def _dopbba_single_group(self, group_id): for bpm in bpmnames: idx = bpmnames_all.index(bpm) self.data['measure'][bpm] = {'x0':orbit[idx], 'y0':orbit[idx+nbpms]} + self.data["scancenterx"][idx] = self.data['measure'][bpm]["x0"] + self.data["scancentery"][idx] = self.data['measure'][bpm]["y0"] - print("restoring initial conditions.") - + # print("restoring initial conditions.") #! -> automatically restored before each group tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) From dbe3f2f157dae799c67625256409e701dad5cf26 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 24 Sep 2025 15:45:45 -0300 Subject: [PATCH 10/61] COMMISS.PBBA.MNT: document formatting. --- apsuite/commisslib/measure_pbba.py | 889 +++++++++++++++-------------- 1 file changed, 467 insertions(+), 422 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 4abbfcaaf..8b0e742cd 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -4,349 +4,351 @@ import time as _time from copy import deepcopy as _dcopy -import matplotlib.cm as _cmap -import matplotlib.gridspec as _mpl_gs -import matplotlib.pyplot as _plt import numpy as _np import pyaccel as _pyacc import pymodels as _pymodels -from scipy.optimize import least_squares as _least_squares from siriuspy.clientconfigdb import ConfigDBClient -from siriuspy.devices import CurrInfoSI as _CurrInfoSI, \ - PowerSupply as _PowerSupply, SOFB as _SOFB, Tune as _Tune +from siriuspy.devices import ( + CurrInfoSI as _CurrInfoSI, + PowerSupply as _PowerSupply, + SOFB as _SOFB, + Tune as _Tune +) from siriuspy.namesys import SiriusPVName as _PVName -from ..utils import ParamsBaseClass as _ParamsBaseClass, \ - ThreadedMeasBaseClass as _BaseClass -from .measure_bba import DoBBA as _DoBBA from ..orbcorr.orbit_correction import OrbitCorr as _OrbitCorr +from ..utils import ( + ParamsBaseClass as _ParamsBaseClass, + ThreadedMeasBaseClass as _BaseClass +) + class ParallelBBAParams(_ParamsBaseClass): """.""" BPMNAMES = ( - "SI-01M2:DI-BPM", - "SI-01C1:DI-BPM-1", - "SI-01C1:DI-BPM-2", - "SI-01C2:DI-BPM", - "SI-01C3:DI-BPM-1", - "SI-01C3:DI-BPM-2", - "SI-01C4:DI-BPM", - "SI-02M1:DI-BPM", - "SI-02M2:DI-BPM", - "SI-02C1:DI-BPM-1", - "SI-02C1:DI-BPM-2", - "SI-02C2:DI-BPM", - "SI-02C3:DI-BPM-1", - "SI-02C3:DI-BPM-2", - "SI-02C4:DI-BPM", - "SI-03M1:DI-BPM", - "SI-03M2:DI-BPM", - "SI-03C1:DI-BPM-1", - "SI-03C1:DI-BPM-2", - "SI-03C2:DI-BPM", - "SI-03C3:DI-BPM-1", - "SI-03C3:DI-BPM-2", - "SI-03C4:DI-BPM", - "SI-04M1:DI-BPM", - "SI-04M2:DI-BPM", - "SI-04C1:DI-BPM-1", - "SI-04C1:DI-BPM-2", - "SI-04C2:DI-BPM", - "SI-04C3:DI-BPM-1", - "SI-04C3:DI-BPM-2", - "SI-04C4:DI-BPM", - "SI-05M1:DI-BPM", - "SI-05M2:DI-BPM", - "SI-05C1:DI-BPM-1", - "SI-05C1:DI-BPM-2", - "SI-05C2:DI-BPM", - "SI-05C3:DI-BPM-1", - "SI-05C3:DI-BPM-2", - "SI-05C4:DI-BPM", - "SI-06M1:DI-BPM", - "SI-06M2:DI-BPM", - "SI-06C1:DI-BPM-1", - "SI-06C1:DI-BPM-2", - "SI-06C2:DI-BPM", - "SI-06C3:DI-BPM-1", - "SI-06C3:DI-BPM-2", - "SI-06C4:DI-BPM", - "SI-07M1:DI-BPM", - "SI-07M2:DI-BPM", - "SI-07C1:DI-BPM-1", - "SI-07C1:DI-BPM-2", - "SI-07C2:DI-BPM", - "SI-07C3:DI-BPM-1", - "SI-07C3:DI-BPM-2", - "SI-07C4:DI-BPM", - "SI-08M1:DI-BPM", - "SI-08M2:DI-BPM", - "SI-08C1:DI-BPM-1", - "SI-08C1:DI-BPM-2", - "SI-08C2:DI-BPM", - "SI-08C3:DI-BPM-1", - "SI-08C3:DI-BPM-2", - "SI-08C4:DI-BPM", - "SI-09M1:DI-BPM", - "SI-09M2:DI-BPM", - "SI-09C1:DI-BPM-1", - "SI-09C1:DI-BPM-2", - "SI-09C2:DI-BPM", - "SI-09C3:DI-BPM-1", - "SI-09C3:DI-BPM-2", - "SI-09C4:DI-BPM", - "SI-10M1:DI-BPM", - "SI-10M2:DI-BPM", - "SI-10C1:DI-BPM-1", - "SI-10C1:DI-BPM-2", - "SI-10C2:DI-BPM", - "SI-10C3:DI-BPM-1", - "SI-10C3:DI-BPM-2", - "SI-10C4:DI-BPM", - "SI-11M1:DI-BPM", - "SI-11M2:DI-BPM", - "SI-11C1:DI-BPM-1", - "SI-11C1:DI-BPM-2", - "SI-11C2:DI-BPM", - "SI-11C3:DI-BPM-1", - "SI-11C3:DI-BPM-2", - "SI-11C4:DI-BPM", - "SI-12M1:DI-BPM", - "SI-12M2:DI-BPM", - "SI-12C1:DI-BPM-1", - "SI-12C1:DI-BPM-2", - "SI-12C2:DI-BPM", - "SI-12C3:DI-BPM-1", - "SI-12C3:DI-BPM-2", - "SI-12C4:DI-BPM", - "SI-13M1:DI-BPM", - "SI-13M2:DI-BPM", - "SI-13C1:DI-BPM-1", - "SI-13C1:DI-BPM-2", - "SI-13C2:DI-BPM", - "SI-13C3:DI-BPM-1", - "SI-13C3:DI-BPM-2", - "SI-13C4:DI-BPM", - "SI-14M1:DI-BPM", - "SI-14M2:DI-BPM", - "SI-14C1:DI-BPM-1", - "SI-14C1:DI-BPM-2", - "SI-14C2:DI-BPM", - "SI-14C3:DI-BPM-1", - "SI-14C3:DI-BPM-2", - "SI-14C4:DI-BPM", - "SI-15M1:DI-BPM", - "SI-15M2:DI-BPM", - "SI-15C1:DI-BPM-1", - "SI-15C1:DI-BPM-2", - "SI-15C2:DI-BPM", - "SI-15C3:DI-BPM-1", - "SI-15C3:DI-BPM-2", - "SI-15C4:DI-BPM", - "SI-16M1:DI-BPM", - "SI-16M2:DI-BPM", - "SI-16C1:DI-BPM-1", - "SI-16C1:DI-BPM-2", - "SI-16C2:DI-BPM", - "SI-16C3:DI-BPM-1", - "SI-16C3:DI-BPM-2", - "SI-16C4:DI-BPM", - "SI-17M1:DI-BPM", - "SI-17M2:DI-BPM", - "SI-17C1:DI-BPM-1", - "SI-17C1:DI-BPM-2", - "SI-17C2:DI-BPM", - "SI-17C3:DI-BPM-1", - "SI-17C3:DI-BPM-2", - "SI-17C4:DI-BPM", - "SI-18M1:DI-BPM", - "SI-18M2:DI-BPM", - "SI-18C1:DI-BPM-1", - "SI-18C1:DI-BPM-2", - "SI-18C2:DI-BPM", - "SI-18C3:DI-BPM-1", - "SI-18C3:DI-BPM-2", - "SI-18C4:DI-BPM", - "SI-19M1:DI-BPM", - "SI-19M2:DI-BPM", - "SI-19C1:DI-BPM-1", - "SI-19C1:DI-BPM-2", - "SI-19C2:DI-BPM", - "SI-19C3:DI-BPM-1", - "SI-19C3:DI-BPM-2", - "SI-19C4:DI-BPM", - "SI-20M1:DI-BPM", - "SI-20M2:DI-BPM", - "SI-20C1:DI-BPM-1", - "SI-20C1:DI-BPM-2", - "SI-20C2:DI-BPM", - "SI-20C3:DI-BPM-1", - "SI-20C3:DI-BPM-2", - "SI-20C4:DI-BPM", - "SI-01M1:DI-BPM", + 'SI-01M2:DI-BPM', + 'SI-01C1:DI-BPM-1', + 'SI-01C1:DI-BPM-2', + 'SI-01C2:DI-BPM', + 'SI-01C3:DI-BPM-1', + 'SI-01C3:DI-BPM-2', + 'SI-01C4:DI-BPM', + 'SI-02M1:DI-BPM', + 'SI-02M2:DI-BPM', + 'SI-02C1:DI-BPM-1', + 'SI-02C1:DI-BPM-2', + 'SI-02C2:DI-BPM', + 'SI-02C3:DI-BPM-1', + 'SI-02C3:DI-BPM-2', + 'SI-02C4:DI-BPM', + 'SI-03M1:DI-BPM', + 'SI-03M2:DI-BPM', + 'SI-03C1:DI-BPM-1', + 'SI-03C1:DI-BPM-2', + 'SI-03C2:DI-BPM', + 'SI-03C3:DI-BPM-1', + 'SI-03C3:DI-BPM-2', + 'SI-03C4:DI-BPM', + 'SI-04M1:DI-BPM', + 'SI-04M2:DI-BPM', + 'SI-04C1:DI-BPM-1', + 'SI-04C1:DI-BPM-2', + 'SI-04C2:DI-BPM', + 'SI-04C3:DI-BPM-1', + 'SI-04C3:DI-BPM-2', + 'SI-04C4:DI-BPM', + 'SI-05M1:DI-BPM', + 'SI-05M2:DI-BPM', + 'SI-05C1:DI-BPM-1', + 'SI-05C1:DI-BPM-2', + 'SI-05C2:DI-BPM', + 'SI-05C3:DI-BPM-1', + 'SI-05C3:DI-BPM-2', + 'SI-05C4:DI-BPM', + 'SI-06M1:DI-BPM', + 'SI-06M2:DI-BPM', + 'SI-06C1:DI-BPM-1', + 'SI-06C1:DI-BPM-2', + 'SI-06C2:DI-BPM', + 'SI-06C3:DI-BPM-1', + 'SI-06C3:DI-BPM-2', + 'SI-06C4:DI-BPM', + 'SI-07M1:DI-BPM', + 'SI-07M2:DI-BPM', + 'SI-07C1:DI-BPM-1', + 'SI-07C1:DI-BPM-2', + 'SI-07C2:DI-BPM', + 'SI-07C3:DI-BPM-1', + 'SI-07C3:DI-BPM-2', + 'SI-07C4:DI-BPM', + 'SI-08M1:DI-BPM', + 'SI-08M2:DI-BPM', + 'SI-08C1:DI-BPM-1', + 'SI-08C1:DI-BPM-2', + 'SI-08C2:DI-BPM', + 'SI-08C3:DI-BPM-1', + 'SI-08C3:DI-BPM-2', + 'SI-08C4:DI-BPM', + 'SI-09M1:DI-BPM', + 'SI-09M2:DI-BPM', + 'SI-09C1:DI-BPM-1', + 'SI-09C1:DI-BPM-2', + 'SI-09C2:DI-BPM', + 'SI-09C3:DI-BPM-1', + 'SI-09C3:DI-BPM-2', + 'SI-09C4:DI-BPM', + 'SI-10M1:DI-BPM', + 'SI-10M2:DI-BPM', + 'SI-10C1:DI-BPM-1', + 'SI-10C1:DI-BPM-2', + 'SI-10C2:DI-BPM', + 'SI-10C3:DI-BPM-1', + 'SI-10C3:DI-BPM-2', + 'SI-10C4:DI-BPM', + 'SI-11M1:DI-BPM', + 'SI-11M2:DI-BPM', + 'SI-11C1:DI-BPM-1', + 'SI-11C1:DI-BPM-2', + 'SI-11C2:DI-BPM', + 'SI-11C3:DI-BPM-1', + 'SI-11C3:DI-BPM-2', + 'SI-11C4:DI-BPM', + 'SI-12M1:DI-BPM', + 'SI-12M2:DI-BPM', + 'SI-12C1:DI-BPM-1', + 'SI-12C1:DI-BPM-2', + 'SI-12C2:DI-BPM', + 'SI-12C3:DI-BPM-1', + 'SI-12C3:DI-BPM-2', + 'SI-12C4:DI-BPM', + 'SI-13M1:DI-BPM', + 'SI-13M2:DI-BPM', + 'SI-13C1:DI-BPM-1', + 'SI-13C1:DI-BPM-2', + 'SI-13C2:DI-BPM', + 'SI-13C3:DI-BPM-1', + 'SI-13C3:DI-BPM-2', + 'SI-13C4:DI-BPM', + 'SI-14M1:DI-BPM', + 'SI-14M2:DI-BPM', + 'SI-14C1:DI-BPM-1', + 'SI-14C1:DI-BPM-2', + 'SI-14C2:DI-BPM', + 'SI-14C3:DI-BPM-1', + 'SI-14C3:DI-BPM-2', + 'SI-14C4:DI-BPM', + 'SI-15M1:DI-BPM', + 'SI-15M2:DI-BPM', + 'SI-15C1:DI-BPM-1', + 'SI-15C1:DI-BPM-2', + 'SI-15C2:DI-BPM', + 'SI-15C3:DI-BPM-1', + 'SI-15C3:DI-BPM-2', + 'SI-15C4:DI-BPM', + 'SI-16M1:DI-BPM', + 'SI-16M2:DI-BPM', + 'SI-16C1:DI-BPM-1', + 'SI-16C1:DI-BPM-2', + 'SI-16C2:DI-BPM', + 'SI-16C3:DI-BPM-1', + 'SI-16C3:DI-BPM-2', + 'SI-16C4:DI-BPM', + 'SI-17M1:DI-BPM', + 'SI-17M2:DI-BPM', + 'SI-17C1:DI-BPM-1', + 'SI-17C1:DI-BPM-2', + 'SI-17C2:DI-BPM', + 'SI-17C3:DI-BPM-1', + 'SI-17C3:DI-BPM-2', + 'SI-17C4:DI-BPM', + 'SI-18M1:DI-BPM', + 'SI-18M2:DI-BPM', + 'SI-18C1:DI-BPM-1', + 'SI-18C1:DI-BPM-2', + 'SI-18C2:DI-BPM', + 'SI-18C3:DI-BPM-1', + 'SI-18C3:DI-BPM-2', + 'SI-18C4:DI-BPM', + 'SI-19M1:DI-BPM', + 'SI-19M2:DI-BPM', + 'SI-19C1:DI-BPM-1', + 'SI-19C1:DI-BPM-2', + 'SI-19C2:DI-BPM', + 'SI-19C3:DI-BPM-1', + 'SI-19C3:DI-BPM-2', + 'SI-19C4:DI-BPM', + 'SI-20M1:DI-BPM', + 'SI-20M2:DI-BPM', + 'SI-20C1:DI-BPM-1', + 'SI-20C1:DI-BPM-2', + 'SI-20C2:DI-BPM', + 'SI-20C3:DI-BPM-1', + 'SI-20C3:DI-BPM-2', + 'SI-20C4:DI-BPM', + 'SI-01M1:DI-BPM', ) QUADNAMES = ( - "SI-01M2:PS-QS", - "SI-01C1:PS-Q1", - "SI-01C1:PS-QS", - "SI-01C2:PS-QS", - "SI-01C3:PS-Q4", - "SI-01C3:PS-QS", - "SI-01C4:PS-Q1", - "SI-02M1:PS-QDB2", - "SI-02M2:PS-QDB2", - "SI-02C1:PS-Q1", - "SI-02C1:PS-QS", - "SI-02C2:PS-QS", - "SI-02C3:PS-Q4", - "SI-02C3:PS-QS", - "SI-02C4:PS-Q1", - "SI-03M1:PS-QDP2", - "SI-03M2:PS-QDP2", - "SI-03C1:PS-Q1", - "SI-03C1:PS-QS", - "SI-03C2:PS-QS", - "SI-03C3:PS-Q4", - "SI-03C3:PS-QS", - "SI-03C4:PS-Q1", - "SI-04M1:PS-QDB2", - "SI-04M2:PS-QDB2", - "SI-04C1:PS-Q1", - "SI-04C1:PS-QS", - "SI-04C2:PS-QS", - "SI-04C3:PS-Q4", - "SI-04C3:PS-QS", - "SI-04C4:PS-Q1", - "SI-05M1:PS-QS", - "SI-05M2:PS-QS", - "SI-05C1:PS-Q1", - "SI-05C1:PS-QS", - "SI-05C2:PS-QS", - "SI-05C3:PS-Q4", - "SI-05C3:PS-QS", - "SI-05C4:PS-Q1", - "SI-06M1:PS-QDB2", - "SI-06M2:PS-QDB2", - "SI-06C1:PS-Q1", - "SI-06C1:PS-QS", - "SI-06C2:PS-QS", - "SI-06C3:PS-Q4", - "SI-06C3:PS-QS", - "SI-06C4:PS-Q1", - "SI-07M1:PS-QDP2", - "SI-07M2:PS-QDP2", - "SI-07C1:PS-Q1", - "SI-07C1:PS-QS", - "SI-07C2:PS-QS", - "SI-07C3:PS-Q4", - "SI-07C3:PS-QS", - "SI-07C4:PS-Q1", - "SI-08M1:PS-QDB2", - "SI-08M2:PS-QDB2", - "SI-08C1:PS-Q1", - "SI-08C1:PS-QS", - "SI-08C2:PS-QS", - "SI-08C3:PS-Q4", - "SI-08C3:PS-QS", - "SI-08C4:PS-Q1", - "SI-09M1:PS-QS", - "SI-09M2:PS-QS", - "SI-09C1:PS-Q1", - "SI-09C1:PS-QS", - "SI-09C2:PS-QS", - "SI-09C3:PS-Q4", - "SI-09C3:PS-QS", - "SI-09C4:PS-Q1", - "SI-10M1:PS-QDB2", - "SI-10M2:PS-QDB2", - "SI-10C1:PS-Q1", - "SI-10C1:PS-QS", - "SI-10C2:PS-QS", - "SI-10C3:PS-Q4", - "SI-10C3:PS-QS", - "SI-10C4:PS-Q1", - "SI-11M1:PS-QDP2", - "SI-11M2:PS-QDP2", - "SI-11C1:PS-Q1", - "SI-11C1:PS-QS", - "SI-11C2:PS-QS", - "SI-11C3:PS-Q4", - "SI-11C3:PS-QS", - "SI-11C4:PS-Q1", - "SI-12M1:PS-QDB2", - "SI-12M2:PS-QDB2", - "SI-12C1:PS-Q1", - "SI-12C1:PS-QS", - "SI-12C2:PS-QS", - "SI-12C3:PS-Q4", - "SI-12C3:PS-QS", - "SI-12C4:PS-Q1", - "SI-13M1:PS-QS", - "SI-13M2:PS-QS", - "SI-13C1:PS-Q1", - "SI-13C1:PS-QS", - "SI-13C2:PS-QS", - "SI-13C3:PS-Q4", - "SI-13C3:PS-QS", - "SI-13C4:PS-Q1", - "SI-14M1:PS-QDB2", - "SI-14M2:PS-QDB2", - "SI-14C1:PS-Q1", - "SI-14C1:PS-QS", - "SI-14C2:PS-QS", - "SI-14C3:PS-Q4", - "SI-14C3:PS-QS", - "SI-14C4:PS-Q1", - "SI-15M1:PS-QDP2", - "SI-15M2:PS-QDP2", - "SI-15C1:PS-Q1", - "SI-15C1:PS-QS", - "SI-15C2:PS-QS", - "SI-15C3:PS-Q4", - "SI-15C3:PS-QS", - "SI-15C4:PS-Q1", - "SI-16M1:PS-QDB2", - "SI-16M2:PS-QDB2", - "SI-16C1:PS-Q1", - "SI-16C1:PS-QS", - "SI-16C2:PS-QS", - "SI-16C3:PS-Q4", - "SI-16C3:PS-QS", - "SI-16C4:PS-Q1", - "SI-17M1:PS-QS", - "SI-17M2:PS-QS", - "SI-17C1:PS-Q1", - "SI-17C1:PS-QS", - "SI-17C2:PS-QS", - "SI-17C3:PS-Q4", - "SI-17C3:PS-QS", - "SI-17C4:PS-Q1", - "SI-18M1:PS-QDB2", - "SI-18M2:PS-QDB2", - "SI-18C1:PS-Q1", - "SI-18C1:PS-QS", - "SI-18C2:PS-QS", - "SI-18C3:PS-Q4", - "SI-18C3:PS-QS", - "SI-18C4:PS-Q1", - "SI-19M1:PS-QDP2", - "SI-19M2:PS-QDP2", - "SI-19C1:PS-Q1", - "SI-19C1:PS-QS", - "SI-19C2:PS-QS", - "SI-19C3:PS-Q4", - "SI-19C3:PS-QS", - "SI-19C4:PS-Q1", - "SI-20M1:PS-QDB2", - "SI-20M2:PS-QDB2", - "SI-20C1:PS-Q1", - "SI-20C1:PS-QS", - "SI-20C2:PS-QS", - "SI-20C3:PS-Q4", - "SI-20C3:PS-QS", - "SI-20C4:PS-Q1", - "SI-01M1:PS-QS", + 'SI-01M2:PS-QS', + 'SI-01C1:PS-Q1', + 'SI-01C1:PS-QS', + 'SI-01C2:PS-QS', + 'SI-01C3:PS-Q4', + 'SI-01C3:PS-QS', + 'SI-01C4:PS-Q1', + 'SI-02M1:PS-QDB2', + 'SI-02M2:PS-QDB2', + 'SI-02C1:PS-Q1', + 'SI-02C1:PS-QS', + 'SI-02C2:PS-QS', + 'SI-02C3:PS-Q4', + 'SI-02C3:PS-QS', + 'SI-02C4:PS-Q1', + 'SI-03M1:PS-QDP2', + 'SI-03M2:PS-QDP2', + 'SI-03C1:PS-Q1', + 'SI-03C1:PS-QS', + 'SI-03C2:PS-QS', + 'SI-03C3:PS-Q4', + 'SI-03C3:PS-QS', + 'SI-03C4:PS-Q1', + 'SI-04M1:PS-QDB2', + 'SI-04M2:PS-QDB2', + 'SI-04C1:PS-Q1', + 'SI-04C1:PS-QS', + 'SI-04C2:PS-QS', + 'SI-04C3:PS-Q4', + 'SI-04C3:PS-QS', + 'SI-04C4:PS-Q1', + 'SI-05M1:PS-QS', + 'SI-05M2:PS-QS', + 'SI-05C1:PS-Q1', + 'SI-05C1:PS-QS', + 'SI-05C2:PS-QS', + 'SI-05C3:PS-Q4', + 'SI-05C3:PS-QS', + 'SI-05C4:PS-Q1', + 'SI-06M1:PS-QDB2', + 'SI-06M2:PS-QDB2', + 'SI-06C1:PS-Q1', + 'SI-06C1:PS-QS', + 'SI-06C2:PS-QS', + 'SI-06C3:PS-Q4', + 'SI-06C3:PS-QS', + 'SI-06C4:PS-Q1', + 'SI-07M1:PS-QDP2', + 'SI-07M2:PS-QDP2', + 'SI-07C1:PS-Q1', + 'SI-07C1:PS-QS', + 'SI-07C2:PS-QS', + 'SI-07C3:PS-Q4', + 'SI-07C3:PS-QS', + 'SI-07C4:PS-Q1', + 'SI-08M1:PS-QDB2', + 'SI-08M2:PS-QDB2', + 'SI-08C1:PS-Q1', + 'SI-08C1:PS-QS', + 'SI-08C2:PS-QS', + 'SI-08C3:PS-Q4', + 'SI-08C3:PS-QS', + 'SI-08C4:PS-Q1', + 'SI-09M1:PS-QS', + 'SI-09M2:PS-QS', + 'SI-09C1:PS-Q1', + 'SI-09C1:PS-QS', + 'SI-09C2:PS-QS', + 'SI-09C3:PS-Q4', + 'SI-09C3:PS-QS', + 'SI-09C4:PS-Q1', + 'SI-10M1:PS-QDB2', + 'SI-10M2:PS-QDB2', + 'SI-10C1:PS-Q1', + 'SI-10C1:PS-QS', + 'SI-10C2:PS-QS', + 'SI-10C3:PS-Q4', + 'SI-10C3:PS-QS', + 'SI-10C4:PS-Q1', + 'SI-11M1:PS-QDP2', + 'SI-11M2:PS-QDP2', + 'SI-11C1:PS-Q1', + 'SI-11C1:PS-QS', + 'SI-11C2:PS-QS', + 'SI-11C3:PS-Q4', + 'SI-11C3:PS-QS', + 'SI-11C4:PS-Q1', + 'SI-12M1:PS-QDB2', + 'SI-12M2:PS-QDB2', + 'SI-12C1:PS-Q1', + 'SI-12C1:PS-QS', + 'SI-12C2:PS-QS', + 'SI-12C3:PS-Q4', + 'SI-12C3:PS-QS', + 'SI-12C4:PS-Q1', + 'SI-13M1:PS-QS', + 'SI-13M2:PS-QS', + 'SI-13C1:PS-Q1', + 'SI-13C1:PS-QS', + 'SI-13C2:PS-QS', + 'SI-13C3:PS-Q4', + 'SI-13C3:PS-QS', + 'SI-13C4:PS-Q1', + 'SI-14M1:PS-QDB2', + 'SI-14M2:PS-QDB2', + 'SI-14C1:PS-Q1', + 'SI-14C1:PS-QS', + 'SI-14C2:PS-QS', + 'SI-14C3:PS-Q4', + 'SI-14C3:PS-QS', + 'SI-14C4:PS-Q1', + 'SI-15M1:PS-QDP2', + 'SI-15M2:PS-QDP2', + 'SI-15C1:PS-Q1', + 'SI-15C1:PS-QS', + 'SI-15C2:PS-QS', + 'SI-15C3:PS-Q4', + 'SI-15C3:PS-QS', + 'SI-15C4:PS-Q1', + 'SI-16M1:PS-QDB2', + 'SI-16M2:PS-QDB2', + 'SI-16C1:PS-Q1', + 'SI-16C1:PS-QS', + 'SI-16C2:PS-QS', + 'SI-16C3:PS-Q4', + 'SI-16C3:PS-QS', + 'SI-16C4:PS-Q1', + 'SI-17M1:PS-QS', + 'SI-17M2:PS-QS', + 'SI-17C1:PS-Q1', + 'SI-17C1:PS-QS', + 'SI-17C2:PS-QS', + 'SI-17C3:PS-Q4', + 'SI-17C3:PS-QS', + 'SI-17C4:PS-Q1', + 'SI-18M1:PS-QDB2', + 'SI-18M2:PS-QDB2', + 'SI-18C1:PS-Q1', + 'SI-18C1:PS-QS', + 'SI-18C2:PS-QS', + 'SI-18C3:PS-Q4', + 'SI-18C3:PS-QS', + 'SI-18C4:PS-Q1', + 'SI-19M1:PS-QDP2', + 'SI-19M2:PS-QDP2', + 'SI-19C1:PS-Q1', + 'SI-19C1:PS-QS', + 'SI-19C2:PS-QS', + 'SI-19C3:PS-Q4', + 'SI-19C3:PS-QS', + 'SI-19C4:PS-Q1', + 'SI-20M1:PS-QDB2', + 'SI-20M2:PS-QDB2', + 'SI-20C1:PS-Q1', + 'SI-20C1:PS-QS', + 'SI-20C2:PS-QS', + 'SI-20C3:PS-Q4', + 'SI-20C3:PS-QS', + 'SI-20C4:PS-Q1', + 'SI-01M1:PS-QS', ) BPMNAMES = tuple([_PVName(bpm) for bpm in BPMNAMES]) @@ -373,7 +375,7 @@ def __init__(self): def __str__(self): """.""" - return "..." + return '...' class DoParallelBBA(_BaseClass): @@ -386,35 +388,35 @@ def __init__(self, isonline=True): ) self._groups2dopbba = list() self._activegroup_id = None - self.clt_confdb = ConfigDBClient(config_type="si_bbadata") + self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 - self.data["bpmnames"] = list(ParallelBBAParams.BPMNAMES) - self.data["quadnames"] = list(ParallelBBAParams.QUADNAMES) - self.data["scancenterx"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) - self.data["scancentery"] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) - self.data["measure"] = dict() + self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) + self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) + self.data['scancenterx'] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) + self.data['scancentery'] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) + self.data['measure'] = dict() self._model = None self._famdata = None if self.isonline: - self.devices["tune"] = _Tune(_Tune.DEVICES.SI) - self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) - self.devices["currinfosi"] = _CurrInfoSI() + self.devices['tune'] = _Tune(_Tune.DEVICES.SI) + self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) + self.devices['currinfosi'] = _CurrInfoSI() def __str__(self): """.""" - return "..." + return '...' @property def havebeam(self): """.""" - haveb = self.devices["currinfosi"] + haveb = self.devices['currinfosi'] return haveb.connected and haveb.storedbeam @property def measuredbpms(self): """.""" - return sorted(self.data["measure"]) + return sorted(self.data['measure']) @property def groups2dopbba(self): @@ -422,7 +424,11 @@ def groups2dopbba(self): @groups2dopbba.setter def groups2dopbba(self, groups): - self._groups2dopbba = [[_PVName(bpm) for bpm in group if isinstance(bpm, str)] for group in groups if isinstance(group, (list, tuple, _np.ndarray))] + self._groups2dopbba = [ + [_PVName(bpm) for bpm in group if isinstance(bpm, str)] + for group in groups + if isinstance(group, (list, tuple, _np.ndarray)) + ] @property def active_group_id(self): @@ -439,30 +445,30 @@ def get_active_bpmnames(self): def connect_to_quadrupoles(self): """.""" for bpm in self.get_active_bpmnames(): - idx = self.data["bpmnames"].index(bpm) - qname = self.data["quadnames"][idx] + idx = self.data['bpmnames'].index(bpm) + qname = self.data['quadnames'][idx] if qname and qname not in self.devices: self.devices[qname] = _PowerSupply(qname) def get_active_quadnames(self): qnames = [] for bpm in self.get_active_bpmnames(): - idx = self.data["bpmnames"].index(bpm) - qname = self.data["quadnames"][idx] + idx = self.data['bpmnames'].index(bpm) + qname = self.data['quadnames'][idx] if qname: qnames.append(qname) return qnames def get_orbit(self): """.""" - sofb = self.devices["sofb"] + sofb = self.devices['sofb'] sofb.cmd_reset() sofb.wait_buffer(self.params.timeout_wait_orbit) return _np.hstack([sofb.orbx, sofb.orby]) def correct_orbit(self): """.""" - sofb = self.devices["sofb"] + sofb = self.devices['sofb'] sofb.correct_orbit_manually( nr_iters=self.params.sofb_maxcorriter, residue=self.params.sofb_maxorberr, @@ -473,17 +479,23 @@ def get_kicks(self): return _np.hstack(( list(sofb.kickch), list(sofb.kickcv), - list(sofb.kickrf)) - ) + list(sofb.kickrf), + )) def set_delta_kicks(self, dkicks): sofb = self.devices['sofb'] nch, ncv, nrf = sofb._data.nr_ch, sofb._data.nr_cv, 1 - if len(dkicks) != nch+ncv+nrf: - raise ValueError(f'invalid dim for dkicks, must have shape=({nch+ncv+nrf},)') + if len(dkicks) != nch + ncv + nrf: + raise ValueError( + f'invalid dim for dkicks, must have shape=({nch + ncv + nrf},)' + ) dch, dcv, drf = dkicks[:nch], dkicks[nch:ncv], dkicks[ncv:] - factch, factcv, factrf = sofb.mancorrgainch, sofb.mancorrgaincv, sofb.mancorrgainrf + factch, factcv, factrf = ( + sofb.mancorrgainch, + sofb.mancorrgaincv, + sofb.mancorrgainrf, + ) sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch, dcv, drf nrsteps = _np.ceil(max(_np.abs(dch).max(), _np.abs(dcv).max()) / 1.0) for i in range(int(nrsteps)): @@ -492,7 +504,11 @@ def set_delta_kicks(self, dkicks): sofb.mancorrgainrf = (i + 1) / nrsteps * 100 sofb.cmd_applycorr_all() _time.sleep(self.params.wait_correctors) - sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch * 0, dcv * 0, drf * 0 + sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = ( + dch * 0, + dcv * 0, + drf * 0, + ) sofb.mancorrgainch, sofb.mancorrgaincv = factch, factcv, factrf def get_tunes(self): @@ -502,7 +518,7 @@ def get_tunes(self): # #### pbba utils ##### def get_group_delta_kl(self): bpms = self.get_active_bpmnames() - delta_kl = _np.ones(len(bpms))*self.params.quad_deltakl + delta_kl = _np.ones(len(bpms)) * self.params.quad_deltakl delta_kl[1::2] *= -1 return delta_kl @@ -514,39 +530,44 @@ def set_strengths(self, strengths, track_tune=False): order = self.get_group_ordering() bpms = self.get_active_bpmnames() if len(strengths) != len(bpms): - raise ValueError('dim mismatch between the active group and \"strengths\".') - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - if track_tune: tunes = [] + raise ValueError( + 'dim mismatch between the active group and "strengths".' + ) + quad_names = self.data['quadnames'] + bpm_names = self.data['bpmnames'] + if track_tune: + tunes = [] for _o in order: bpmname = bpms[_o] quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad.pwrstate: - print("\n error: quadrupole " + quadname + " is Off.") + print('\n error: quadrupole ' + quadname + ' is Off.') self._stopevt.set() - print(" exiting...") + print(' exiting...') return quad.strength = strengths[_o] _time.sleep(self.params.wait_quadrupole) - if track_tune: tunes.append(self.get_tunes()) - if track_tune: return tunes + if track_tune: + tunes.append(self.get_tunes()) + if track_tune: + return tunes return def get_strengths(self): order = self.get_group_ordering() bpms = self.get_active_bpmnames() strengths = _np.zeros(len(bpms)) - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] + quad_names = self.data['quadnames'] + bpm_names = self.data['bpmnames'] for _o in order: bpmname = bpms[_o] quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad.pwrstate: - print("\n error: quadrupole " + quadname + " is Off.") + print('\n error: quadrupole ' + quadname + ' is Off.') self._stopevt.set() - print(" exiting...") + print(' exiting...') return strengths[_o] = quad.strength return strengths @@ -554,9 +575,11 @@ def get_strengths(self): def meas_ios(self, track_tune=False): delta_stren = self.get_group_delta_kl() strens_orig = self.get_strengths() - tune_up = self.set_strengths(strens_orig + delta_stren/2, track_tune) + tune_up = self.set_strengths(strens_orig + delta_stren / 2, track_tune) orb_pos = self.get_orbit() - tune_down = self.set_strengths(strens_orig + delta_stren/2, track_tune) + tune_down = self.set_strengths( + strens_orig + delta_stren / 2, track_tune + ) orb_neg = self.get_orbit() tune_back = self.set_strengths(strens_orig, track_tune) if track_tune: @@ -567,13 +590,13 @@ def meas_ios(self, track_tune=False): @staticmethod def get_default_quads(model, fam_data): """.""" - quads_idx = _dcopy(fam_data["QN"]["index"]) - qs_idx = [idx for idx in fam_data["QS"]["index"]] + quads_idx = _dcopy(fam_data['QN']['index']) + qs_idx = [idx for idx in fam_data['QS']['index']] quads_idx.extend(qs_idx) quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) - bpms_idx = _np.array([idx[0] for idx in fam_data["BPM"]["index"]]) + bpms_idx = _np.array([idx[0] for idx in fam_data['BPM']['index']]) bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) @@ -583,18 +606,18 @@ def get_default_quads(model, fam_data): qnames = list() for i, qidx in enumerate(quads_bba_idx): name = model[qidx].fam_name - idc = fam_data[name]["index"].index([qidx]) - sub = fam_data[name]["subsection"][idc] - inst = fam_data[name]["instance"][idc] - name = "QS" if name.startswith(("S", "F")) else name - qname = "SI-{0:s}:PS-{1:s}-{2:s}".format(sub, name, inst) - qnames.append(qname.strip("-")) - - sub = fam_data["BPM"]["subsection"][i] - inst = fam_data["BPM"]["instance"][i] - bname = "SI-{0:s}:DI-BPM-{1:s}".format(sub, inst) - bname = bname.strip("-") - bpmnames.append(bname.strip("-")) + idc = fam_data[name]['index'].index([qidx]) + sub = fam_data[name]['subsection'][idc] + inst = fam_data[name]['instance'][idc] + name = 'QS' if name.startswith(('S', 'F')) else name + qname = 'SI-{0:s}:PS-{1:s}-{2:s}'.format(sub, name, inst) + qnames.append(qname.strip('-')) + + sub = fam_data['BPM']['subsection'][i] + inst = fam_data['BPM']['instance'][i] + bname = 'SI-{0:s}:DI-BPM-{1:s}'.format(sub, inst) + bname = bname.strip('-') + bpmnames.append(bname.strip('-')) return bpmnames, bpms_idx, qnames, quads_bba_idx @property @@ -612,48 +635,62 @@ def fam_data(self): @fam_data.setter def fam_data(self, value): - raise ValueError('\n Can\'t set fam_data manually, try setting a model') + raise ValueError( + "\n Can't set fam_data manually, try setting a model" + ) def get_ios_jacobian(self): model = self._model if model is None: - print("\n undefined model... setting a default one") - model = _pymodels.si.fitted_models.vertical_dispersion_and_coupling(_pymodels.si.create_accelerator()) + print('\n undefined model... setting a default one') + model = ( + _pymodels.si.fitted_models.vertical_dispersion_and_coupling( + _pymodels.si.create_accelerator() + ) + ) self.model = model fam_data = self._famdata - use6d = any([model.cavity_on, model.radiation_on>0]) - _orbcorr = _OrbitCorr(model=model, acc='SI', corr_system='SOFB', use6dorb=use6d) + use6d = any([model.cavity_on, model.radiation_on > 0]) + _orbcorr = _OrbitCorr( + model=model, acc='SI', corr_system='SOFB', use6dorb=use6d + ) bpms = self.get_active_bpmnames() order = self.get_group_ordering() delta_stren = self.get_group_delta_kl() - bnames, _, qnames, quadindices = self.get_default_quads(model, fam_data) + bnames, _, qnames, quadindices = self.get_default_quads( + model, fam_data + ) + def _get_strengths(): strens = [] for _o in order: _id = bnames.index(bpms[_o]) qidx = quadindices[_id] - if "QS" in qnames[_id]: + if 'QS' in qnames[_id]: strens.append(model[qidx].KsL) else: strens.append(model[qidx].KL) return strens + strens_orig = _get_strengths() + def _set_strengths(strengths): for _o in order: _id = bnames.index(bpms[_o]) qidx = quadindices[_id] - if "QS" in qnames[_id]: + if 'QS' in qnames[_id]: model[qidx].KsL = strengths[_o] else: model[qidx].KL = strengths[_o] - _set_strengths(strens_orig + delta_stren/2) + + _set_strengths(strens_orig + delta_stren / 2) try: jac_pos = _orbcorr.get_jacobian_matrix() except Exception as E: _set_strengths(strens_orig) print(E) return - _set_strengths(strens_orig - delta_stren/2) + _set_strengths(strens_orig - delta_stren / 2) try: jac_neg = _orbcorr.get_jacobian_matrix() except Exception as E: @@ -665,20 +702,24 @@ def _set_strengths(strengths): @staticmethod def inverse_matrix(matrix, rcond=1e-5): - return _np.dot(_np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T) + return _np.dot( + _np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T + ) def correct_ios(self, jacobian=None, track_tune=False): if jacobian is None: jacobian = self.get_ios_jacobian() - inverse_jacobian = self.inverse_matrix(jacobian, self.params.inv_jac_rcond) + inverse_jacobian = self.inverse_matrix( + jacobian, self.params.inv_jac_rcond + ) ios_iter, tune_iter, dkicks_iter = [], [], [] kicks_ini = self.get_kicks() nr_iters = self.params.corr_nr_iters - for i in range(nr_iters+1): + for i in range(nr_iters + 1): if self._stopevt.is_set() or not self.havebeam: - print(" exiting...") + print(' exiting...') break - print(" {0:02d}/{1:02d} --> ".format(i + 1, nr_iters), end="") + print(' {0:02d}/{1:02d} --> '.format(i + 1, nr_iters), end='') ios, tune = self.meas_ios(track_tune) ios_iter.append(ios) tune_iter.append(tune) @@ -690,53 +731,54 @@ def correct_ios(self, jacobian=None, track_tune=False): kicks_fim = self.get_kicks() group_name = f'group_{self.active_group_id:0d}' self.data[group_name] = { - 'bpms':self.groups2dopbba[self.active_group_id], + 'bpms': self.groups2dopbba[self.active_group_id], 'ios_iter': ios_iter, 'dkicks_iter': dkicks_iter, 'kicks_ini': kicks_ini, 'kicks_fim': kicks_fim, - 'ordering':self.get_group_ordering(), - 'delta_kl':self.get_group_delta_kl(), + 'ordering': self.get_group_ordering(), + 'delta_kl': self.get_group_delta_kl(), } - if track_tune: self.data[group_name]['tune_iter'] = tune_iter + if track_tune: + self.data[group_name]['tune_iter'] = tune_iter # #### private methods #### def _do_pbba(self): tini = _datetime.datetime.fromtimestamp(_time.time()) print( - "Starting measurement at {:s}".format( - tini.strftime("%Y-%m-%d %Hh%Mm%Ss") + 'Starting measurement at {:s}'.format( + tini.strftime('%Y-%m-%d %Hh%Mm%Ss') ) ) - sofb = self.devices["sofb"] + sofb = self.devices['sofb'] sofb.nr_points = self.params.sofb_nrpoints loop_on = False if sofb.autocorrsts: loop_on = True - print("SOFB feedback is enable, disabling it...") + print('SOFB feedback is enable, disabling it...') sofb.cmd_turn_off_autocorr() for i in range(len(self.groups2dopbba)): if self._stopevt.is_set(): - print("stopped!") + print('stopped!') break if not self.havebeam: - print("Beam was Lost") + print('Beam was Lost') break - print("\nCorrecting Orbit...", end="") + print('\nCorrecting Orbit...', end='') self.correct_orbit() - print("Ok!") + print('Ok!') self._dopbba_single_group(i) if loop_on: - print("SOFB feedback was enable, restoring original state...") + print('SOFB feedback was enable, restoring original state...') sofb.cmd_turn_on_autocorr() tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) - dtime = dtime.split(".")[0] - print("finished! Elapsed time {:s}".format(dtime)) + dtime = dtime.split('.')[0] + print('finished! Elapsed time {:s}'.format(dtime)) def _dopbba_single_group(self, group_id): """.""" @@ -747,21 +789,24 @@ def _dopbba_single_group(self, group_id): self.connect_to_quadrupoles() tini = _datetime.datetime.fromtimestamp(_time.time()) - strtini = tini.strftime("%Hh%Mm%Ss") - print("{:s} --> Doing PBBA for Group {:d}".format(strtini, group_id)) + strtini = tini.strftime('%Hh%Mm%Ss') + print('{:s} --> Doing PBBA for Group {:d}'.format(strtini, group_id)) ios_jac = self.get_ios_jacobian(self._model, self._famdata) self.correct_ios(ios_jac, self.params.dotrack_tune) orbit = self.get_orbit() for bpm in bpmnames: idx = bpmnames_all.index(bpm) - self.data['measure'][bpm] = {'x0':orbit[idx], 'y0':orbit[idx+nbpms]} - self.data["scancenterx"][idx] = self.data['measure'][bpm]["x0"] - self.data["scancentery"][idx] = self.data['measure'][bpm]["y0"] + self.data['measure'][bpm] = { + 'x0': orbit[idx], + 'y0': orbit[idx + nbpms], + } + self.data['scancenterx'][idx] = self.data['measure'][bpm]['x0'] + self.data['scancentery'][idx] = self.data['measure'][bpm]['y0'] # print("restoring initial conditions.") #! -> automatically restored before each group tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) - dtime = dtime.split(".")[0] - print("Done! Elapsed time: {:s}\n".format(dtime)) + dtime = dtime.split('.')[0] + print('Done! Elapsed time: {:s}\n'.format(dtime)) From ec5c6991ec59b92ed82d1b03d2af67e54ab2d95a Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 24 Sep 2025 16:41:05 -0300 Subject: [PATCH 11/61] COMMISS.PBBA.WIP: Suggestions. --- apsuite/commisslib/measure_pbba.py | 237 +++++++++++++---------------- 1 file changed, 102 insertions(+), 135 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 8b0e742cd..cb27fc273 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -6,7 +6,7 @@ import numpy as _np import pyaccel as _pyacc -import pymodels as _pymodels +from pymodels import si as _si from siriuspy.clientconfigdb import ConfigDBClient from siriuspy.devices import ( CurrInfoSI as _CurrInfoSI, @@ -358,7 +358,7 @@ def __init__(self): """.""" super().__init__() - self.quad_deltakl = 0.01 # [1/m] + self.quad_deltakl = 0.02 # [1/m] self.wait_correctors = 0.3 # [s] self.wait_quadrupole = 0.3 # [s] @@ -367,7 +367,7 @@ def __init__(self): self.corr_nr_iters = 6 self.inv_jac_rcond = 1e-5 - self.sofb_nrpoints = 10 + self.sofb_nrpoints = 20 self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] @@ -387,13 +387,10 @@ def __init__(self, isonline=True): params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) self._groups2dopbba = list() - self._activegroup_id = None self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) - self.data['scancenterx'] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) - self.data['scancentery'] = _np.zeros(len(ParallelBBAParams.BPMNAMES)) self.data['measure'] = dict() self._model = None self._famdata = None @@ -402,6 +399,7 @@ def __init__(self, isonline=True): self.devices['tune'] = _Tune(_Tune.DEVICES.SI) self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) self.devices['currinfosi'] = _CurrInfoSI() + self.connect_to_quadrupoles() def __str__(self): """.""" @@ -420,6 +418,7 @@ def measuredbpms(self): @property def groups2dopbba(self): + """.""" return _dcopy(self._groups2dopbba) @groups2dopbba.setter @@ -430,29 +429,18 @@ def groups2dopbba(self, groups): if isinstance(group, (list, tuple, _np.ndarray)) ] - @property - def active_group_id(self): - return self._activegroup_id - - @active_group_id.setter - def active_group_id(self, group_idx): - if group_idx < len(self._groups2dopbba): - self._activegroup_id = group_idx - - def get_active_bpmnames(self): - return self.groups2dopbba[self.active_group_id] - def connect_to_quadrupoles(self): """.""" - for bpm in self.get_active_bpmnames(): - idx = self.data['bpmnames'].index(bpm) - qname = self.data['quadnames'][idx] - if qname and qname not in self.devices: - self.devices[qname] = _PowerSupply(qname) + for qname in self.data['quadnames']: + if qname in self.devices: + continue + self.devices[qname] = _PowerSupply( + qname, props2init=('PwrState-Sts', 'KL-SP', 'KL-RB') + ) - def get_active_quadnames(self): + def get_active_quadnames(self, group_id): qnames = [] - for bpm in self.get_active_bpmnames(): + for bpm in self._groups2dopbba[group_id]: idx = self.data['bpmnames'].index(bpm) qname = self.data['quadnames'][idx] if qname: @@ -516,61 +504,46 @@ def get_tunes(self): return _np.array([tune.tunex, tune.tuney]) # #### pbba utils ##### - def get_group_delta_kl(self): - bpms = self.get_active_bpmnames() + def get_group_delta_kl(self, group_id): + bpms = self._groups2dopbba[group_id] delta_kl = _np.ones(len(bpms)) * self.params.quad_deltakl delta_kl[1::2] *= -1 return delta_kl - def get_group_ordering(self): - bpms = self.get_active_bpmnames() - return _np.arange(len(bpms)) + def get_group_ordering(self, group_id): + return _np.arange(len(self._groups2dopbba[group_id])) - def set_strengths(self, strengths, track_tune=False): - order = self.get_group_ordering() - bpms = self.get_active_bpmnames() - if len(strengths) != len(bpms): - raise ValueError( - 'dim mismatch between the active group and "strengths".' - ) + def set_strengths(self, group_id, strengths): + """.""" + bpms = self._groups2dopbba[group_id] quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] - if track_tune: - tunes = [] - for _o in order: - bpmname = bpms[_o] + for strength, bpmname in zip(strengths, bpms): quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] - if not quad.pwrstate: - print('\n error: quadrupole ' + quadname + ' is Off.') - self._stopevt.set() - print(' exiting...') - return - quad.strength = strengths[_o] - _time.sleep(self.params.wait_quadrupole) - if track_tune: - tunes.append(self.get_tunes()) - if track_tune: - return tunes - return + quad.strength = strength - def get_strengths(self): - order = self.get_group_ordering() - bpms = self.get_active_bpmnames() - strengths = _np.zeros(len(bpms)) + for strength, bpmname in zip(strengths, bpms): + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + if not quad.wait_float( + 'KLRef-Mon', strength, timeout=self.params.wait_quadrupoles + ): + return False + return True + + def get_strengths(self, group_id): + """.""" + bpms = self._groups2dopbba[group_id] quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] - for _o in order: - bpmname = bpms[_o] + + strengths = [] + for bpmname in bpms: quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] - if not quad.pwrstate: - print('\n error: quadrupole ' + quadname + ' is Off.') - self._stopevt.set() - print(' exiting...') - return - strengths[_o] = quad.strength - return strengths + strengths.append(quad.strength) + return _np.array(strengths) def meas_ios(self, track_tune=False): delta_stren = self.get_group_delta_kl() @@ -627,25 +600,20 @@ def model(self): @model.setter def model(self, value): self._model = value - self._famdata = _pymodels.si.families.get_family_data(self._model) + self._famdata = _si.families.get_family_data(self._model) @property def fam_data(self): + """.""" return self._famdata - @fam_data.setter - def fam_data(self, value): - raise ValueError( - "\n Can't set fam_data manually, try setting a model" - ) - - def get_ios_jacobian(self): + def calc_ios_jacobians(self): model = self._model if model is None: - print('\n undefined model... setting a default one') + print('\n Undefined model... setting a default one') model = ( - _pymodels.si.fitted_models.vertical_dispersion_and_coupling( - _pymodels.si.create_accelerator() + _si.fitted_models.vertical_dispersion_and_coupling( + _si.create_accelerator() ) ) self.model = model @@ -654,51 +622,52 @@ def get_ios_jacobian(self): _orbcorr = _OrbitCorr( model=model, acc='SI', corr_system='SOFB', use6dorb=use6d ) - bpms = self.get_active_bpmnames() - order = self.get_group_ordering() - delta_stren = self.get_group_delta_kl() - bnames, _, qnames, quadindices = self.get_default_quads( - model, fam_data - ) - def _get_strengths(): - strens = [] - for _o in order: - _id = bnames.index(bpms[_o]) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - strens.append(model[qidx].KsL) - else: - strens.append(model[qidx].KL) - return strens - - strens_orig = _get_strengths() - - def _set_strengths(strengths): - for _o in order: - _id = bnames.index(bpms[_o]) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - model[qidx].KsL = strengths[_o] - else: - model[qidx].KL = strengths[_o] - - _set_strengths(strens_orig + delta_stren / 2) - try: - jac_pos = _orbcorr.get_jacobian_matrix() - except Exception as E: - _set_strengths(strens_orig) - print(E) - return - _set_strengths(strens_orig - delta_stren / 2) - try: - jac_neg = _orbcorr.get_jacobian_matrix() - except Exception as E: + for bpms in self._groups2dopbba: + order = self.get_group_ordering() + delta_stren = self.get_group_delta_kl() + bnames, _, qnames, quadindices = self.get_default_quads( + model, fam_data + ) + + def _get_strengths(): + strens = [] + for bname in bpms: + _id = bnames.index(bname) + qidx = quadindices[_id] + if 'QS' in qnames[_id]: + strens.append(model[qidx].KsL) + else: + strens.append(model[qidx].KL) + return strens + + strens_orig = _get_strengths() + + def _set_strengths(strengths): + for _o in order: + _id = bnames.index(bpms[_o]) + qidx = quadindices[_id] + if 'QS' in qnames[_id]: + model[qidx].KsL = strengths[_o] + else: + model[qidx].KL = strengths[_o] + + _set_strengths(strens_orig + delta_stren / 2) + try: + jac_pos = _orbcorr.get_jacobian_matrix() + except Exception as E: + _set_strengths(strens_orig) + print(E) + return + _set_strengths(strens_orig - delta_stren / 2) + try: + jac_neg = _orbcorr.get_jacobian_matrix() + except Exception as E: + _set_strengths(strens_orig) + print(E) + return _set_strengths(strens_orig) - print(E) - return - _set_strengths(strens_orig) - return jac_pos - jac_neg + return jac_pos - jac_neg @staticmethod def inverse_matrix(matrix, rcond=1e-5): @@ -706,7 +675,7 @@ def inverse_matrix(matrix, rcond=1e-5): _np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T ) - def correct_ios(self, jacobian=None, track_tune=False): + def correct_ios(self, group_id, jacobian=None, track_tune=False): if jacobian is None: jacobian = self.get_ios_jacobian() inverse_jacobian = self.inverse_matrix( @@ -729,9 +698,9 @@ def correct_ios(self, jacobian=None, track_tune=False): dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) kicks_fim = self.get_kicks() - group_name = f'group_{self.active_group_id:0d}' + group_name = f'group_{group_id:0d}' self.data[group_name] = { - 'bpms': self.groups2dopbba[self.active_group_id], + 'bpms': self._groups2dopbba[group_id], 'ios_iter': ios_iter, 'dkicks_iter': dkicks_iter, 'kicks_ini': kicks_ini, @@ -751,17 +720,19 @@ def _do_pbba(self): ) ) + self.calc_ios_jacobians() + sofb = self.devices['sofb'] sofb.nr_points = self.params.sofb_nrpoints loop_on = False if sofb.autocorrsts: loop_on = True - print('SOFB feedback is enable, disabling it...') + print('SOFB feedback is enabled, disabling it...') sofb.cmd_turn_off_autocorr() - for i in range(len(self.groups2dopbba)): + for i in range(len(self._groups2dopbba)): if self._stopevt.is_set(): - print('stopped!') + print('Stopped!') break if not self.havebeam: print('Beam was Lost') @@ -772,28 +743,26 @@ def _do_pbba(self): self._dopbba_single_group(i) if loop_on: - print('SOFB feedback was enable, restoring original state...') + print('SOFB feedback was enabled, restoring original state...') sofb.cmd_turn_on_autocorr() tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] - print('finished! Elapsed time {:s}'.format(dtime)) + print('Finished! Elapsed time {:s}'.format(dtime)) def _dopbba_single_group(self, group_id): """.""" - self.activegroup_id = group_id - bpmnames = self.get_active_bpmnames() + bpmnames = self._groups2dopbba[group_id] bpmnames_all = self.data['bpmnames'] nbpms = len(bpmnames_all) - self.connect_to_quadrupoles() tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime('%Hh%Mm%Ss') print('{:s} --> Doing PBBA for Group {:d}'.format(strtini, group_id)) - ios_jac = self.get_ios_jacobian(self._model, self._famdata) - self.correct_ios(ios_jac, self.params.dotrack_tune) + ios_jac = self.get_ios_jacobian(group_id, self._model, self._famdata) + self.correct_ios(group_id, ios_jac, self.params.dotrack_tune) orbit = self.get_orbit() for bpm in bpmnames: idx = bpmnames_all.index(bpm) @@ -801,8 +770,6 @@ def _dopbba_single_group(self, group_id): 'x0': orbit[idx], 'y0': orbit[idx + nbpms], } - self.data['scancenterx'][idx] = self.data['measure'][bpm]['x0'] - self.data['scancentery'][idx] = self.data['measure'][bpm]['y0'] # print("restoring initial conditions.") #! -> automatically restored before each group From 8717f540b2dfcdeeec605395d552a8c2f105d844 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 26 Sep 2025 12:30:59 -0300 Subject: [PATCH 12/61] WIP: remove tune readings, add analysis/helper functions, pre-calc jacobians... --- apsuite/commisslib/measure_pbba.py | 262 ++++++++++++++++++----------- 1 file changed, 166 insertions(+), 96 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index cb27fc273..819c37987 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -12,17 +12,19 @@ CurrInfoSI as _CurrInfoSI, PowerSupply as _PowerSupply, SOFB as _SOFB, - Tune as _Tune ) from siriuspy.namesys import SiriusPVName as _PVName from ..orbcorr.orbit_correction import OrbitCorr as _OrbitCorr from ..utils import ( ParamsBaseClass as _ParamsBaseClass, - ThreadedMeasBaseClass as _BaseClass + ThreadedMeasBaseClass as _BaseClass, ) +DELTA_KL = [0, 0, 0, 0, 0] + + class ParallelBBAParams(_ParamsBaseClass): """.""" @@ -371,8 +373,6 @@ def __init__(self): self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] - self.dotrack_tune = True - def __str__(self): """.""" return '...' @@ -394,9 +394,9 @@ def __init__(self, isonline=True): self.data['measure'] = dict() self._model = None self._famdata = None + self._jacobians = list() if self.isonline: - self.devices['tune'] = _Tune(_Tune.DEVICES.SI) self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) self.devices['currinfosi'] = _CurrInfoSI() self.connect_to_quadrupoles() @@ -439,6 +439,7 @@ def connect_to_quadrupoles(self): ) def get_active_quadnames(self, group_id): + """.""" qnames = [] for bpm in self._groups2dopbba[group_id]: idx = self.data['bpmnames'].index(bpm) @@ -463,6 +464,7 @@ def correct_orbit(self): ) def get_kicks(self): + """.""" sofb = self.devices['sofb'] return _np.hstack(( list(sofb.kickch), @@ -471,6 +473,7 @@ def get_kicks(self): )) def set_delta_kicks(self, dkicks): + """.""" sofb = self.devices['sofb'] nch, ncv, nrf = sofb._data.nr_ch, sofb._data.nr_cv, 1 if len(dkicks) != nch + ncv + nrf: @@ -499,19 +502,7 @@ def set_delta_kicks(self, dkicks): ) sofb.mancorrgainch, sofb.mancorrgaincv = factch, factcv, factrf - def get_tunes(self): - tune = self.devices['tune'] - return _np.array([tune.tunex, tune.tuney]) - # #### pbba utils ##### - def get_group_delta_kl(self, group_id): - bpms = self._groups2dopbba[group_id] - delta_kl = _np.ones(len(bpms)) * self.params.quad_deltakl - delta_kl[1::2] *= -1 - return delta_kl - - def get_group_ordering(self, group_id): - return _np.arange(len(self._groups2dopbba[group_id])) def set_strengths(self, group_id, strengths): """.""" @@ -527,7 +518,7 @@ def set_strengths(self, group_id, strengths): quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad.wait_float( - 'KLRef-Mon', strength, timeout=self.params.wait_quadrupoles + 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole ): return False return True @@ -545,20 +536,20 @@ def get_strengths(self, group_id): strengths.append(quad.strength) return _np.array(strengths) - def meas_ios(self, track_tune=False): - delta_stren = self.get_group_delta_kl() - strens_orig = self.get_strengths() - tune_up = self.set_strengths(strens_orig + delta_stren / 2, track_tune) + def meas_ios(self, group_id): + """.""" + delta_strens = DELTA_KL + strens_orig = self.get_strengths(group_id) + _set_flag = self.set_strengths( + group_id, strens_orig + delta_strens / 2 + ) orb_pos = self.get_orbit() - tune_down = self.set_strengths( - strens_orig + delta_stren / 2, track_tune + _set_flag = self.set_strengths( + group_id, strens_orig - delta_strens / 2 ) orb_neg = self.get_orbit() - tune_back = self.set_strengths(strens_orig, track_tune) - if track_tune: - tune_variation = tune_up + tune_down + tune_back - return orb_pos - orb_neg, tune_variation - return orb_pos - orb_neg, None + _set_flag = self.set_strengths(group_id, strens_orig) + return orb_pos - orb_neg @staticmethod def get_default_quads(model, fam_data): @@ -595,6 +586,12 @@ def get_default_quads(model, fam_data): @property def model(self): + """.""" + if self._model is None: + print('\n Undefined model... setting a default one') + self._model = _si.fitted_models.vertical_dispersion_and_coupling( + _si.create_accelerator() + ) return self._model @model.setter @@ -607,109 +604,185 @@ def fam_data(self): """.""" return self._famdata - def calc_ios_jacobians(self): - model = self._model - if model is None: - print('\n Undefined model... setting a default one') - model = ( - _si.fitted_models.vertical_dispersion_and_coupling( - _si.create_accelerator() - ) - ) - self.model = model + def calc_ios_jacobians(self, groups_to_calc=None): + """Calculate the IOS Response Matrices for all groups.""" + model = self.model fam_data = self._famdata use6d = any([model.cavity_on, model.radiation_on > 0]) _orbcorr = _OrbitCorr( - model=model, acc='SI', corr_system='SOFB', use6dorb=use6d + model=model, acc='SI', corr_system='SOFB', use6dtrack=use6d + ) + jacobian_shape = ( + len(_orbcorr.respm.bpm_idx), + len(_orbcorr.respm.ch_idx) + + len(_orbcorr.respm.cv_idx) + + len(_orbcorr.respm.rf_idx), + ) + bnames, _, qnames, quadindices = self.get_default_quads( + model, fam_data ) - for bpms in self._groups2dopbba: - order = self.get_group_ordering() - delta_stren = self.get_group_delta_kl() - bnames, _, qnames, quadindices = self.get_default_quads( - model, fam_data - ) + def _get_strengths(_bpms): + strens = [] + for bname in _bpms: + _id = bnames.index(bname) + qidx = quadindices[_id] + if 'QS' in qnames[_id]: + strens.append(model[qidx].KsL) + else: + strens.append(model[qidx].KL) + return strens + + def _set_strengths(_bpms, strengths): + for strength, bname in zip(strengths, _bpms): + _id = bnames.index(bname) + qidx = quadindices[_id] + if 'QS' in qnames[_id]: + model[qidx].KsL = strength + else: + model[qidx].KL = strength + + jacobians = [] + groups_to_calc = ( + _np.arange(len(self._groups2dopbba)) + if groups_to_calc is None + else groups_to_calc + ) + for bpms in self._groups2dopbba[groups_to_calc]: + delta_strens = DELTA_KL + strens_orig = _get_strengths(bpms) - def _get_strengths(): - strens = [] - for bname in bpms: - _id = bnames.index(bname) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - strens.append(model[qidx].KsL) - else: - strens.append(model[qidx].KL) - return strens - - strens_orig = _get_strengths() - - def _set_strengths(strengths): - for _o in order: - _id = bnames.index(bpms[_o]) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - model[qidx].KsL = strengths[_o] - else: - model[qidx].KL = strengths[_o] - - _set_strengths(strens_orig + delta_stren / 2) + _set_strengths(bpms, strens_orig + delta_strens / 2) try: jac_pos = _orbcorr.get_jacobian_matrix() - except Exception as E: - _set_strengths(strens_orig) - print(E) - return - _set_strengths(strens_orig - delta_stren / 2) + except Exception: + _set_strengths(bpms, strens_orig) + jac_pos = _np.full(jacobian_shape, _np.nan) + + _set_strengths(bpms, strens_orig - delta_strens / 2) try: jac_neg = _orbcorr.get_jacobian_matrix() - except Exception as E: - _set_strengths(strens_orig) - print(E) - return - _set_strengths(strens_orig) - return jac_pos - jac_neg + except Exception: + _set_strengths(bpms, strens_orig) + jac_neg = _np.full(jacobian_shape, _np.nan) + + _set_strengths(bpms, strens_orig) + jacobians.append(jac_pos - jac_neg) + + return jacobians + + def analyze_group(self, group_id): + """Helper function to analyze group's properties.""" + print(f'Analyzing group {group_id:2d}:') + try: + jacobian = self._jacobians[group_id] + except Exception: + jacobian = self.calc_ios_jacobians(groups_to_calc=[group_id]) + u_mat, svals, vt_mat = _np.linalg.svd(jacobian) + + model = self.model + bnames, _, qnames, quadindices = self.get_default_quads( + model, self._famdata + ) + delta_strens = DELTA_KL + + tune_variation = [_pyacc.optics.get_frac_tunes(model, dim='4D')] + + for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): + _id = bnames.index(bpm) + qname = qnames[_id] + qidx = quadindices[_id] + if 'QS' in qname: + model[qidx].KsL += dkl / 2 + else: + model[qidx].KL += dkl / 2 + tune_variation.append( + _pyacc.optics.get_frac_tunes(model, dim='4D') + ) + + for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): + _id = bnames.index(bpm) + qname = qnames[_id] + qidx = quadindices[_id] + if 'QS' in qname: + model[qidx].KsL -= dkl + else: + model[qidx].KL -= dkl + tune_variation.append( + _pyacc.optics.get_frac_tunes(model, dim='4D') + ) + + for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): + _id = bnames.index(bpm) + qname = qnames[_id] + qidx = quadindices[_id] + if 'QS' in qname: + model[qidx].KsL += dkl / 2 + else: + model[qidx].KL += dkl / 2 + tune_variation.append( + _pyacc.optics.get_frac_tunes(model, dim='4D') + ) + + return { + 'U_matrix': u_mat, + 'Vt_matrix': vt_mat, + 'Svals': svals, + 'Tune_variation': tune_variation, + } + + def analyze_groups(self): + """Helper function to analyze the groups' properties.""" + anl = dict() + for group_id in range(len(self._groups2dopbba)): + anl[f'group_{group_id:2d}'] = self.analyze_group(group_id) + return anl + + @property + def jacobians(self): + """.""" + return _dcopy(self._jacobians) @staticmethod def inverse_matrix(matrix, rcond=1e-5): + """.""" return _np.dot( _np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T ) - def correct_ios(self, group_id, jacobian=None, track_tune=False): - if jacobian is None: - jacobian = self.get_ios_jacobian() + def correct_ios(self, group_id): + """.""" + jacobian = self._jacobians[group_id] inverse_jacobian = self.inverse_matrix( jacobian, self.params.inv_jac_rcond ) - ios_iter, tune_iter, dkicks_iter = [], [], [] + ios_iter, dkicks_iter = [], [] kicks_ini = self.get_kicks() nr_iters = self.params.corr_nr_iters + for i in range(nr_iters + 1): if self._stopevt.is_set() or not self.havebeam: print(' exiting...') break print(' {0:02d}/{1:02d} --> '.format(i + 1, nr_iters), end='') - ios, tune = self.meas_ios(track_tune) + ios = self.meas_ios(group_id) ios_iter.append(ios) - tune_iter.append(tune) if i >= nr_iters: break dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) + kicks_fim = self.get_kicks() - group_name = f'group_{group_id:0d}' + group_name = f'group_{group_id:2d}' self.data[group_name] = { 'bpms': self._groups2dopbba[group_id], 'ios_iter': ios_iter, 'dkicks_iter': dkicks_iter, 'kicks_ini': kicks_ini, 'kicks_fim': kicks_fim, - 'ordering': self.get_group_ordering(), - 'delta_kl': self.get_group_delta_kl(), + 'delta_kl': DELTA_KL, } - if track_tune: - self.data[group_name]['tune_iter'] = tune_iter # #### private methods #### def _do_pbba(self): @@ -720,7 +793,7 @@ def _do_pbba(self): ) ) - self.calc_ios_jacobians() + self._jacobians = self.calc_ios_jacobians() sofb = self.devices['sofb'] sofb.nr_points = self.params.sofb_nrpoints @@ -761,8 +834,7 @@ def _dopbba_single_group(self, group_id): strtini = tini.strftime('%Hh%Mm%Ss') print('{:s} --> Doing PBBA for Group {:d}'.format(strtini, group_id)) - ios_jac = self.get_ios_jacobian(group_id, self._model, self._famdata) - self.correct_ios(group_id, ios_jac, self.params.dotrack_tune) + self.correct_ios(group_id) orbit = self.get_orbit() for bpm in bpmnames: idx = bpmnames_all.index(bpm) @@ -771,8 +843,6 @@ def _dopbba_single_group(self, group_id): 'y0': orbit[idx + nbpms], } - # print("restoring initial conditions.") #! -> automatically restored before each group - tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] From 8525e79e1d7d8b2c2bbc99aef494ffa84ba9fe44 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 26 Sep 2025 17:04:50 -0300 Subject: [PATCH 13/61] WIP: status checkings, groups rework --- apsuite/commisslib/measure_pbba.py | 254 +++++++++++++++++++---------- 1 file changed, 167 insertions(+), 87 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 819c37987..c69c4bd48 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -20,9 +20,7 @@ ParamsBaseClass as _ParamsBaseClass, ThreadedMeasBaseClass as _BaseClass, ) - - -DELTA_KL = [0, 0, 0, 0, 0] +from mathphys.functions import get_namedtuple as _get_namedtuple class ParallelBBAParams(_ParamsBaseClass): @@ -378,15 +376,76 @@ def __str__(self): return '...' +class BBAPairPVName(_PVName): + """.""" + + def __new__( + cls, + pv_name, + associated_quad_str=None, + associated_dkl=0.0, + elements=None, + ): + """.""" + if 'BPM' in pv_name: + obj = super().__new__(cls, pv_name, elements) + if associated_quad_str is None: + associated_quad_str = ParallelBBAParams.QUADNAMES[ + ParallelBBAParams.BPMNAMES.index(pv_name) + ] + obj.associated_quad = _PVName(str(associated_quad_str)) + obj.associated_dkl = associated_dkl + return obj + else: + raise ValueError('Creation restricted for BPMs') + + +class PBBAGroup(list[BBAPairPVName]): + """Lista de SiriusPVNames (BPMs + metadados).""" + + def __init__(self, iterable=()): + """.""" + if all(isinstance(_, BBAPairPVName) for _ in iterable): + super().__init__(iterable) + elif all(isinstance(_, str) for _ in iterable): + iterable = [ + BBAPairPVName( + bname, + ParallelBBAParams.QUADNAMES[ + ParallelBBAParams.BPMNAMES.index(bname) + ], + ) + for bname in iterable + if bname in ParallelBBAParams.BPMNAMES + ] + super().__init__(iterable) + else: + raise ValueError('') + + @property + def delta_kl(self): + """.""" + return _np.array([_.associated_dkl for _ in self]) + + @delta_kl.setter + def delta_kl(self, value): + if len(value) != len(self): + raise ValueError + for i, val in enumerate(value): + self[i].associated_dkl = val + + class DoParallelBBA(_BaseClass): """.""" + STATUS = _get_namedtuple('Status', ['Fail', 'Success']) + def __init__(self, isonline=True): """.""" super().__init__( params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) - self._groups2dopbba = list() + self._groups2dopbba: list[PBBAGroup] = list() self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) @@ -394,10 +453,12 @@ def __init__(self, isonline=True): self.data['measure'] = dict() self._model = None self._famdata = None - self._jacobians = list() + self._jacobians: list[_np.ndarray] = list() if self.isonline: self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) + self.devices['sofb'].nr_points = self.params.sofb_nrpoints + self._sofb_loop_on = self.devices['sofb'].autocorrsts self.devices['currinfosi'] = _CurrInfoSI() self.connect_to_quadrupoles() @@ -419,15 +480,24 @@ def measuredbpms(self): @property def groups2dopbba(self): """.""" - return _dcopy(self._groups2dopbba) + return self._groups2dopbba @groups2dopbba.setter def groups2dopbba(self, groups): - self._groups2dopbba = [ - [_PVName(bpm) for bpm in group if isinstance(bpm, str)] - for group in groups - if isinstance(group, (list, tuple, _np.ndarray)) - ] + pbba_groups_list = [] + for group in groups: + if isinstance(group, PBBAGroup): + pbba_groups_list.append(group) + elif isinstance(group, (list, tuple, _np.ndarray)): + if all(isinstance(x, str) for x in group): + pbba_groups_list.append(PBBAGroup(group)) + elif all(isinstance(x, BBAPairPVName) for x in group): + pbba_groups_list.append(PBBAGroup(group)) + else: + raise ValueError('') + else: + raise ValueError('') + self._groups2dopbba = pbba_groups_list def connect_to_quadrupoles(self): """.""" @@ -438,16 +508,6 @@ def connect_to_quadrupoles(self): qname, props2init=('PwrState-Sts', 'KL-SP', 'KL-RB') ) - def get_active_quadnames(self, group_id): - """.""" - qnames = [] - for bpm in self._groups2dopbba[group_id]: - idx = self.data['bpmnames'].index(bpm) - qname = self.data['quadnames'][idx] - if qname: - qnames.append(qname) - return qnames - def get_orbit(self): """.""" sofb = self.devices['sofb'] @@ -504,7 +564,7 @@ def set_delta_kicks(self, dkicks): # #### pbba utils ##### - def set_strengths(self, group_id, strengths): + def set_strengths(self, group_id, strengths, ignore_timeout=False): """.""" bpms = self._groups2dopbba[group_id] quad_names = self.data['quadnames'] @@ -514,14 +574,15 @@ def set_strengths(self, group_id, strengths): quad = self.devices[quadname] quad.strength = strength - for strength, bpmname in zip(strengths, bpms): - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - if not quad.wait_float( - 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole - ): - return False - return True + if not ignore_timeout: + for strength, bpmname in zip(strengths, bpms): + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + if not quad.wait_float( + 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole + ): + return DoParallelBBA.STATUS.Fail + return DoParallelBBA.STATUS.Success def get_strengths(self, group_id): """.""" @@ -538,18 +599,20 @@ def get_strengths(self, group_id): def meas_ios(self, group_id): """.""" - delta_strens = DELTA_KL + delta_strens = self._groups2dopbba[group_id].delta_kl strens_orig = self.get_strengths(group_id) - _set_flag = self.set_strengths( - group_id, strens_orig + delta_strens / 2 - ) + _sts = self.set_strengths(group_id, strens_orig + delta_strens / 2) + if not _sts: + return None, DoParallelBBA.STATUS.Fail orb_pos = self.get_orbit() - _set_flag = self.set_strengths( - group_id, strens_orig - delta_strens / 2 - ) + _sts = self.set_strengths(group_id, strens_orig - delta_strens / 2) + if not _sts: + return None, DoParallelBBA.STATUS.Fail orb_neg = self.get_orbit() - _set_flag = self.set_strengths(group_id, strens_orig) - return orb_pos - orb_neg + _sts = self.set_strengths(group_id, strens_orig) + if not _sts: + return None, DoParallelBBA.STATUS.Fail + return orb_pos - orb_neg, DoParallelBBA.STATUS.Success @staticmethod def get_default_quads(model, fam_data): @@ -589,9 +652,11 @@ def model(self): """.""" if self._model is None: print('\n Undefined model... setting a default one') + self._model = _si.create_accelerator() self._model = _si.fitted_models.vertical_dispersion_and_coupling( - _si.create_accelerator() + self._model ) + self._famdata = _si.families.get_family_data(self._model) return self._model @model.setter @@ -602,12 +667,12 @@ def model(self, value): @property def fam_data(self): """.""" - return self._famdata + return _dcopy(self._famdata) def calc_ios_jacobians(self, groups_to_calc=None): """Calculate the IOS Response Matrices for all groups.""" model = self.model - fam_data = self._famdata + fam_data = self.fam_data use6d = any([model.cavity_on, model.radiation_on > 0]) _orbcorr = _OrbitCorr( model=model, acc='SI', corr_system='SOFB', use6dtrack=use6d @@ -622,19 +687,19 @@ def calc_ios_jacobians(self, groups_to_calc=None): model, fam_data ) - def _get_strengths(_bpms): + def _get_strengths(group): strens = [] - for bname in _bpms: + for bname in group: _id = bnames.index(bname) qidx = quadindices[_id] if 'QS' in qnames[_id]: strens.append(model[qidx].KsL) else: strens.append(model[qidx].KL) - return strens + return _np.array(strens) - def _set_strengths(_bpms, strengths): - for strength, bname in zip(strengths, _bpms): + def _set_strengths(group, strengths): + for strength, bname in zip(strengths, group): _id = bnames.index(bname) qidx = quadindices[_id] if 'QS' in qnames[_id]: @@ -648,25 +713,26 @@ def _set_strengths(_bpms, strengths): if groups_to_calc is None else groups_to_calc ) - for bpms in self._groups2dopbba[groups_to_calc]: - delta_strens = DELTA_KL - strens_orig = _get_strengths(bpms) + groups_to_calc = [self._groups2dopbba[i] for i in groups_to_calc] + for group in groups_to_calc: + delta_strens = group.delta_kl + strens_orig = _get_strengths(group) - _set_strengths(bpms, strens_orig + delta_strens / 2) + _set_strengths(group, strens_orig + delta_strens / 2) try: jac_pos = _orbcorr.get_jacobian_matrix() except Exception: - _set_strengths(bpms, strens_orig) + _set_strengths(group, strens_orig) jac_pos = _np.full(jacobian_shape, _np.nan) - _set_strengths(bpms, strens_orig - delta_strens / 2) + _set_strengths(group, strens_orig - delta_strens / 2) try: jac_neg = _orbcorr.get_jacobian_matrix() except Exception: - _set_strengths(bpms, strens_orig) + _set_strengths(group, strens_orig) jac_neg = _np.full(jacobian_shape, _np.nan) - _set_strengths(bpms, strens_orig) + _set_strengths(group, strens_orig) jacobians.append(jac_pos - jac_neg) return jacobians @@ -684,7 +750,7 @@ def analyze_group(self, group_id): bnames, _, qnames, quadindices = self.get_default_quads( model, self._famdata ) - delta_strens = DELTA_KL + delta_strens = self._groups2dopbba[group_id].delta_kl tune_variation = [_pyacc.optics.get_frac_tunes(model, dim='4D')] @@ -750,22 +816,39 @@ def inverse_matrix(matrix, rcond=1e-5): _np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T ) + def exit_graciously(self, group_id, stable_strengths): + """.""" + self.set_strengths(group_id, stable_strengths, ignore_timeout=True) + _time.sleep(self.params.wait_quadrupole) + self.correct_orbit() + def correct_ios(self, group_id): """.""" jacobian = self._jacobians[group_id] inverse_jacobian = self.inverse_matrix( jacobian, self.params.inv_jac_rcond ) + group_name = f'group_{group_id:2d}' + self.data[group_name] = { + 'bpms': self._groups2dopbba[group_id], + 'kicks_ini': self.get_kicks(), + 'strengths_orig': self.get_strengths(group_id), + } ios_iter, dkicks_iter = [], [] - kicks_ini = self.get_kicks() nr_iters = self.params.corr_nr_iters - + print(' correcting IOS:', end='') for i in range(nr_iters + 1): if self._stopevt.is_set() or not self.havebeam: print(' exiting...') - break - print(' {0:02d}/{1:02d} --> '.format(i + 1, nr_iters), end='') - ios = self.meas_ios(group_id) + return DoParallelBBA.STATUS.Fail + print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') + ios, sts = self.meas_ios(group_id) + if not sts: + self.exit_graciously( + group_id, + stable_strengths=self.data[group_name]['strengths_orig'], + ) + return DoParallelBBA.STATUS.Fail ios_iter.append(ios) if i >= nr_iters: break @@ -773,16 +856,14 @@ def correct_ios(self, group_id): dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - kicks_fim = self.get_kicks() - group_name = f'group_{group_id:2d}' - self.data[group_name] = { - 'bpms': self._groups2dopbba[group_id], - 'ios_iter': ios_iter, - 'dkicks_iter': dkicks_iter, - 'kicks_ini': kicks_ini, - 'kicks_fim': kicks_fim, - 'delta_kl': DELTA_KL, - } + self.data[group_name]['kicks_fim'] = self.get_kicks() + self.data[group_name]['ios_iter'] = ios_iter + self.data[group_name]['dkicks_iter'] = dkicks_iter + self.data[group_name]['delta_kl'] = self._groups2dopbba[ + group_id + ].delta_kl + + return DoParallelBBA.STATUS.Success # #### private methods #### def _do_pbba(self): @@ -796,10 +877,7 @@ def _do_pbba(self): self._jacobians = self.calc_ios_jacobians() sofb = self.devices['sofb'] - sofb.nr_points = self.params.sofb_nrpoints - loop_on = False - if sofb.autocorrsts: - loop_on = True + if self._sofb_loop_on: print('SOFB feedback is enabled, disabling it...') sofb.cmd_turn_off_autocorr() @@ -810,12 +888,12 @@ def _do_pbba(self): if not self.havebeam: print('Beam was Lost') break - print('\nCorrecting Orbit...', end='') + print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') self._dopbba_single_group(i) - if loop_on: + if self._sofb_loop_on: print('SOFB feedback was enabled, restoring original state...') sofb.cmd_turn_on_autocorr() @@ -834,16 +912,18 @@ def _dopbba_single_group(self, group_id): strtini = tini.strftime('%Hh%Mm%Ss') print('{:s} --> Doing PBBA for Group {:d}'.format(strtini, group_id)) - self.correct_ios(group_id) - orbit = self.get_orbit() - for bpm in bpmnames: - idx = bpmnames_all.index(bpm) - self.data['measure'][bpm] = { - 'x0': orbit[idx], - 'y0': orbit[idx + nbpms], - } - + status = self.correct_ios(group_id) + if status: + orbit = self.get_orbit() + for bpm in bpmnames: + idx = bpmnames_all.index(bpm) + self.data['measure'][bpm] = { + 'x0': orbit[idx], + 'y0': orbit[idx + nbpms], + } tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] - print('Done! Elapsed time: {:s}\n'.format(dtime)) + if status: + print('Done! Elapsed time: {:s}\n'.format(dtime)) + print('Fail! Elapsed time: {:s}\n'.format(dtime)) From 988bc764292217b1f47d510793c534b1e78df662 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 10:09:52 -0300 Subject: [PATCH 14/61] WIP: back to old group fmt --- apsuite/commisslib/measure_pbba.py | 95 ++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index c69c4bd48..3d0b0e0fb 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -445,7 +445,9 @@ def __init__(self, isonline=True): super().__init__( params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) - self._groups2dopbba: list[PBBAGroup] = list() + # self._groups2dopbba: list[PBBAGroup] = list() + self._groups2dopbba: list[_PVName] = list() + self._delta_kl: list[_np.ndarray] = list() self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) @@ -480,24 +482,50 @@ def measuredbpms(self): @property def groups2dopbba(self): """.""" - return self._groups2dopbba + # return self._groups2dopbba + return _dcopy(self._groups2dopbba) + + # @groups2dopbba.setter + # def groups2dopbba(self, groups): + # pbba_groups_list = [] + # for group in groups: + # if isinstance(group, PBBAGroup): + # pbba_groups_list.append(group) + # elif isinstance(group, (list, tuple, _np.ndarray)): + # if all(isinstance(x, str) for x in group): + # pbba_groups_list.append(PBBAGroup(group)) + # elif all(isinstance(x, BBAPairPVName) for x in group): + # pbba_groups_list.append(PBBAGroup(group)) + # else: + # raise ValueError('') + # else: + # raise ValueError('') + # self._groups2dopbba = pbba_groups_list @groups2dopbba.setter def groups2dopbba(self, groups): - pbba_groups_list = [] - for group in groups: - if isinstance(group, PBBAGroup): - pbba_groups_list.append(group) - elif isinstance(group, (list, tuple, _np.ndarray)): - if all(isinstance(x, str) for x in group): - pbba_groups_list.append(PBBAGroup(group)) - elif all(isinstance(x, BBAPairPVName) for x in group): - pbba_groups_list.append(PBBAGroup(group)) - else: - raise ValueError('') - else: - raise ValueError('') - self._groups2dopbba = pbba_groups_list + self._groups2dopbba = [ + [_PVName(bpm) for bpm in group if isinstance(bpm, str)] + for group in groups + if isinstance(group, (list, tuple, _np.ndarray)) + ] + + @property + def delta_kl(self): + """.""" + return _dcopy(self._delta_kl) + + @delta_kl.setter + def delta_kl(self, value): + _max = self.params.quad_deltakl + for i, group in enumerate(self._groups2dopbba): + if len(value[i]) != len(group): + raise ValueError( + f'size mismatch between group {i} and given delta_kl' + ) + if any([abs(v) > _max for v in value[i]]): + raise ValueError(f"values for delta kl can't exceed {_max}") + self._delta_kl = _dcopy(value) def connect_to_quadrupoles(self): """.""" @@ -599,7 +627,8 @@ def get_strengths(self, group_id): def meas_ios(self, group_id): """.""" - delta_strens = self._groups2dopbba[group_id].delta_kl + # delta_strens = self._groups2dopbba[group_id].delta_kl + delta_strens = self._delta_kl[group_id] strens_orig = self.get_strengths(group_id) _sts = self.set_strengths(group_id, strens_orig + delta_strens / 2) if not _sts: @@ -715,7 +744,13 @@ def _set_strengths(group, strengths): ) groups_to_calc = [self._groups2dopbba[i] for i in groups_to_calc] for group in groups_to_calc: - delta_strens = group.delta_kl + # delta_strens = group.delta_kl + try: + delta_strens = self._delta_kl[self._groups2dopbba.index(group)] + except Exception as e: + str_msg = 'undefined or empty "delta_kl"' + str_msg += f' of group {self._groups2dopbba.index(group)}' + raise IndexError(str_msg) from e strens_orig = _get_strengths(group) _set_strengths(group, strens_orig + delta_strens / 2) @@ -735,11 +770,20 @@ def _set_strengths(group, strengths): _set_strengths(group, strens_orig) jacobians.append(jac_pos - jac_neg) + problems = [] + for i, jac in enumerate(jacobians): + if _np.any(_np.isnan(jac)): + group_id = self._groups2dopbba.index(groups_to_calc[i]) + problems.append(group_id) + if problems: + str_msg = 'Error(s) when obtaining the jacobian' + str_msg += f'for group(s): {problems}' + raise ValueError(str_msg) + return jacobians def analyze_group(self, group_id): """Helper function to analyze group's properties.""" - print(f'Analyzing group {group_id:2d}:') try: jacobian = self._jacobians[group_id] except Exception: @@ -750,7 +794,8 @@ def analyze_group(self, group_id): bnames, _, qnames, quadindices = self.get_default_quads( model, self._famdata ) - delta_strens = self._groups2dopbba[group_id].delta_kl + # delta_strens = self._groups2dopbba[group_id].delta_kl + delta_strens = self._delta_kl[group_id] tune_variation = [_pyacc.optics.get_frac_tunes(model, dim='4D')] @@ -801,7 +846,8 @@ def analyze_groups(self): """Helper function to analyze the groups' properties.""" anl = dict() for group_id in range(len(self._groups2dopbba)): - anl[f'group_{group_id:2d}'] = self.analyze_group(group_id) + print(f'Analyzing group: {group_id:d}') + anl[f'group_{group_id:d}'] = self.analyze_group(group_id) return anl @property @@ -859,9 +905,10 @@ def correct_ios(self, group_id): self.data[group_name]['kicks_fim'] = self.get_kicks() self.data[group_name]['ios_iter'] = ios_iter self.data[group_name]['dkicks_iter'] = dkicks_iter - self.data[group_name]['delta_kl'] = self._groups2dopbba[ - group_id - ].delta_kl + # self.data[group_name]['delta_kl'] = self._groups2dopbba[ + # group_id + # ].delta_kl + self.data[group_name]['delta_kl'] = self._delta_kl[group_id] return DoParallelBBA.STATUS.Success From 32fbc27e2c319de34aa085e5fcf938f2defed7c8 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 1 Oct 2025 13:52:53 -0300 Subject: [PATCH 15/61] COMMISS.PBBA.WIP: revision suggestions. --- apsuite/commisslib/measure_pbba.py | 521 +++++++++++++---------------- 1 file changed, 236 insertions(+), 285 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 3d0b0e0fb..5b6e37e76 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -6,6 +6,7 @@ import numpy as _np import pyaccel as _pyacc +from mathphys.functions import get_namedtuple as _get_namedtuple from pymodels import si as _si from siriuspy.clientconfigdb import ConfigDBClient from siriuspy.devices import ( @@ -20,7 +21,6 @@ ParamsBaseClass as _ParamsBaseClass, ThreadedMeasBaseClass as _BaseClass, ) -from mathphys.functions import get_namedtuple as _get_namedtuple class ParallelBBAParams(_ParamsBaseClass): @@ -446,21 +446,19 @@ def __init__(self, isonline=True): params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) # self._groups2dopbba: list[PBBAGroup] = list() - self._groups2dopbba: list[_PVName] = list() - self._delta_kl: list[_np.ndarray] = list() self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) - self.data['measure'] = dict() + self.data['measure'] = list() + self.data['groups2dopbba'] = list() + self.data['delta_kl'] = list() + self.data['jacobians'] = list() self._model = None - self._famdata = None - self._jacobians: list[_np.ndarray] = list() + self._fam_data = None if self.isonline: self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) - self.devices['sofb'].nr_points = self.params.sofb_nrpoints - self._sofb_loop_on = self.devices['sofb'].autocorrsts self.devices['currinfosi'] = _CurrInfoSI() self.connect_to_quadrupoles() @@ -474,16 +472,11 @@ def havebeam(self): haveb = self.devices['currinfosi'] return haveb.connected and haveb.storedbeam - @property - def measuredbpms(self): - """.""" - return sorted(self.data['measure']) - @property def groups2dopbba(self): """.""" - # return self._groups2dopbba - return _dcopy(self._groups2dopbba) + # return self.data['groups2dopbba'] + return _dcopy(self.data['groups2dopbba']) # @groups2dopbba.setter # def groups2dopbba(self, groups): @@ -500,11 +493,11 @@ def groups2dopbba(self): # raise ValueError('') # else: # raise ValueError('') - # self._groups2dopbba = pbba_groups_list + # self.data['groups2dopbba'] = pbba_groups_list @groups2dopbba.setter def groups2dopbba(self, groups): - self._groups2dopbba = [ + self.data['groups2dopbba'] = [ [_PVName(bpm) for bpm in group if isinstance(bpm, str)] for group in groups if isinstance(group, (list, tuple, _np.ndarray)) @@ -513,19 +506,59 @@ def groups2dopbba(self, groups): @property def delta_kl(self): """.""" - return _dcopy(self._delta_kl) + return _dcopy(self.data['delta_kl']) @delta_kl.setter def delta_kl(self, value): _max = self.params.quad_deltakl - for i, group in enumerate(self._groups2dopbba): + for i, group in enumerate(self.data['groups2dopbba']): if len(value[i]) != len(group): raise ValueError( f'size mismatch between group {i} and given delta_kl' ) if any([abs(v) > _max for v in value[i]]): raise ValueError(f"values for delta kl can't exceed {_max}") - self._delta_kl = _dcopy(value) + self.data['delta_kl'] = _dcopy(value) + + @property + def jacobians(self): + """.""" + return _dcopy(self.data['jacobians']) + + @jacobians.setter + def jacobians(self, jacs): + """.""" + if len(jacs) != len(self.data['groups2dopbba']): + raise ValueError('Size not compatible.') + self.data['jacobians'] = _dcopy(jacs) + + @property + def model(self): + """.""" + if self._model is None: + print('\n Undefined model... setting a default one') + self._model = _si.create_accelerator() + self._model.cavity_on = True + self._model.radiation_on = 1 + self._model = _si.fitted_models.vertical_dispersion_and_coupling( + self._model + ) + self._fam_data = _si.families.get_family_data(self._model) + return self._model + + @model.setter + def model(self, value): + if not value.cavity_on and value.radiation_on != 1: + raise ValueError( + 'cavity_on must be True and radiation_on must be 1' + ) + self._model = value + self._fam_data = _si.families.get_family_data(self._model) + + @property + def fam_data(self): + """.""" + return self._fam_data def connect_to_quadrupoles(self): """.""" @@ -545,6 +578,8 @@ def get_orbit(self): def correct_orbit(self): """.""" + if not self.havebeam: + return sofb = self.devices['sofb'] sofb.correct_orbit_manually( nr_iters=self.params.sofb_maxcorriter, @@ -554,11 +589,7 @@ def correct_orbit(self): def get_kicks(self): """.""" sofb = self.devices['sofb'] - return _np.hstack(( - list(sofb.kickch), - list(sofb.kickcv), - list(sofb.kickrf), - )) + return _np._r[sofb.kickch, sofb.kickcv, sofb.kickrf] def set_delta_kicks(self, dkicks): """.""" @@ -568,7 +599,7 @@ def set_delta_kicks(self, dkicks): raise ValueError( f'invalid dim for dkicks, must have shape=({nch + ncv + nrf},)' ) - dch, dcv, drf = dkicks[:nch], dkicks[nch:ncv], dkicks[ncv:] + dch, dcv, drf = dkicks[:nch], dkicks[nch : nch + ncv], dkicks[-1] factch, factcv, factrf = ( sofb.mancorrgainch, @@ -592,9 +623,9 @@ def set_delta_kicks(self, dkicks): # #### pbba utils ##### - def set_strengths(self, group_id, strengths, ignore_timeout=False): + def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): """.""" - bpms = self._groups2dopbba[group_id] + bpms = self.data['groups2dopbba'][group_id] quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] for strength, bpmname in zip(strengths, bpms): @@ -602,19 +633,21 @@ def set_strengths(self, group_id, strengths, ignore_timeout=False): quad = self.devices[quadname] quad.strength = strength - if not ignore_timeout: - for strength, bpmname in zip(strengths, bpms): - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - if not quad.wait_float( - 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole - ): - return DoParallelBBA.STATUS.Fail + if ignore_timeout: + return DoParallelBBA.STATUS.Success + + for strength, bpmname in zip(strengths, bpms): + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + if not quad._wait_float( + 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole + ): + return DoParallelBBA.STATUS.Fail return DoParallelBBA.STATUS.Success - def get_strengths(self, group_id): + def get_quad_strengths(self, group_id): """.""" - bpms = self._groups2dopbba[group_id] + bpms = self.data['groups2dopbba'][group_id] quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] @@ -627,96 +660,37 @@ def get_strengths(self, group_id): def meas_ios(self, group_id): """.""" - # delta_strens = self._groups2dopbba[group_id].delta_kl - delta_strens = self._delta_kl[group_id] - strens_orig = self.get_strengths(group_id) - _sts = self.set_strengths(group_id, strens_orig + delta_strens / 2) - if not _sts: + # delta_strens = self.data['groups2dopbba'][group_id].delta_kl + delta_strens = self.data['delta_kl'][group_id] + strens_orig = self.get_quad_strengths(group_id) + if not self.set_quad_strengths( + group_id, strens_orig + delta_strens / 2 + ): return None, DoParallelBBA.STATUS.Fail + orb_pos = self.get_orbit() - _sts = self.set_strengths(group_id, strens_orig - delta_strens / 2) - if not _sts: - return None, DoParallelBBA.STATUS.Fail - orb_neg = self.get_orbit() - _sts = self.set_strengths(group_id, strens_orig) - if not _sts: - return None, DoParallelBBA.STATUS.Fail - return orb_pos - orb_neg, DoParallelBBA.STATUS.Success - @staticmethod - def get_default_quads(model, fam_data): - """.""" - quads_idx = _dcopy(fam_data['QN']['index']) - qs_idx = [idx for idx in fam_data['QS']['index']] - quads_idx.extend(qs_idx) - quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) - quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) - - bpms_idx = _np.array([idx[0] for idx in fam_data['BPM']['index']]) - bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) - - diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) - bba_idx = _np.argmin(diff, axis=1) - quads_bba_idx = quads_idx[bba_idx] - bpmnames = list() - qnames = list() - for i, qidx in enumerate(quads_bba_idx): - name = model[qidx].fam_name - idc = fam_data[name]['index'].index([qidx]) - sub = fam_data[name]['subsection'][idc] - inst = fam_data[name]['instance'][idc] - name = 'QS' if name.startswith(('S', 'F')) else name - qname = 'SI-{0:s}:PS-{1:s}-{2:s}'.format(sub, name, inst) - qnames.append(qname.strip('-')) - - sub = fam_data['BPM']['subsection'][i] - inst = fam_data['BPM']['instance'][i] - bname = 'SI-{0:s}:DI-BPM-{1:s}'.format(sub, inst) - bname = bname.strip('-') - bpmnames.append(bname.strip('-')) - return bpmnames, bpms_idx, qnames, quads_bba_idx + if not self.set_quad_strengths( + group_id, strens_orig - delta_strens / 2 + ): + return None, DoParallelBBA.STATUS.Fail - @property - def model(self): - """.""" - if self._model is None: - print('\n Undefined model... setting a default one') - self._model = _si.create_accelerator() - self._model = _si.fitted_models.vertical_dispersion_and_coupling( - self._model - ) - self._famdata = _si.families.get_family_data(self._model) - return self._model + orb_neg = self.get_orbit() - @model.setter - def model(self, value): - self._model = value - self._famdata = _si.families.get_family_data(self._model) + if not self.set_quad_strengths(group_id, strens_orig): + return None, DoParallelBBA.STATUS.Fail - @property - def fam_data(self): - """.""" - return _dcopy(self._famdata) + return orb_pos - orb_neg, DoParallelBBA.STATUS.Success def calc_ios_jacobians(self, groups_to_calc=None): """Calculate the IOS Response Matrices for all groups.""" model = self.model - fam_data = self.fam_data - use6d = any([model.cavity_on, model.radiation_on > 0]) _orbcorr = _OrbitCorr( - model=model, acc='SI', corr_system='SOFB', use6dtrack=use6d - ) - jacobian_shape = ( - len(_orbcorr.respm.bpm_idx), - len(_orbcorr.respm.ch_idx) - + len(_orbcorr.respm.cv_idx) - + len(_orbcorr.respm.rf_idx), - ) - bnames, _, qnames, quadindices = self.get_default_quads( - model, fam_data + model=model, acc='SI', corr_system='SOFB', use6dtrack=True ) + quadindices = self.get_quads_indices_in_model(quadnames) - def _get_strengths(group): + def _get_quad_strengths(group): strens = [] for bname in group: _id = bnames.index(bname) @@ -727,7 +701,7 @@ def _get_strengths(group): strens.append(model[qidx].KL) return _np.array(strens) - def _set_strengths(group, strengths): + def _set_quad_strengths(group, strengths): for strength, bname in zip(strengths, group): _id = bnames.index(bname) qidx = quadindices[_id] @@ -738,181 +712,116 @@ def _set_strengths(group, strengths): jacobians = [] groups_to_calc = ( - _np.arange(len(self._groups2dopbba)) + _np.arange(len(self.data['groups2dopbba'])) if groups_to_calc is None else groups_to_calc ) - groups_to_calc = [self._groups2dopbba[i] for i in groups_to_calc] - for group in groups_to_calc: + for group_id in groups_to_calc: + group = self.data['groups2dopbba'][group_id] # delta_strens = group.delta_kl try: - delta_strens = self._delta_kl[self._groups2dopbba.index(group)] + delta_strens = self.data['delta_kl'][group_id] except Exception as e: str_msg = 'undefined or empty "delta_kl"' - str_msg += f' of group {self._groups2dopbba.index(group)}' + str_msg += f' of group {group_id}' raise IndexError(str_msg) from e - strens_orig = _get_strengths(group) + strens_orig = _get_quad_strengths(group) - _set_strengths(group, strens_orig + delta_strens / 2) + _set_quad_strengths(group, strens_orig + delta_strens / 2) try: jac_pos = _orbcorr.get_jacobian_matrix() - except Exception: - _set_strengths(group, strens_orig) - jac_pos = _np.full(jacobian_shape, _np.nan) + except Exception as err: + _set_quad_strengths(group, strens_orig) + raise err - _set_strengths(group, strens_orig - delta_strens / 2) + _set_quad_strengths(group, strens_orig - delta_strens / 2) try: jac_neg = _orbcorr.get_jacobian_matrix() - except Exception: - _set_strengths(group, strens_orig) - jac_neg = _np.full(jacobian_shape, _np.nan) + except Exception as err: + _set_quad_strengths(group, strens_orig) + raise err - _set_strengths(group, strens_orig) + _set_quad_strengths(group, strens_orig) jacobians.append(jac_pos - jac_neg) + return jacobians - problems = [] - for i, jac in enumerate(jacobians): - if _np.any(_np.isnan(jac)): - group_id = self._groups2dopbba.index(groups_to_calc[i]) - problems.append(group_id) - if problems: - str_msg = 'Error(s) when obtaining the jacobian' - str_msg += f'for group(s): {problems}' - raise ValueError(str_msg) + def analyze_groups(self): + """Helper function to analyze the groups' properties.""" + if not self.data['jacobians']: + raise ValueError('Please calculate and set jacobians first.') - return jacobians + anl = [] + for group_id in range(len(self.data['groups2dopbba'])): + print(f'Analyzing group: {group_id:d}') + anl.append(self.analyze_group(group_id)) + return anl def analyze_group(self, group_id): """Helper function to analyze group's properties.""" - try: - jacobian = self._jacobians[group_id] - except Exception: - jacobian = self.calc_ios_jacobians(groups_to_calc=[group_id]) + jacobian = self.data['jacobians'][group_id] u_mat, svals, vt_mat = _np.linalg.svd(jacobian) model = self.model - bnames, _, qnames, quadindices = self.get_default_quads( - model, self._famdata - ) - # delta_strens = self._groups2dopbba[group_id].delta_kl - delta_strens = self._delta_kl[group_id] - - tune_variation = [_pyacc.optics.get_frac_tunes(model, dim='4D')] - - for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): - _id = bnames.index(bpm) - qname = qnames[_id] - qidx = quadindices[_id] - if 'QS' in qname: - model[qidx].KsL += dkl / 2 - else: - model[qidx].KL += dkl / 2 - tune_variation.append( - _pyacc.optics.get_frac_tunes(model, dim='4D') - ) + quadindices = self.get_quads_indices_in_model(quadnames) + # delta_strens = self.data['groups2dopbba'][group_id].delta_kl + delta_strens = self.data['delta_kl'][group_id] + group = self.data['groups2dopbba'][group_id] - for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): - _id = bnames.index(bpm) - qname = qnames[_id] - qidx = quadindices[_id] - if 'QS' in qname: - model[qidx].KsL -= dkl - else: - model[qidx].KL -= dkl - tune_variation.append( - _pyacc.optics.get_frac_tunes(model, dim='4D') - ) + tune_variation = [_pyacc.optics.get_frac_tunes(model)[:2]] - for dkl, bpm in zip(delta_strens, self._groups2dopbba[group_id]): - _id = bnames.index(bpm) - qname = qnames[_id] - qidx = quadindices[_id] - if 'QS' in qname: - model[qidx].KsL += dkl / 2 - else: - model[qidx].KL += dkl / 2 - tune_variation.append( - _pyacc.optics.get_frac_tunes(model, dim='4D') - ) + for sign in [1, -1, 1]: + for dkl, bpm in zip(delta_strens, group): + _id = bnames.index(bpm) + qname = qnames[_id] + qidx = quadindices[_id] + if 'QS' in qname: + model[qidx].KsL += sign * dkl / 2 + else: + model[qidx].KL += sign * dkl / 2 + tune_variation.append(_pyacc.optics.get_frac_tunes(model)[:2]) return { - 'U_matrix': u_mat, - 'Vt_matrix': vt_mat, - 'Svals': svals, - 'Tune_variation': tune_variation, + 'u_matrix': u_mat, + 'vt_matrix': vt_mat, + 'svals': svals, + 'tune_variation': tune_variation, } - def analyze_groups(self): - """Helper function to analyze the groups' properties.""" - anl = dict() - for group_id in range(len(self._groups2dopbba)): - print(f'Analyzing group: {group_id:d}') - anl[f'group_{group_id:d}'] = self.analyze_group(group_id) - return anl - - @property - def jacobians(self): - """.""" - return _dcopy(self._jacobians) - - @staticmethod - def inverse_matrix(matrix, rcond=1e-5): - """.""" - return _np.dot( - _np.linalg.pinv(_np.dot(matrix.T, matrix), rcond=rcond), matrix.T - ) - - def exit_graciously(self, group_id, stable_strengths): - """.""" - self.set_strengths(group_id, stable_strengths, ignore_timeout=True) - _time.sleep(self.params.wait_quadrupole) - self.correct_orbit() - - def correct_ios(self, group_id): + # #### private methods #### + def _get_quads_indices_in_model(self, quadnames): """.""" - jacobian = self._jacobians[group_id] - inverse_jacobian = self.inverse_matrix( - jacobian, self.params.inv_jac_rcond - ) - group_name = f'group_{group_id:2d}' - self.data[group_name] = { - 'bpms': self._groups2dopbba[group_id], - 'kicks_ini': self.get_kicks(), - 'strengths_orig': self.get_strengths(group_id), - } - ios_iter, dkicks_iter = [], [] - nr_iters = self.params.corr_nr_iters - print(' correcting IOS:', end='') - for i in range(nr_iters + 1): - if self._stopevt.is_set() or not self.havebeam: - print(' exiting...') - return DoParallelBBA.STATUS.Fail - print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') - ios, sts = self.meas_ios(group_id) - if not sts: - self.exit_graciously( - group_id, - stable_strengths=self.data[group_name]['strengths_orig'], - ) - return DoParallelBBA.STATUS.Fail - ios_iter.append(ios) - if i >= nr_iters: - break - dkicks = list(-1 * _np.dot(inverse_jacobian, ios)) - dkicks_iter.append(dkicks) - self.set_delta_kicks(dkicks) - - self.data[group_name]['kicks_fim'] = self.get_kicks() - self.data[group_name]['ios_iter'] = ios_iter - self.data[group_name]['dkicks_iter'] = dkicks_iter - # self.data[group_name]['delta_kl'] = self._groups2dopbba[ - # group_id - # ].delta_kl - self.data[group_name]['delta_kl'] = self._delta_kl[group_id] + # quads_idx = _dcopy(fam_data['QN']['index']) + # qs_idx = [idx for idx in fam_data['QS']['index']] + # quads_idx.extend(qs_idx) + # quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) + # quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) + + # bpms_idx = _np.array([idx[0] for idx in fam_data['BPM']['index']]) + # bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) + + # diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) + # bba_idx = _np.argmin(diff, axis=1) + # quads_bba_idx = quads_idx[bba_idx] + # bpmnames = list() + # qnames = list() + # for i, qidx in enumerate(quads_bba_idx): + # name = model[qidx].fam_name + # idc = fam_data[name]['index'].index([qidx]) + # sub = fam_data[name]['subsection'][idc] + # inst = fam_data[name]['instance'][idc] + # name = 'QS' if name.startswith(('S', 'F')) else name + # qname = 'SI-{0:s}:PS-{1:s}-{2:s}'.format(sub, name, inst) + # qnames.append(qname.strip('-')) + + # sub = fam_data['BPM']['subsection'][i] + # inst = fam_data['BPM']['instance'][i] + # bname = 'SI-{0:s}:DI-BPM-{1:s}'.format(sub, inst) + # bname = bname.strip('-') + # bpmnames.append(bname.strip('-')) + # return bpmnames, bpms_idx, qnames, quads_bba_idx + quad_indices = _np.zeros(len(quadnames), dtype=int) + return quad_indices - return DoParallelBBA.STATUS.Success - - # #### private methods #### def _do_pbba(self): tini = _datetime.datetime.fromtimestamp(_time.time()) print( @@ -921,14 +830,14 @@ def _do_pbba(self): ) ) - self._jacobians = self.calc_ios_jacobians() + self.data['jacobians'] = self.calc_ios_jacobians() sofb = self.devices['sofb'] - if self._sofb_loop_on: - print('SOFB feedback is enabled, disabling it...') - sofb.cmd_turn_off_autocorr() + if sofb.autocorrsts: + print('SOFB feedback is enabled. Please desable it first.') + return - for i in range(len(self._groups2dopbba)): + for group_id in range(len(self.data['groups2dopbba'])): if self._stopevt.is_set(): print('Stopped!') break @@ -938,11 +847,8 @@ def _do_pbba(self): print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') - self._dopbba_single_group(i) - - if self._sofb_loop_on: - print('SOFB feedback was enabled, restoring original state...') - sofb.cmd_turn_on_autocorr() + if self._dopbba_single_group(group_id): + break tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) @@ -951,26 +857,71 @@ def _do_pbba(self): def _dopbba_single_group(self, group_id): """.""" - bpmnames = self._groups2dopbba[group_id] - bpmnames_all = self.data['bpmnames'] - nbpms = len(bpmnames_all) - tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime('%Hh%Mm%Ss') - print('{:s} --> Doing PBBA for Group {:d}'.format(strtini, group_id)) - - status = self.correct_ios(group_id) - if status: - orbit = self.get_orbit() - for bpm in bpmnames: - idx = bpmnames_all.index(bpm) - self.data['measure'][bpm] = { - 'x0': orbit[idx], - 'y0': orbit[idx + nbpms], - } + print(f'{strtini:s} --> Doing PBBA for Group {group_id:d}') + + jac = self.data['jacobians'][group_id] + inv_jac = _np.linalg.pinv(jac, self.params.inv_jac_rcond) + + group_data = { + 'bpms': self.data['groups2dopbba'][group_id], + 'orbit_init': self.get_orbit(), + 'kicks_init': self.get_kicks(), + 'strengths_init': self.get_quad_strengths(group_id), + } + ios_iter, dkicks_iter = [], [] + nr_iters = self.params.corr_nr_iters + print(' correcting IOS:', end='') + sts = self.STATUS.Fail + for i in range(nr_iters): + print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') + if self._stopevt.is_set() or not self.havebeam: + self._restore_init_conditions( + group_id, + init_strengths=group_data['strengths_init'], + ) + print(' exiting...') + break + ios, sts = self.meas_ios(group_id) + if not sts: + self._restore_init_conditions( + group_id, + init_strengths=group_data['strengths_init'], + ) + break + ios_iter.append(ios) + dkicks = list(-1 * _np.dot(inv_jac, ios)) + dkicks_iter.append(dkicks) + self.set_delta_kicks(dkicks) + print('Done.') + else: + sts = self.STATUS.Success + + group_data['kicks_end'] = self.get_kicks() + group_data['ios_iter'] = ios_iter + group_data['dkicks_iter'] = dkicks_iter + group_data['orbit_end'] = self.get_orbit() + # group_data['delta_kl'] = self.data['groups2dopbba'][ + # group_id + # ].delta_kl + group_data['delta_kl'] = self.data['delta_kl'][group_id] + self.data['measure'].append(group_data) + + self.correct_orbit() + tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] - if status: + if sts: print('Done! Elapsed time: {:s}\n'.format(dtime)) print('Fail! Elapsed time: {:s}\n'.format(dtime)) + return sts + + def _restore_init_conditions(self, group_id, init_strengths): + """.""" + self.set_quad_strengths( + group_id, init_strengths, ignore_timeout=True + ) + _time.sleep(self.params.wait_quadrupole) + self.correct_orbit() From 827903d8cb511f2742581fb2ca9f75253418db1e Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 1 Oct 2025 14:43:18 -0300 Subject: [PATCH 16/61] COMMISS.PBBA.WIP: add __str__ methods.. --- apsuite/commisslib/measure_pbba.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 5b6e37e76..fd8f70fb8 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -373,7 +373,17 @@ def __init__(self): def __str__(self): """.""" - return '...' + stg = '' + stg += f'quad_deltakl = {self.quad_deltakl:.3f}' + stg += f'wait_correctors = {self.wait_correctors:.3f}' + stg += f'wait_quadrupole = {self.wait_quadrupole:.3f}' + stg += f'timeout_wait_orbit = {self.timeout_wait_orbit:.3f}' + stg += f'corr_nr_iters = {self.corr_nr_iters:.3f}' + stg += f'inv_jac_rcond = {self.inv_jac_rcond:.3f}' + stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}' + stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}' + stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}' + return stg class BBAPairPVName(_PVName): @@ -464,7 +474,7 @@ def __init__(self, isonline=True): def __str__(self): """.""" - return '...' + return str(self.params) @property def havebeam(self): From 39f9f4752724ff2e5b40728f75ad36a3d0300dff Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 17:02:54 -0300 Subject: [PATCH 17/61] WIP: add default groups and deltas KL --- apsuite/commisslib/measure_pbba.py | 49 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index fd8f70fb8..2f1fc2b7b 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -385,6 +385,49 @@ def __str__(self): stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}' return stg + @staticmethod + def get_default_groups(): + """.""" + return [ + sorted( + [ + e + for e in ParallelBBAParams.BPMNAMES + if (e.sub[2:], e.idx) in k + ], + key=lambda x: { + 'Q4': 0, + 'Q1': 1, + 'QDB2': 2, + 'QDP2': 3, + 'QS': 4, + }.get( + ParallelBBAParams.QUADNAMES[ + ParallelBBAParams.BPMNAMES.index(x) + ].dev, + 999, + ), + ) + for k in [ + [('M2', ''), ('C3', '1')], + [('C1', '1'), ('C3', '2')], + [('C1', '2'), ('C4', '')], + [('C2', ''), ('M1', '')], + ] + ] + + def get_default_dkl(self, groups=None): + """.""" + groups = ( + ParallelBBAParams.get_default_groups() + if groups is None + else groups + ) + dkl = [_np.ones(len(g)) * self.quad_deltakl for g in groups] + for d in dkl: + d[::2] *= -1 + return dkl + class BBAPairPVName(_PVName): """.""" @@ -461,8 +504,10 @@ def __init__(self, isonline=True): self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) self.data['measure'] = list() - self.data['groups2dopbba'] = list() - self.data['delta_kl'] = list() + self.data['groups2dopbba'] = ParallelBBAParams.get_default_groups() + self.data['delta_kl'] = self.params.get_default_dkl( + self.data['groups2dopbba'] + ) self.data['jacobians'] = list() self._model = None self._fam_data = None From f0debcd8f80b47b75d7503aaafab1dc19eaef7c4 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 17:04:33 -0300 Subject: [PATCH 18/61] WIP: removing comments --- apsuite/commisslib/measure_pbba.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 2f1fc2b7b..fc2b7c9ac 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -498,7 +498,6 @@ def __init__(self, isonline=True): super().__init__( params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) - # self._groups2dopbba: list[PBBAGroup] = list() self.clt_confdb = ConfigDBClient(config_type='si_bbadata') self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) @@ -530,26 +529,8 @@ def havebeam(self): @property def groups2dopbba(self): """.""" - # return self.data['groups2dopbba'] return _dcopy(self.data['groups2dopbba']) - # @groups2dopbba.setter - # def groups2dopbba(self, groups): - # pbba_groups_list = [] - # for group in groups: - # if isinstance(group, PBBAGroup): - # pbba_groups_list.append(group) - # elif isinstance(group, (list, tuple, _np.ndarray)): - # if all(isinstance(x, str) for x in group): - # pbba_groups_list.append(PBBAGroup(group)) - # elif all(isinstance(x, BBAPairPVName) for x in group): - # pbba_groups_list.append(PBBAGroup(group)) - # else: - # raise ValueError('') - # else: - # raise ValueError('') - # self.data['groups2dopbba'] = pbba_groups_list - @groups2dopbba.setter def groups2dopbba(self, groups): self.data['groups2dopbba'] = [ @@ -715,7 +696,6 @@ def get_quad_strengths(self, group_id): def meas_ios(self, group_id): """.""" - # delta_strens = self.data['groups2dopbba'][group_id].delta_kl delta_strens = self.data['delta_kl'][group_id] strens_orig = self.get_quad_strengths(group_id) if not self.set_quad_strengths( @@ -773,7 +753,6 @@ def _set_quad_strengths(group, strengths): ) for group_id in groups_to_calc: group = self.data['groups2dopbba'][group_id] - # delta_strens = group.delta_kl try: delta_strens = self.data['delta_kl'][group_id] except Exception as e: @@ -957,9 +936,6 @@ def _dopbba_single_group(self, group_id): group_data['ios_iter'] = ios_iter group_data['dkicks_iter'] = dkicks_iter group_data['orbit_end'] = self.get_orbit() - # group_data['delta_kl'] = self.data['groups2dopbba'][ - # group_id - # ].delta_kl group_data['delta_kl'] = self.data['delta_kl'][group_id] self.data['measure'].append(group_data) From 2ce837f4247f291ae22409cd90eb1eed9cd0eea1 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 17:06:15 -0300 Subject: [PATCH 19/61] WIP/ENH: enhancing/fixing/completing some functions --- apsuite/commisslib/measure_pbba.py | 90 ++++++++++-------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index fc2b7c9ac..556bd4ffd 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -723,27 +723,26 @@ def calc_ios_jacobians(self, groups_to_calc=None): _orbcorr = _OrbitCorr( model=model, acc='SI', corr_system='SOFB', use6dtrack=True ) - quadindices = self.get_quads_indices_in_model(quadnames) + quadindices = self._get_quads_indices_in_model(self.data['quadnames']) + + def _get_or_set_kl(bname, value=None): + _do = getattr if value is None else setattr + bidx = self.data['bpmnames'].index(bname) + qidx = quadindices[bidx] + if 'QS' == self.data['quadnames'][bidx].dev: + return _do(model[qidx], 'KsL', value) + else: + return _do(model[qidx], 'KL', value) def _get_quad_strengths(group): strens = [] for bname in group: - _id = bnames.index(bname) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - strens.append(model[qidx].KsL) - else: - strens.append(model[qidx].KL) + strens.append(_get_or_set_kl(bname)) return _np.array(strens) def _set_quad_strengths(group, strengths): for strength, bname in zip(strengths, group): - _id = bnames.index(bname) - qidx = quadindices[_id] - if 'QS' in qnames[_id]: - model[qidx].KsL = strength - else: - model[qidx].KL = strength + _get_or_set_kl(bname, strength) jacobians = [] groups_to_calc = ( @@ -796,65 +795,42 @@ def analyze_group(self, group_id): u_mat, svals, vt_mat = _np.linalg.svd(jacobian) model = self.model - quadindices = self.get_quads_indices_in_model(quadnames) - # delta_strens = self.data['groups2dopbba'][group_id].delta_kl + quadindices = self._get_quads_indices_in_model(self.data['quadnames']) delta_strens = self.data['delta_kl'][group_id] group = self.data['groups2dopbba'][group_id] tune_variation = [_pyacc.optics.get_frac_tunes(model)[:2]] - for sign in [1, -1, 1]: + for fac in [1, -2, 1]: for dkl, bpm in zip(delta_strens, group): - _id = bnames.index(bpm) - qname = qnames[_id] + _id = self.data['bpmnames'].index(bpm) + qname = self.data['quadnames'][_id] qidx = quadindices[_id] if 'QS' in qname: - model[qidx].KsL += sign * dkl / 2 + model[qidx].KsL += fac * dkl / 2 else: - model[qidx].KL += sign * dkl / 2 + model[qidx].KL += fac * dkl / 2 tune_variation.append(_pyacc.optics.get_frac_tunes(model)[:2]) return { 'u_matrix': u_mat, 'vt_matrix': vt_mat, 'svals': svals, - 'tune_variation': tune_variation, + 'tune_variation': _np.array(tune_variation), } # #### private methods #### def _get_quads_indices_in_model(self, quadnames): """.""" - # quads_idx = _dcopy(fam_data['QN']['index']) - # qs_idx = [idx for idx in fam_data['QS']['index']] - # quads_idx.extend(qs_idx) - # quads_idx = _np.array([idx[len(idx) // 2] for idx in quads_idx]) - # quads_pos = _np.array(_pyacc.lattice.find_spos(model, quads_idx)) - - # bpms_idx = _np.array([idx[0] for idx in fam_data['BPM']['index']]) - # bpms_pos = _np.array(_pyacc.lattice.find_spos(model, bpms_idx)) - - # diff = _np.abs(bpms_pos[:, None] - quads_pos[None, :]) - # bba_idx = _np.argmin(diff, axis=1) - # quads_bba_idx = quads_idx[bba_idx] - # bpmnames = list() - # qnames = list() - # for i, qidx in enumerate(quads_bba_idx): - # name = model[qidx].fam_name - # idc = fam_data[name]['index'].index([qidx]) - # sub = fam_data[name]['subsection'][idc] - # inst = fam_data[name]['instance'][idc] - # name = 'QS' if name.startswith(('S', 'F')) else name - # qname = 'SI-{0:s}:PS-{1:s}-{2:s}'.format(sub, name, inst) - # qnames.append(qname.strip('-')) - - # sub = fam_data['BPM']['subsection'][i] - # inst = fam_data['BPM']['instance'][i] - # bname = 'SI-{0:s}:DI-BPM-{1:s}'.format(sub, inst) - # bname = bname.strip('-') - # bpmnames.append(bname.strip('-')) - # return bpmnames, bpms_idx, qnames, quads_bba_idx - quad_indices = _np.zeros(len(quadnames), dtype=int) - return quad_indices + fam_data = self.fam_data + quadindices = [] + for qname in quadnames: + key = qname.dev + idx = fam_data[key]['devnames'].index(qname) + qindex = fam_data[key]['index'][idx] + qindex = qindex[0] if len(qindex) == 1 else qindex + quadindices.append(qindex) + return quadindices def _do_pbba(self): tini = _datetime.datetime.fromtimestamp(_time.time()) @@ -912,16 +888,14 @@ def _dopbba_single_group(self, group_id): print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') if self._stopevt.is_set() or not self.havebeam: self._restore_init_conditions( - group_id, - init_strengths=group_data['strengths_init'], + group_id, init_strengths=group_data['strengths_init'] ) print(' exiting...') break ios, sts = self.meas_ios(group_id) if not sts: self._restore_init_conditions( - group_id, - init_strengths=group_data['strengths_init'], + group_id, init_strengths=group_data['strengths_init'] ) break ios_iter.append(ios) @@ -951,8 +925,6 @@ def _dopbba_single_group(self, group_id): def _restore_init_conditions(self, group_id, init_strengths): """.""" - self.set_quad_strengths( - group_id, init_strengths, ignore_timeout=True - ) + self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) _time.sleep(self.params.wait_quadrupole) self.correct_orbit() From 18d4049692841a30d03b56193c594ce069f842c7 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 18:46:40 -0300 Subject: [PATCH 20/61] WIP: meas final ios --- apsuite/commisslib/measure_pbba.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 556bd4ffd..9926415df 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -906,6 +906,14 @@ def _dopbba_single_group(self, group_id): else: sts = self.STATUS.Success + # final ios + ios, sts = self.meas_ios(group_id) + if not sts: + self._restore_init_conditions( + group_id, init_strengths=group_data['strengths_init'] + ) + ios_iter.append(ios) + group_data['kicks_end'] = self.get_kicks() group_data['ios_iter'] = ios_iter group_data['dkicks_iter'] = dkicks_iter From 8bb65254bd733b47bdd38bc897fd180e8503ed9e Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 18:47:31 -0300 Subject: [PATCH 21/61] WIP: prevent reading orbit if beam is down --- apsuite/commisslib/measure_pbba.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 9926415df..69eaaf823 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -607,6 +607,8 @@ def connect_to_quadrupoles(self): def get_orbit(self): """.""" + if not self.havebeam: + return sofb = self.devices['sofb'] sofb.cmd_reset() sofb.wait_buffer(self.params.timeout_wait_orbit) From e51ba9979406ceb8bdcae8be64dfeb68516f50fa Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 1 Oct 2025 18:47:48 -0300 Subject: [PATCH 22/61] WIP: add processing functions --- apsuite/commisslib/measure_pbba.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 69eaaf823..ff80bc51d 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -821,6 +821,57 @@ def analyze_group(self, group_id): 'tune_variation': _np.array(tune_variation), } + def process_data(self): + """.""" + for group_id in range(len(self.data['groups2dopbba'])): + self.process_data_single_group(group_id) + + def process_data_single_group(self, group_id): + """.""" + meas_data = self.data['measure'][group_id] + bpmnames = self.data['bpmnames'] + nbpms = len(bpmnames) + orbit = meas_data['orbit_end'] + + # #### error estimation ? ##### + # ios_iter = meas_data['ios_iter'] + # ios_init = ios_iter[0] + # iosx_init, iosy_init = ios_init[:nbpms], ios_init[nbpms:] + # ios_end = ios_iter[-1] + # iosx_end, iosy_end = ios_end[:nbpms], ios_end[nbpms:] + stdx0 = 0.0 + stdy0 = 0.0 + + for bpm in meas_data['bpms']: + bpm_idx = bpmnames.index(bpm) + self.analysis[bpm] = { + 'x0': orbit[bpm_idx], + 'y0': orbit[bpm_idx + nbpms], + 'stdx0': stdx0, + 'stdy0': stdy0 + } + + def get_pbba_results(self, error=False): + """.""" + bpms = self.data['bpmnames'] + bbax = _np.zeros(len(bpms)) + bbay = _np.zeros(len(bpms)) + if error: + bbaxerr = _np.zeros(len(bpms)) + bbayerr = _np.zeros(len(bpms)) + for idx, bpm in enumerate(bpms): + res = self.analysis.get(bpm) + if not res: + continue + bbax[idx] = res['x0'] + bbay[idx] = res['y0'] + if error and 'stdx0' in res: + bbaxerr[idx] = res['stdx0'] + bbayerr[idx] = res['stdy0'] + if error: + return bbax, bbay, bbaxerr, bbayerr + return bbax, bbay + # #### private methods #### def _get_quads_indices_in_model(self, quadnames): """.""" From 91625ae9c933d41e511d27e9649f3275aae9f059 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 3 Oct 2025 10:23:10 -0300 Subject: [PATCH 23/61] WIP: delete unused class --- apsuite/commisslib/measure_pbba.py | 59 ------------------------------ 1 file changed, 59 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index ff80bc51d..e6b82d1d1 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -429,65 +429,6 @@ def get_default_dkl(self, groups=None): return dkl -class BBAPairPVName(_PVName): - """.""" - - def __new__( - cls, - pv_name, - associated_quad_str=None, - associated_dkl=0.0, - elements=None, - ): - """.""" - if 'BPM' in pv_name: - obj = super().__new__(cls, pv_name, elements) - if associated_quad_str is None: - associated_quad_str = ParallelBBAParams.QUADNAMES[ - ParallelBBAParams.BPMNAMES.index(pv_name) - ] - obj.associated_quad = _PVName(str(associated_quad_str)) - obj.associated_dkl = associated_dkl - return obj - else: - raise ValueError('Creation restricted for BPMs') - - -class PBBAGroup(list[BBAPairPVName]): - """Lista de SiriusPVNames (BPMs + metadados).""" - - def __init__(self, iterable=()): - """.""" - if all(isinstance(_, BBAPairPVName) for _ in iterable): - super().__init__(iterable) - elif all(isinstance(_, str) for _ in iterable): - iterable = [ - BBAPairPVName( - bname, - ParallelBBAParams.QUADNAMES[ - ParallelBBAParams.BPMNAMES.index(bname) - ], - ) - for bname in iterable - if bname in ParallelBBAParams.BPMNAMES - ] - super().__init__(iterable) - else: - raise ValueError('') - - @property - def delta_kl(self): - """.""" - return _np.array([_.associated_dkl for _ in self]) - - @delta_kl.setter - def delta_kl(self, value): - if len(value) != len(self): - raise ValueError - for i, val in enumerate(value): - self[i].associated_dkl = val - - class DoParallelBBA(_BaseClass): """.""" From 5599805be2e8b99517ae437740632fa2297c44f8 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 3 Oct 2025 15:04:12 -0300 Subject: [PATCH 24/61] ADD: option to analyze coupling --- apsuite/commisslib/measure_pbba.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index e6b82d1d1..920551e3a 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -721,7 +721,7 @@ def _set_quad_strengths(group, strengths): jacobians.append(jac_pos - jac_neg) return jacobians - def analyze_groups(self): + def analyze_groups(self, analyze_coupling=False): """Helper function to analyze the groups' properties.""" if not self.data['jacobians']: raise ValueError('Please calculate and set jacobians first.') @@ -729,10 +729,10 @@ def analyze_groups(self): anl = [] for group_id in range(len(self.data['groups2dopbba'])): print(f'Analyzing group: {group_id:d}') - anl.append(self.analyze_group(group_id)) + anl.append(self.analyze_group(group_id, analyze_coupling)) return anl - def analyze_group(self, group_id): + def analyze_group(self, group_id, analyze_coupling=False): """Helper function to analyze group's properties.""" jacobian = self.data['jacobians'][group_id] u_mat, svals, vt_mat = _np.linalg.svd(jacobian) @@ -744,6 +744,16 @@ def analyze_group(self, group_id): tune_variation = [_pyacc.optics.get_frac_tunes(model)[:2]] + if analyze_coupling: + def _get_coupling_parameters(): + ed = _pyacc.optics.calc_edwards_teng(model)[0] + mtsp, ratio = _pyacc.optics.estimate_coupling_parameters(ed) + return mtsp, _np.std(ratio) + + min_tunesep, std_ratio = _get_coupling_parameters() + min_tunesep_variation = [min_tunesep] + std_ratio_variation = [std_ratio] + for fac in [1, -2, 1]: for dkl, bpm in zip(delta_strens, group): _id = self.data['bpmnames'].index(bpm) @@ -754,13 +764,21 @@ def analyze_group(self, group_id): else: model[qidx].KL += fac * dkl / 2 tune_variation.append(_pyacc.optics.get_frac_tunes(model)[:2]) + if analyze_coupling: + min_tunesep, std_ratio = _get_coupling_parameters() + min_tunesep_variation.append(min_tunesep) + std_ratio_variation.append(std_ratio) - return { + ret = { 'u_matrix': u_mat, 'vt_matrix': vt_mat, 'svals': svals, 'tune_variation': _np.array(tune_variation), } + if analyze_coupling: + ret['min_tunesep_variation'] = _np.array(min_tunesep_variation) + ret['std_ratio_variation'] = _np.array(std_ratio_variation) + return ret def process_data(self): """.""" From 1c1c7f64d58dc189f711dfd39a691e163f8dd205 Mon Sep 17 00:00:00 2001 From: Vitor Date: Sun, 12 Oct 2025 22:23:24 -0300 Subject: [PATCH 25/61] MNT: disable ruff rule B905 (zip missing strict) ~ python 3.10 --- apsuite/commisslib/measure_pbba.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 920551e3a..79f46417c 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -607,7 +607,7 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): bpms = self.data['groups2dopbba'][group_id] quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] - for strength, bpmname in zip(strengths, bpms): + for strength, bpmname in zip(strengths, bpms): # noqa: B905 quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] quad.strength = strength @@ -615,7 +615,7 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): if ignore_timeout: return DoParallelBBA.STATUS.Success - for strength, bpmname in zip(strengths, bpms): + for strength, bpmname in zip(strengths, bpms): # noqa: B905 quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad._wait_float( @@ -684,7 +684,7 @@ def _get_quad_strengths(group): return _np.array(strens) def _set_quad_strengths(group, strengths): - for strength, bname in zip(strengths, group): + for strength, bname in zip(strengths, group): # noqa: B905 _get_or_set_kl(bname, strength) jacobians = [] @@ -755,7 +755,7 @@ def _get_coupling_parameters(): std_ratio_variation = [std_ratio] for fac in [1, -2, 1]: - for dkl, bpm in zip(delta_strens, group): + for dkl, bpm in zip(delta_strens, group): # noqa: B905 _id = self.data['bpmnames'].index(bpm) qname = self.data['quadnames'][_id] qidx = quadindices[_id] From 0b867c91f938b1d06db962d01c8b332750bad26a Mon Sep 17 00:00:00 2001 From: Vitor Date: Sun, 12 Oct 2025 22:25:37 -0300 Subject: [PATCH 26/61] FIX: calc EdwardsTeng params only in 4D --- apsuite/commisslib/measure_pbba.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 79f46417c..6cf4a8184 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -746,8 +746,13 @@ def analyze_group(self, group_id, analyze_coupling=False): if analyze_coupling: def _get_coupling_parameters(): + rad_on, cav_on = model.radiation_on, model.cavity_on + model.radiation_on = 0 + model.cavity_on = False ed = _pyacc.optics.calc_edwards_teng(model)[0] mtsp, ratio = _pyacc.optics.estimate_coupling_parameters(ed) + model.radiation_on = rad_on + model.cavity_on = cav_on return mtsp, _np.std(ratio) min_tunesep, std_ratio = _get_coupling_parameters() From 7868346b360b5243739986f0eed36808220504b9 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 13 Oct 2025 09:57:10 -0300 Subject: [PATCH 27/61] MNT: fix __str__ --- apsuite/commisslib/measure_pbba.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 6cf4a8184..513f9e2a4 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -374,15 +374,15 @@ def __init__(self): def __str__(self): """.""" stg = '' - stg += f'quad_deltakl = {self.quad_deltakl:.3f}' - stg += f'wait_correctors = {self.wait_correctors:.3f}' - stg += f'wait_quadrupole = {self.wait_quadrupole:.3f}' - stg += f'timeout_wait_orbit = {self.timeout_wait_orbit:.3f}' - stg += f'corr_nr_iters = {self.corr_nr_iters:.3f}' - stg += f'inv_jac_rcond = {self.inv_jac_rcond:.3f}' - stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}' - stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}' - stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}' + stg += f'quad_deltakl = {self.quad_deltakl:.3f}\n' + stg += f'wait_correctors = {self.wait_correctors:.3f}\n' + stg += f'wait_quadrupole = {self.wait_quadrupole:.3f}\n' + stg += f'timeout_wait_orbit = {self.timeout_wait_orbit:.3f}\n' + stg += f'corr_nr_iters = {self.corr_nr_iters:.3f}\n' + stg += f'inv_jac_rcond = {self.inv_jac_rcond:.3e}\n' + stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' + stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' + stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' return stg @staticmethod From 6f2f3a762f2482edb82d546929c28586995c89d5 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 13 Oct 2025 10:04:49 -0300 Subject: [PATCH 28/61] FIX: _dopbba_single_group always printing "Fail" when ending --- apsuite/commisslib/measure_pbba.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 513f9e2a4..36075e029 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -945,7 +945,8 @@ def _dopbba_single_group(self, group_id): dtime = dtime.split('.')[0] if sts: print('Done! Elapsed time: {:s}\n'.format(dtime)) - print('Fail! Elapsed time: {:s}\n'.format(dtime)) + else: + print('Fail! Elapsed time: {:s}\n'.format(dtime)) return sts def _restore_init_conditions(self, group_id, init_strengths): From 453190b50fabf876f3949142bd0cca007e446f5a Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 13 Oct 2025 11:06:21 -0300 Subject: [PATCH 29/61] ENH: add DoParallelBBA __str__ --- apsuite/commisslib/measure_pbba.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 36075e029..b724b175d 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -459,7 +459,29 @@ def __init__(self, isonline=True): def __str__(self): """.""" - return str(self.params) + stn = 'Params\n' + stp = self.params.__str__() + stp = ' ' + stp.replace('\n', '\n ') + stn += stp + '\n' + connected = str(self.connected & len(self.devices.keys()) > 0) + stn += 'Connected? ' + connected + '\n\n' + stn += ' {:^20s} {:^20s} {:^7s}\n'.format( + 'BPM', 'Quad', 'dKL' + ) + tmplt = '{:03d}: {:^20s} {:^20s} {:+.3f}\n' + dta = self.data + for group_id, group in enumerate(self.data['groups2dopbba']): + stn += f'> Group {group_id:03d}\n' + for j, bpm in enumerate(group): + idx = dta['bpmnames'].index(bpm) + stn += tmplt.format( + idx, + dta['bpmnames'][idx], + dta['quadnames'][idx], + dta['delta_kl'][group_id][j] + ) + stn += '\n' + return stn @property def havebeam(self): From 4d2879b8528a1bda77a2c1c7f40a305439cfc742 Mon Sep 17 00:00:00 2001 From: ximenes Date: Mon, 13 Oct 2025 17:41:38 -0300 Subject: [PATCH 30/61] COMMISS.PBBA.FIX: changes made during first machine study. --- apsuite/commisslib/measure_pbba.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index b724b175d..97fedb7bb 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -418,11 +418,7 @@ def get_default_groups(): def get_default_dkl(self, groups=None): """.""" - groups = ( - ParallelBBAParams.get_default_groups() - if groups is None - else groups - ) + groups = self.get_default_groups() if groups is None else groups dkl = [_np.ones(len(g)) * self.quad_deltakl for g in groups] for d in dkl: d[::2] *= -1 @@ -439,8 +435,6 @@ def __init__(self, isonline=True): super().__init__( params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) - self.clt_confdb = ConfigDBClient(config_type='si_bbadata') - self.clt_confdb._TIMEOUT_DEFAULT = 20 self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) self.data['measure'] = list() @@ -590,7 +584,7 @@ def correct_orbit(self): def get_kicks(self): """.""" sofb = self.devices['sofb'] - return _np._r[sofb.kickch, sofb.kickcv, sofb.kickrf] + return _np.r_[sofb.kickch, sofb.kickcv, sofb.kickrf] def set_delta_kicks(self, dkicks): """.""" @@ -620,7 +614,9 @@ def set_delta_kicks(self, dkicks): dcv * 0, drf * 0, ) - sofb.mancorrgainch, sofb.mancorrgaincv = factch, factcv, factrf + sofb.mancorrgainch, sofb.mancorrgaincv, sofb.mancorrgainrf = ( + factch, factcv, factrf + ) # #### pbba utils ##### @@ -740,7 +736,9 @@ def _set_quad_strengths(group, strengths): raise err _set_quad_strengths(group, strens_orig) - jacobians.append(jac_pos - jac_neg) + jac = jac_pos - jac_neg + jac[:, -1] *= 1e6 # rescale: [m/Hz] -> [um/Hz] + jacobians.append(jac) return jacobians def analyze_groups(self, analyze_coupling=False): @@ -880,6 +878,7 @@ def _do_pbba(self): ) self.data['jacobians'] = self.calc_ios_jacobians() + self.data['measure'] = list() sofb = self.devices['sofb'] if sofb.autocorrsts: From 0650b27452e6f28b0c947ea2b4c9a7a6b485620e Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 24 Oct 2025 20:49:33 -0300 Subject: [PATCH 31/61] BUG/FIX: trying to find and fix "remnant KL" error --- apsuite/commisslib/measure_pbba.py | 118 +++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 23 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 97fedb7bb..1c48162cf 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -8,7 +8,6 @@ import pyaccel as _pyacc from mathphys.functions import get_namedtuple as _get_namedtuple from pymodels import si as _si -from siriuspy.clientconfigdb import ConfigDBClient from siriuspy.devices import ( CurrInfoSI as _CurrInfoSI, PowerSupply as _PowerSupply, @@ -354,6 +353,8 @@ class ParallelBBAParams(_ParamsBaseClass): BPMNAMES = tuple([_PVName(bpm) for bpm in BPMNAMES]) QUADNAMES = tuple([_PVName(quad) for quad in QUADNAMES]) + KLMON_TYPES = ['KLRef-Mon', 'KL-RB'] + def __init__(self): """.""" super().__init__() @@ -370,6 +371,7 @@ def __init__(self): self.sofb_nrpoints = 20 self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] + self._kl_pv_2track = 'KLRef-Mon' def __str__(self): """.""" @@ -383,6 +385,7 @@ def __str__(self): stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' + stg += f'kl_pv_2track = {self._kl_pv_2track}\n' return stg @staticmethod @@ -424,6 +427,19 @@ def get_default_dkl(self, groups=None): d[::2] *= -1 return dkl + @property + def kl_pv_2track(self): + """.""" + return self._kl_pv_2track + + @kl_pv_2track.setter + def kl_pv_2track(self, value): + """.""" + if value in ParallelBBAParams.KLMON_TYPES: + self._kl_pv_2track = value + else: + raise ValueError('Choose between: ', ParallelBBAParams.KLMON_TYPES) + class DoParallelBBA(_BaseClass): """.""" @@ -446,6 +462,9 @@ def __init__(self, isonline=True): self._model = None self._fam_data = None + self.data['KLRef_strengths'] = dict() + self.data['KL_init_strengths'] = dict() + if self.isonline: self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) self.devices['currinfosi'] = _CurrInfoSI() @@ -559,8 +578,41 @@ def connect_to_quadrupoles(self): if qname in self.devices: continue self.devices[qname] = _PowerSupply( - qname, props2init=('PwrState-Sts', 'KL-SP', 'KL-RB') + qname, + props2init=('PwrState-Sts', 'KL-SP', 'KL-RB', 'KLRef-Mon') ) + self.data['KLRef_strengths'] = self.devices[qname].strengthref_mon + self.data['KL_init_strengths'] = self.devices[qname].strength + + def check_strengths(self): + """.""" + print('Checking strengths... ', end='') + if self.params.kl_pv_2track == 'KLRef-Mon': + init_strengths = self.data['KLRef_strengths'] + else: + init_strengths = self.data['KL_init_strengths'] + if not init_strengths: + print('Make sure quadrupoles are connected!') + return + sts = DoParallelBBA.STATUS.Success + for qname in self.data['quadnames']: + if qname not in self.devices.keys(): + print(f'\n{qname}: offline') + else: + delta_kl = ( + self.devices[qname] + - + init_strengths[qname] + ) + if abs(delta_kl) > 0.001: + msg = f'\n{qname}: KL-RB (now)' + msg += f' - {self.params.kl_pv_2track} (initial)' + msg += f' = {delta_kl}' + print(msg) + sts = DoParallelBBA.STATUS.Fail + if sts: + print('OK!') + return sts def get_orbit(self): """.""" @@ -637,7 +689,11 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad._wait_float( - 'KLRef-Mon', strength, timeout=self.params.wait_quadrupole + 'KL-RB', # or KL-Mon ? + strength, + rel_tol=0.0, + abs_tol=0.1*self.params.quad_deltakl, + timeout=self.params.wait_quadrupole ): return DoParallelBBA.STATUS.Fail return DoParallelBBA.STATUS.Success @@ -892,7 +948,10 @@ def _do_pbba(self): if not self.havebeam: print('Beam was Lost') break - print('\nCorrecting Orbit... ', end='') + if not self.check_strengths(): + msg = "Restoring initial strengths...\n" + self._restore_init_conditions(massage=msg, correct_orbit=False) + print('Correcting Orbit... ', end='') self.correct_orbit() print('Ok!') if self._dopbba_single_group(group_id): @@ -916,7 +975,6 @@ def _dopbba_single_group(self, group_id): 'bpms': self.data['groups2dopbba'][group_id], 'orbit_init': self.get_orbit(), 'kicks_init': self.get_kicks(), - 'strengths_init': self.get_quad_strengths(group_id), } ios_iter, dkicks_iter = [], [] nr_iters = self.params.corr_nr_iters @@ -925,31 +983,22 @@ def _dopbba_single_group(self, group_id): for i in range(nr_iters): print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') if self._stopevt.is_set() or not self.havebeam: - self._restore_init_conditions( - group_id, init_strengths=group_data['strengths_init'] - ) - print(' exiting...') + self._restore_init_conditions() break ios, sts = self.meas_ios(group_id) if not sts: - self._restore_init_conditions( - group_id, init_strengths=group_data['strengths_init'] - ) + self._restore_init_conditions() break ios_iter.append(ios) dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) print('Done.') - else: - sts = self.STATUS.Success # final ios ios, sts = self.meas_ios(group_id) if not sts: - self._restore_init_conditions( - group_id, init_strengths=group_data['strengths_init'] - ) + self._restore_init_conditions() ios_iter.append(ios) group_data['kicks_end'] = self.get_kicks() @@ -965,13 +1014,36 @@ def _dopbba_single_group(self, group_id): dtime = str(tfin - tini) dtime = dtime.split('.')[0] if sts: - print('Done! Elapsed time: {:s}\n'.format(dtime)) + print('Finished. Status: OK! Elapsed time: {:s}\n'.format(dtime)) else: - print('Fail! Elapsed time: {:s}\n'.format(dtime)) + print('Finished. Status: Fail! Elapsed time: {:s}\n'.format(dtime)) return sts - def _restore_init_conditions(self, group_id, init_strengths): + def _restore_init_conditions(self, + message="Restoring initial conditions and exiting...\n", + correct_orbit=True + ): """.""" - self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) - _time.sleep(self.params.wait_quadrupole) - self.correct_orbit() + print(message, end='') + if self.params.kl_pv_2track == 'KLRef-Mon': + init_strengths = self.data['KLRef_strengths'] + else: + init_strengths = self.data['KL_init_strengths'] + + for qname in self.data['quadnames']: + quad = self.devices[qname] + quad.strength = init_strengths[qname] + + for qname in self.data['quadnames']: + quad = self.devices[qname] + if not quad._wait_float( + 'KL-RB', # or KL-Mon ? + init_strengths[qname], + rel_tol=0.0, + abs_tol=0.1 * self.params.quad_deltakl, + timeout=self.params.wait_quadrupole + ): + print(f'{qname}: could not be restored to initial strenght') + + if correct_orbit: + self.correct_orbit() From 3391ae54dcd19ff959b339a8489152d469e7adeb Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 24 Oct 2025 22:01:33 -0300 Subject: [PATCH 32/61] MNT: prints stardardization --- apsuite/commisslib/measure_pbba.py | 63 +++++++++++++++++++----------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 1c48162cf..7fccae605 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -597,7 +597,7 @@ def check_strengths(self): sts = DoParallelBBA.STATUS.Success for qname in self.data['quadnames']: if qname not in self.devices.keys(): - print(f'\n{qname}: offline') + print(f'\n {qname}: offline') else: delta_kl = ( self.devices[qname] @@ -605,7 +605,7 @@ def check_strengths(self): init_strengths[qname] ) if abs(delta_kl) > 0.001: - msg = f'\n{qname}: KL-RB (now)' + msg = f'\n {qname}: KL-RB (now)' msg += f' - {self.params.kl_pv_2track} (initial)' msg += f' = {delta_kl}' print(msg) @@ -938,20 +938,22 @@ def _do_pbba(self): sofb = self.devices['sofb'] if sofb.autocorrsts: - print('SOFB feedback is enabled. Please desable it first.') + print('\nSOFB feedback is enabled. Please desable it first.') return for group_id in range(len(self.data['groups2dopbba'])): if self._stopevt.is_set(): - print('Stopped!') + print('\nStopped!') break if not self.havebeam: - print('Beam was Lost') + print('\nBeam was Lost') break if not self.check_strengths(): - msg = "Restoring initial strengths...\n" - self._restore_init_conditions(massage=msg, correct_orbit=False) - print('Correcting Orbit... ', end='') + self._restore_init_conditions( + massage="\nRestoring initial strengths...", + correct_orbit=False + ) + print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') if self._dopbba_single_group(group_id): @@ -960,13 +962,13 @@ def _do_pbba(self): tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] - print('Finished! Elapsed time {:s}'.format(dtime)) + print('\nFinished! Elapsed time {:s}'.format(dtime)) def _dopbba_single_group(self, group_id): """.""" tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime('%Hh%Mm%Ss') - print(f'{strtini:s} --> Doing PBBA for Group {group_id:d}') + print(f'{strtini:s}: Doing PBBA for Group {group_id:d}') jac = self.data['jacobians'][group_id] inv_jac = _np.linalg.pinv(jac, self.params.inv_jac_rcond) @@ -978,27 +980,38 @@ def _dopbba_single_group(self, group_id): } ios_iter, dkicks_iter = [], [] nr_iters = self.params.corr_nr_iters - print(' correcting IOS:', end='') + print(' Correcting IOS:') sts = self.STATUS.Fail for i in range(nr_iters): - print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') - if self._stopevt.is_set() or not self.havebeam: - self._restore_init_conditions() + print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') + if self._stopevt.is_set(): + self._restore_init_conditions( + extra_info_before_message="Measurement stopped. " + ) + break + if not self.havebeam: + self._restore_init_conditions( + extra_info_before_message="Error: beam is off. " + ) break ios, sts = self.meas_ios(group_id) if not sts: - self._restore_init_conditions() + self._restore_init_conditions( + extra_info_before_message="Fail while measuring IOS. " + ) break ios_iter.append(ios) dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - print('Done.') + print('Done') # final ios ios, sts = self.meas_ios(group_id) if not sts: - self._restore_init_conditions() + self._restore_init_conditions( + extra_info_before_message="Fail while measuring IOS. " + ) ios_iter.append(ios) group_data['kicks_end'] = self.get_kicks() @@ -1013,18 +1026,20 @@ def _dopbba_single_group(self, group_id): tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] + msg = ' Finished. Status: ' if sts: - print('Finished. Status: OK! Elapsed time: {:s}\n'.format(dtime)) + print(msg + 'OK! Elapsed time: {:s}'.format(dtime)) else: - print('Finished. Status: Fail! Elapsed time: {:s}\n'.format(dtime)) + print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) return sts def _restore_init_conditions(self, - message="Restoring initial conditions and exiting...\n", - correct_orbit=True + message="Restoring initial conditions and exiting...", + correct_orbit=True, + extra_info_before_message="" ): """.""" - print(message, end='') + print(extra_info_before_message+message) if self.params.kl_pv_2track == 'KLRef-Mon': init_strengths = self.data['KLRef_strengths'] else: @@ -1043,7 +1058,9 @@ def _restore_init_conditions(self, abs_tol=0.1 * self.params.quad_deltakl, timeout=self.params.wait_quadrupole ): - print(f'{qname}: could not be restored to initial strenght') + print( + f' {qname}: Could not be restored to initial strenght' + ) if correct_orbit: self.correct_orbit() From fba2d7ab689799a821476c119159dee5bd2d912f Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 24 Oct 2025 22:16:41 -0300 Subject: [PATCH 33/61] MNT: last strength check and orbit correction before leaving meas --- apsuite/commisslib/measure_pbba.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 7fccae605..81fa438ae 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -950,15 +950,26 @@ def _do_pbba(self): break if not self.check_strengths(): self._restore_init_conditions( - massage="\nRestoring initial strengths...", + message="\nRestoring initial strengths...", correct_orbit=False ) print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') - if self._dopbba_single_group(group_id): + if not self._dopbba_single_group(group_id): break + # check strengths before leaving + print('\nStarting clean-up: ') + if not self.check_strengths(): + self._restore_init_conditions( + "\nRestoring initial strengths...", + correct_orbit=False + ) + print('\nCorrecting Orbit... ', end='') + self.correct_orbit() + print('Ok!') + tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] From 7ed18821ab8cd597c0352b47c1894b29b9db52b2 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 29 Oct 2025 10:59:35 -0300 Subject: [PATCH 34/61] MNT: Remove strength check (added on last commit) --- apsuite/commisslib/measure_pbba.py | 81 +++--------------------------- 1 file changed, 8 insertions(+), 73 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 81fa438ae..4b104ad15 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -353,8 +353,6 @@ class ParallelBBAParams(_ParamsBaseClass): BPMNAMES = tuple([_PVName(bpm) for bpm in BPMNAMES]) QUADNAMES = tuple([_PVName(quad) for quad in QUADNAMES]) - KLMON_TYPES = ['KLRef-Mon', 'KL-RB'] - def __init__(self): """.""" super().__init__() @@ -371,7 +369,6 @@ def __init__(self): self.sofb_nrpoints = 20 self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] - self._kl_pv_2track = 'KLRef-Mon' def __str__(self): """.""" @@ -385,7 +382,6 @@ def __str__(self): stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' - stg += f'kl_pv_2track = {self._kl_pv_2track}\n' return stg @staticmethod @@ -427,19 +423,6 @@ def get_default_dkl(self, groups=None): d[::2] *= -1 return dkl - @property - def kl_pv_2track(self): - """.""" - return self._kl_pv_2track - - @kl_pv_2track.setter - def kl_pv_2track(self, value): - """.""" - if value in ParallelBBAParams.KLMON_TYPES: - self._kl_pv_2track = value - else: - raise ValueError('Choose between: ', ParallelBBAParams.KLMON_TYPES) - class DoParallelBBA(_BaseClass): """.""" @@ -462,9 +445,6 @@ def __init__(self, isonline=True): self._model = None self._fam_data = None - self.data['KLRef_strengths'] = dict() - self.data['KL_init_strengths'] = dict() - if self.isonline: self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) self.devices['currinfosi'] = _CurrInfoSI() @@ -581,38 +561,6 @@ def connect_to_quadrupoles(self): qname, props2init=('PwrState-Sts', 'KL-SP', 'KL-RB', 'KLRef-Mon') ) - self.data['KLRef_strengths'] = self.devices[qname].strengthref_mon - self.data['KL_init_strengths'] = self.devices[qname].strength - - def check_strengths(self): - """.""" - print('Checking strengths... ', end='') - if self.params.kl_pv_2track == 'KLRef-Mon': - init_strengths = self.data['KLRef_strengths'] - else: - init_strengths = self.data['KL_init_strengths'] - if not init_strengths: - print('Make sure quadrupoles are connected!') - return - sts = DoParallelBBA.STATUS.Success - for qname in self.data['quadnames']: - if qname not in self.devices.keys(): - print(f'\n {qname}: offline') - else: - delta_kl = ( - self.devices[qname] - - - init_strengths[qname] - ) - if abs(delta_kl) > 0.001: - msg = f'\n {qname}: KL-RB (now)' - msg += f' - {self.params.kl_pv_2track} (initial)' - msg += f' = {delta_kl}' - print(msg) - sts = DoParallelBBA.STATUS.Fail - if sts: - print('OK!') - return sts def get_orbit(self): """.""" @@ -948,24 +896,12 @@ def _do_pbba(self): if not self.havebeam: print('\nBeam was Lost') break - if not self.check_strengths(): - self._restore_init_conditions( - message="\nRestoring initial strengths...", - correct_orbit=False - ) print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') if not self._dopbba_single_group(group_id): break - # check strengths before leaving - print('\nStarting clean-up: ') - if not self.check_strengths(): - self._restore_init_conditions( - "\nRestoring initial strengths...", - correct_orbit=False - ) print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') @@ -1051,17 +987,16 @@ def _restore_init_conditions(self, ): """.""" print(extra_info_before_message+message) - if self.params.kl_pv_2track == 'KLRef-Mon': - init_strengths = self.data['KLRef_strengths'] - else: - init_strengths = self.data['KL_init_strengths'] - for qname in self.data['quadnames']: - quad = self.devices[qname] - quad.strength = init_strengths[qname] + self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) - for qname in self.data['quadnames']: - quad = self.devices[qname] + bpms = self.data['groups2dopbba'][group_id] + quad_names = self.data['quadnames'] + bpm_names = self.data['bpmnames'] + + for strength, bpmname in zip(init_strengths, bpms): # noqa: B905 + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] if not quad._wait_float( 'KL-RB', # or KL-Mon ? init_strengths[qname], From a87aa0bf64050287a4bd47ce627fbf8c747e1331 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 29 Oct 2025 11:11:05 -0300 Subject: [PATCH 35/61] BUGFIX: Wrong KL assignment when measuring IOS (get/set timing conflict) --- apsuite/commisslib/measure_pbba.py | 57 ++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 4b104ad15..0511c5417 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -637,10 +637,10 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad._wait_float( - 'KL-RB', # or KL-Mon ? + 'KLRef-Mon', strength, rel_tol=0.0, - abs_tol=0.1*self.params.quad_deltakl, + abs_tol=0.05*self.params.quad_deltakl, timeout=self.params.wait_quadrupole ): return DoParallelBBA.STATUS.Fail @@ -659,10 +659,16 @@ def get_quad_strengths(self, group_id): strengths.append(quad.strength) return _np.array(strengths) - def meas_ios(self, group_id): + def meas_ios(self, group_id, init_strengths=None): """.""" delta_strens = self.data['delta_kl'][group_id] - strens_orig = self.get_quad_strengths(group_id) + + if init_strengths is None: + strens_orig = self.get_quad_strengths(group_id) + _time.sleep(self.params.wait_quadrupole) + else: + strens_orig = init_strengths + if not self.set_quad_strengths( group_id, strens_orig + delta_strens / 2 ): @@ -682,7 +688,7 @@ def meas_ios(self, group_id): return orb_pos - orb_neg, DoParallelBBA.STATUS.Success - def calc_ios_jacobians(self, groups_to_calc=None): + def calc_ios_jacobians(self, groups_to_calc=None): # noqa: C901 """Calculate the IOS Response Matrices for all groups.""" model = self.model _orbcorr = _OrbitCorr( @@ -922,28 +928,37 @@ def _dopbba_single_group(self, group_id): group_data = { 'bpms': self.data['groups2dopbba'][group_id], + 'strengths_init': self.get_quad_strengths(group_id), 'orbit_init': self.get_orbit(), 'kicks_init': self.get_kicks(), } + ios_iter, dkicks_iter = [], [] nr_iters = self.params.corr_nr_iters + print(' Correcting IOS:') sts = self.STATUS.Fail for i in range(nr_iters): print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') if self._stopevt.is_set(): self._restore_init_conditions( + group_id, + group_data['strengths_init'], extra_info_before_message="Measurement stopped. " ) break if not self.havebeam: self._restore_init_conditions( + group_id, + group_data['strengths_init'], extra_info_before_message="Error: beam is off. " ) break - ios, sts = self.meas_ios(group_id) + ios, sts = self.meas_ios(group_id, group_data['strengths_init']) if not sts: self._restore_init_conditions( + group_id, + group_data['strengths_init'], extra_info_before_message="Fail while measuring IOS. " ) break @@ -954,12 +969,16 @@ def _dopbba_single_group(self, group_id): print('Done') # final ios - ios, sts = self.meas_ios(group_id) - if not sts: - self._restore_init_conditions( - extra_info_before_message="Fail while measuring IOS. " - ) - ios_iter.append(ios) + if sts: + ios, sts = self.meas_ios(group_id, group_data['strengths_init']) + if not sts: + self._restore_init_conditions( + group_id, + group_data['strengths_init'], + extra_info_before_message="Fail while measuring IOS. " + ) + else: + ios_iter.append(ios) group_data['kicks_end'] = self.get_kicks() group_data['ios_iter'] = ios_iter @@ -981,6 +1000,8 @@ def _dopbba_single_group(self, group_id): return sts def _restore_init_conditions(self, + group_id, + init_strengths, message="Restoring initial conditions and exiting...", correct_orbit=True, extra_info_before_message="" @@ -995,17 +1016,17 @@ def _restore_init_conditions(self, bpm_names = self.data['bpmnames'] for strength, bpmname in zip(init_strengths, bpms): # noqa: B905 - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] + qname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[qname] if not quad._wait_float( - 'KL-RB', # or KL-Mon ? - init_strengths[qname], + 'KLRef-Mon', + strength, rel_tol=0.0, - abs_tol=0.1 * self.params.quad_deltakl, + abs_tol=0.05*self.params.quad_deltakl, timeout=self.params.wait_quadrupole ): print( - f' {qname}: Could not be restored to initial strenght' + f' {qname}: Could not be restored to initial strength' ) if correct_orbit: From 0ce51b6760cfb0c9d5ca73b8d935eea356ff965d Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 17 Nov 2025 10:41:12 -0300 Subject: [PATCH 36/61] MNT/ENH: Add "enbllistbpm" selection/control for IOS correction. --- apsuite/commisslib/measure_pbba.py | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 0511c5417..2abdcb670 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -482,6 +482,7 @@ def havebeam(self): haveb = self.devices['currinfosi'] return haveb.connected and haveb.storedbeam + # #### pbba groups and deltas ##### @property def groups2dopbba(self): """.""" @@ -512,6 +513,7 @@ def delta_kl(self, value): raise ValueError(f"values for delta kl can't exceed {_max}") self.data['delta_kl'] = _dcopy(value) + # #### model utils ##### @property def jacobians(self): """.""" @@ -552,6 +554,7 @@ def fam_data(self): """.""" return self._fam_data + # ### quadrupole connection ##### def connect_to_quadrupoles(self): """.""" for qname in self.data['quadnames']: @@ -562,6 +565,7 @@ def connect_to_quadrupoles(self): props2init=('PwrState-Sts', 'KL-SP', 'KL-RB', 'KLRef-Mon') ) + # #### sofb utils ##### def get_orbit(self): """.""" if not self.havebeam: @@ -618,6 +622,27 @@ def set_delta_kicks(self, dkicks): factch, factcv, factrf ) + @property + def enbllistbpm(self): + """.""" + sofb = self.devices['sofb'] + enblx = sofb.bpmxenbl.copy() + enbly = sofb.bpmyenbl.copy() + return _np.array(_np.hstack([enblx, enbly]), dtype=bool) + + @enbllistbpm.setter + def enbllistbpm(self, value): + sofb = self.devices['sofb'] + nbpms = sofb._data.nr_bpms + if len(value) != 2 * nbpms: + raise ValueError(f'Invalid size! Must be {2*nbpms}.') + if all(v in [0, 1, True, False] for v in value): + value = _np.array(value, dtype=bool) + else: + raise ValueError('Values must be boolean (0 / 1 or True / False).') + sofb.bpmxenbl = value[:nbpms] + sofb.bpmyenbl = value[nbpms:] + # #### pbba utils ##### def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): @@ -923,7 +948,8 @@ def _dopbba_single_group(self, group_id): strtini = tini.strftime('%Hh%Mm%Ss') print(f'{strtini:s}: Doing PBBA for Group {group_id:d}') - jac = self.data['jacobians'][group_id] + enblbpm = self.enbllistbpm # cut jacobian with only enabled bpms + jac = (self.data['jacobians'][group_id])[enblbpm, :] inv_jac = _np.linalg.pinv(jac, self.params.inv_jac_rcond) group_data = { @@ -931,6 +957,7 @@ def _dopbba_single_group(self, group_id): 'strengths_init': self.get_quad_strengths(group_id), 'orbit_init': self.get_orbit(), 'kicks_init': self.get_kicks(), + 'enbllistbpm': enblbpm.copy(), } ios_iter, dkicks_iter = [], [] @@ -962,7 +989,8 @@ def _dopbba_single_group(self, group_id): extra_info_before_message="Fail while measuring IOS. " ) break - ios_iter.append(ios) + ios_iter.append(ios) # save ios (all bpms) + ios = ios[enblbpm] # use only enabled bpms for correction dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) From c8b0d5f49ffdec2da753efab008f1bb245fdc304 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 8 Dec 2025 11:03:38 -0300 Subject: [PATCH 37/61] MNT: update exposed "wait_float" method --- apsuite/commisslib/measure_pbba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 2abdcb670..f1fd4901a 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -661,7 +661,7 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): for strength, bpmname in zip(strengths, bpms): # noqa: B905 quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] - if not quad._wait_float( + if not quad.wait_float( 'KLRef-Mon', strength, rel_tol=0.0, @@ -1046,7 +1046,7 @@ def _restore_init_conditions(self, for strength, bpmname in zip(init_strengths, bpms): # noqa: B905 qname = quad_names[bpm_names.index(bpmname)] quad = self.devices[qname] - if not quad._wait_float( + if not quad.wait_float( 'KLRef-Mon', strength, rel_tol=0.0, From 596e95bafffbee05765db4029c11030d390a4ade Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 8 Dec 2025 12:17:28 -0300 Subject: [PATCH 38/61] MNT/ENH: add cycling procedure --- apsuite/commisslib/measure_pbba.py | 49 ++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index f1fd4901a..012b2dcf6 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -370,6 +370,8 @@ def __init__(self): self.sofb_maxcorriter = 5 self.sofb_maxorberr = 5 # [um] + self.cycling_nr_steps = 2 + def __str__(self): """.""" stg = '' @@ -382,6 +384,7 @@ def __str__(self): stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' + stg += f'cycling_nr_steps = {self.cycling_nr_steps:.3f}\n' return stg @staticmethod @@ -960,10 +963,23 @@ def _dopbba_single_group(self, group_id): 'enbllistbpm': enblbpm.copy(), } - ios_iter, dkicks_iter = [], [] - nr_iters = self.params.corr_nr_iters + print(' Cycling:') + msg, sts = self._do_cycling( + group_id, + group_data['strengths_init'], + ) + if not sts: + self._restore_init_conditions( + group_id, + group_data['strengths_init'], + extra_info_before_message=msg + ) + nr_iters = 0 + else: # proceed to IOS correction + print(' Correcting IOS:') + nr_iters = self.params.corr_nr_iters - print(' Correcting IOS:') + ios_iter, dkicks_iter = [], [] sts = self.STATUS.Fail for i in range(nr_iters): print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') @@ -1027,6 +1043,33 @@ def _dopbba_single_group(self, group_id): print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) return sts + def _do_cycling(self, group_id, init_strengths): + delta_strengths = self.data['delta_kl'][group_id] + nr_cycles = self.params.cycling_nr_steps + for i in range(nr_cycles): + print(' {:02d}/{:02d} --> '.format(i+1, nr_cycles), end='') + if self._stopevt.is_set(): + return "Event stopped! ", DoParallelBBA.STATUS.Fail + if not self.havebeam: + return "No beam! ", DoParallelBBA.STATUS.Fail + if not self.set_quad_strengths( + group_id, + init_strengths + delta_strengths / 2 + ): + return "Fail! ", DoParallelBBA.STATUS.Fail + if not self.set_quad_strengths( + group_id, + init_strengths - delta_strengths / 2 + ): + return "Fail! ", DoParallelBBA.STATUS.Fail + if not self.set_quad_strengths( + group_id, + init_strengths + ): + return "Fail! ", DoParallelBBA.STATUS.Fail + print('Ok') + return "", DoParallelBBA.STATUS.Success + def _restore_init_conditions(self, group_id, init_strengths, From 0d7873d1f13fdd04a4efa4f5b887af2c26935ec9 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 9 Feb 2026 18:56:54 -0300 Subject: [PATCH 39/61] MNT.ENH: Manage quadrupoles strength limits --- apsuite/commisslib/measure_pbba.py | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 012b2dcf6..2bfdd0ee9 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -687,6 +687,56 @@ def get_quad_strengths(self, group_id): strengths.append(quad.strength) return _np.array(strengths) + def get_quad_strength_limits(self, group_id, margin=0.0005): + """.""" + bpms = self.data['groups2dopbba'][group_id] + quad_names = self.data['quadnames'] + bpm_names = self.data['bpmnames'] + + limits = [] + for bpmname in bpms: + quadname = quad_names[bpm_names.index(bpmname)] + quad = self.devices[quadname] + pv = quad.pv_object('KL-SP') + upp = pv.upper_disp_limit + low = pv.lower_disp_limit + # Limits are interchanged in some quads: + lolim = min(upp, low) + margin + hilim = max(upp, low) - margin + limits.append([lolim, hilim]) + return _np.array(limits, dtype=float) + + def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): + """.""" + bpms = self.data['groups2dopbba'][group_id] + quad_names = self.data['quadnames'] + bpm_names = self.data['bpmnames'] + + quadlims = self.get_quad_str_limits(group_id, margin=margin) + delta_kl = self.data['delta_kl'][group_id] + + if init_strengths is None: + strengths = self.get_quad_strengths(group_id) + else: + strengths = init_strengths + + ok = True + for idx, bpmname in enumerate(bpms): + quadname = quad_names[bpm_names.index(bpmname)] + stren = strengths[idx] + dkl = delta_kl[idx] + lolim, hilim = quadlims[idx] + low = min(stren+dkl, stren-dkl) + upp = max(stren+dkl, stren-dkl) + if upp > hilim or low < lolim: + max_delta_kl = min(hilim - stren, stren - lolim) + msg = f"WARN: {quadname} KL = {stren:.1g}, dKL = {abs(dkl):.1g}. " + msg += f"Limits: ({lolim:.1g}, {hilim:.1g}). Max. dKL = {max_delta_kl:.1g}." + print(msg) + ok = False + return ok + + def meas_ios(self, group_id, init_strengths=None): """.""" delta_strens = self.data['delta_kl'][group_id] From 401887da2adef5674810bff839e7f929f53dee07 Mon Sep 17 00:00:00 2001 From: ximenes Date: Tue, 10 Feb 2026 00:07:46 -0300 Subject: [PATCH 40/61] MNT.ENH: (All) Mods after Machine Study 2026-02-09 --- apsuite/commisslib/measure_pbba.py | 106 +++++++++++++++++------------ 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 2bfdd0ee9..406ec62b0 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -388,15 +388,51 @@ def __str__(self): return stg @staticmethod - def get_default_groups(): + def get_default_groups(ngroups=4): """.""" - return [ + if ngroups == 2: + group_class = [ + [('M2', ''), ('C3', '1'), ('C1', '2'), ('C4', '')], + [('C1', '1'), ('C3', '2'), ('C2', ''), ('M1', '')], + ] + elif ngroups in {8, 16}: + group_class = [ + [('M2', '')], + [('C3', '1')], + [('C1', '1')], + [('C3', '2')], + [('C1', '2')], + [('C4', '')], + [('C2', '')], + [('M1', '')], + ] + else: + group_class = [ + [('M2', ''), ('C3', '1')], + [('C1', '1'), ('C3', '2')], + [('C1', '2'), ('C4', '')], + [('C2', ''), ('M1', '')], + ] + + groups = [ + sorted([ + e + for e in ParallelBBAParams.BPMNAMES + if (e.sub[2:], e.idx) in k + ]) + for k in group_class + ] + + if ngroups == 16: + groups_ = [] + for grp in groups: + groups_.append(grp[::2]) + groups_.append(grp[1::2]) + groups = groups_ + + groups = [ sorted( - [ - e - for e in ParallelBBAParams.BPMNAMES - if (e.sub[2:], e.idx) in k - ], + group, key=lambda x: { 'Q4': 0, 'Q1': 1, @@ -410,17 +446,13 @@ def get_default_groups(): 999, ), ) - for k in [ - [('M2', ''), ('C3', '1')], - [('C1', '1'), ('C3', '2')], - [('C1', '2'), ('C4', '')], - [('C2', ''), ('M1', '')], - ] + for group in groups ] + return groups - def get_default_dkl(self, groups=None): + def get_default_dkl(self, ngroups=4, groups=None): """.""" - groups = self.get_default_groups() if groups is None else groups + groups = self.get_default_groups(ngroups) if groups is None else groups dkl = [_np.ones(len(g)) * self.quad_deltakl for g in groups] for d in dkl: d[::2] *= -1 @@ -442,7 +474,7 @@ def __init__(self, isonline=True): self.data['measure'] = list() self.data['groups2dopbba'] = ParallelBBAParams.get_default_groups() self.data['delta_kl'] = self.params.get_default_dkl( - self.data['groups2dopbba'] + groups=self.data['groups2dopbba'] ) self.data['jacobians'] = list() self._model = None @@ -459,7 +491,7 @@ def __str__(self): stp = self.params.__str__() stp = ' ' + stp.replace('\n', '\n ') stn += stp + '\n' - connected = str(self.connected & len(self.devices.keys()) > 0) + connected = str(self.connected and len(self.devices.keys()) > 0) stn += 'Connected? ' + connected + '\n\n' stn += ' {:^20s} {:^20s} {:^7s}\n'.format( 'BPM', 'Quad', 'dKL' @@ -603,27 +635,8 @@ def set_delta_kicks(self, dkicks): ) dch, dcv, drf = dkicks[:nch], dkicks[nch : nch + ncv], dkicks[-1] - factch, factcv, factrf = ( - sofb.mancorrgainch, - sofb.mancorrgaincv, - sofb.mancorrgainrf, - ) sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch, dcv, drf - nrsteps = _np.ceil(max(_np.abs(dch).max(), _np.abs(dcv).max()) / 1.0) - for i in range(int(nrsteps)): - sofb.mancorrgainch = (i + 1) / nrsteps * 100 - sofb.mancorrgaincv = (i + 1) / nrsteps * 100 - sofb.mancorrgainrf = (i + 1) / nrsteps * 100 - sofb.cmd_applycorr_all() - _time.sleep(self.params.wait_correctors) - sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = ( - dch * 0, - dcv * 0, - drf * 0, - ) - sofb.mancorrgainch, sofb.mancorrgaincv, sofb.mancorrgainrf = ( - factch, factcv, factrf - ) + sofb.cmd_applycorr_all() @property def enbllistbpm(self): @@ -712,7 +725,7 @@ def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): quad_names = self.data['quadnames'] bpm_names = self.data['bpmnames'] - quadlims = self.get_quad_str_limits(group_id, margin=margin) + quadlims = self.get_quad_strength_limits(group_id, margin=margin) delta_kl = self.data['delta_kl'][group_id] if init_strengths is None: @@ -726,12 +739,12 @@ def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): stren = strengths[idx] dkl = delta_kl[idx] lolim, hilim = quadlims[idx] - low = min(stren+dkl, stren-dkl) - upp = max(stren+dkl, stren-dkl) + low = min(stren+dkl/2, stren-dkl/2) + upp = max(stren+dkl/2, stren-dkl/2) if upp > hilim or low < lolim: max_delta_kl = min(hilim - stren, stren - lolim) - msg = f"WARN: {quadname} KL = {stren:.1g}, dKL = {abs(dkl):.1g}. " - msg += f"Limits: ({lolim:.1g}, {hilim:.1g}). Max. dKL = {max_delta_kl:.1g}." + msg = f"WARN: {quadname} KL = {stren:.2g}, dKL = {abs(dkl):.2g}. " + msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). Max. dKL = {max_delta_kl*2:.2g}." print(msg) ok = False return ok @@ -965,6 +978,11 @@ def _do_pbba(self): ) ) + groups = self.data['groups2dopbba'] + if not all([self.check_isvalid_dkl(g) for g, _ in enumerate(groups)]): + print('Adjust quad strength or change dKL first.') + return + self.data['jacobians'] = self.calc_ios_jacobians() self.data['measure'] = list() @@ -973,7 +991,7 @@ def _do_pbba(self): print('\nSOFB feedback is enabled. Please desable it first.') return - for group_id in range(len(self.data['groups2dopbba'])): + for gid, _ in enumerate(groups): if self._stopevt.is_set(): print('\nStopped!') break @@ -983,7 +1001,7 @@ def _do_pbba(self): print('\nCorrecting Orbit... ', end='') self.correct_orbit() print('Ok!') - if not self._dopbba_single_group(group_id): + if not self._dopbba_single_group(gid): break print('\nCorrecting Orbit... ', end='') From 661a0c41f02f1fa79edbd10fc28301675c38e427 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 14 Apr 2026 10:33:40 -0300 Subject: [PATCH 41/61] BUG: (COMMISS.PBBA) change number of points of SOFB to get orbit. --- apsuite/commisslib/measure_pbba.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 406ec62b0..8724ae6bc 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -606,8 +606,14 @@ def get_orbit(self): if not self.havebeam: return sofb = self.devices['sofb'] + nrpts = sofb.nr_points + sofb.nr_points = self.params.sofb_nrpoints + sofb.cmd_reset() sofb.wait_buffer(self.params.timeout_wait_orbit) + + sofb.nr_points = nrpts + return _np.hstack([sofb.orbx, sofb.orby]) def correct_orbit(self): From ab2fb476a3b67c39d041a9b1d53b6d3b22af61ae Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 15:28:27 -0300 Subject: [PATCH 42/61] MNT: change some default params --- apsuite/commisslib/measure_pbba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 8724ae6bc..d8ba47766 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -357,7 +357,7 @@ def __init__(self): """.""" super().__init__() - self.quad_deltakl = 0.02 # [1/m] + self.quad_deltakl = 0.01 # [1/m] self.wait_correctors = 0.3 # [s] self.wait_quadrupole = 0.3 # [s] @@ -388,7 +388,7 @@ def __str__(self): return stg @staticmethod - def get_default_groups(ngroups=4): + def get_default_groups(ngroups=8): """.""" if ngroups == 2: group_class = [ From 51128eb78bec4a4dc72f4b0ab108f608dc30d2ee Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 15:33:16 -0300 Subject: [PATCH 43/61] MNT: remove rcond and add automatic cut of svals --- apsuite/commisslib/measure_pbba.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index d8ba47766..79e7ed781 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -363,8 +363,8 @@ def __init__(self): self.wait_quadrupole = 0.3 # [s] self.timeout_wait_orbit = 3 # [s] - self.corr_nr_iters = 6 - self.inv_jac_rcond = 1e-5 + self.corr_max_nr_iters = 8 + self.ios_conv_threshold = 0.1 self.sofb_nrpoints = 20 self.sofb_maxcorriter = 5 @@ -379,8 +379,8 @@ def __str__(self): stg += f'wait_correctors = {self.wait_correctors:.3f}\n' stg += f'wait_quadrupole = {self.wait_quadrupole:.3f}\n' stg += f'timeout_wait_orbit = {self.timeout_wait_orbit:.3f}\n' - stg += f'corr_nr_iters = {self.corr_nr_iters:.3f}\n' - stg += f'inv_jac_rcond = {self.inv_jac_rcond:.3e}\n' + stg += f'corr_nr_iters = {self.corr_max_nr_iters:.3f}\n' + stg += f'ios_conv_threshold = {self.ios_conv_threshold:.2e}\n' stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' @@ -1027,7 +1027,7 @@ def _dopbba_single_group(self, group_id): enblbpm = self.enbllistbpm # cut jacobian with only enabled bpms jac = (self.data['jacobians'][group_id])[enblbpm, :] - inv_jac = _np.linalg.pinv(jac, self.params.inv_jac_rcond) + inv_jac = self._calc_inverse_jacobian(jac, group_id) group_data = { 'bpms': self.data['groups2dopbba'][group_id], @@ -1117,6 +1117,15 @@ def _dopbba_single_group(self, group_id): print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) return sts + def _calc_inverse_jacobian(self, jacobian, group_id): + """.""" + u, s, vt = _np.linalg.svd(jacobian, full_matrices=False) + nr_svals = 2 * len(self.groups2dopbba[group_id]) + i_s = _np.zeros_like(s) + i_s[:nr_svals] = 1.0 / s[:nr_svals] + + return vt.T @ _np.diag(i_s) @ u.T + def _do_cycling(self, group_id, init_strengths): delta_strengths = self.data['delta_kl'][group_id] nr_cycles = self.params.cycling_nr_steps From 6a38843b427dc7603b832898edc4fa06ebc4a278 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 15:34:43 -0300 Subject: [PATCH 44/61] MNT: new "measuredbpms" property, and change default config to 8 groups --- apsuite/commisslib/measure_pbba.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 79e7ed781..aa9e49f47 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -450,7 +450,7 @@ def get_default_groups(ngroups=8): ] return groups - def get_default_dkl(self, ngroups=4, groups=None): + def get_default_dkl(self, ngroups=8, groups=None): """.""" groups = self.get_default_groups(ngroups) if groups is None else groups dkl = [_np.ones(len(g)) * self.quad_deltakl for g in groups] @@ -517,6 +517,14 @@ def havebeam(self): haveb = self.devices['currinfosi'] return haveb.connected and haveb.storedbeam + @property + def measuredbpms(self): + """.""" + mesured = [] + for group in self.data['measure']: + mesured.extend(group['bpms']) + return sorted(mesured) + # #### pbba groups and deltas ##### @property def groups2dopbba(self): From 5db8f6135c9f4ecbddd42e5fd51eac634c5e45db Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 15:48:52 -0300 Subject: [PATCH 45/61] MNT: add convergence check and breaks --- apsuite/commisslib/measure_pbba.py | 47 +++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index aa9e49f47..97c112b5a 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -1059,10 +1059,13 @@ def _dopbba_single_group(self, group_id): nr_iters = 0 else: # proceed to IOS correction print(' Correcting IOS:') - nr_iters = self.params.corr_nr_iters + nr_iters = self.params.corr_max_nr_iters ios_iter, dkicks_iter = [], [] sts = self.STATUS.Fail + converged = False + increased = False + _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) for i in range(nr_iters): print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') if self._stopevt.is_set(): @@ -1089,13 +1092,34 @@ def _dopbba_single_group(self, group_id): break ios_iter.append(ios) # save ios (all bpms) ios = ios[enblbpm] # use only enabled bpms for correction + + # check convergence + if (i > 0): + dios_p = _func(ios_iter[-2][enblbpm], ios) + if dios_p < 0.0: + prev_dkicks = dkicks_iter[-1] + self.set_delta_kicks(prev_dkicks) + print('Done.', end=' ') + converged = False + increased = True + break + init_ios = ios_iter[0][enblbpm] + dios_i = _func(ios_iter[0][enblbpm], ios) + if dios_i < self.params.ios_conv_threshold: + print('Done.', end=' ') + converged = True + break + dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - print('Done') + print('Done.', end=' ') - # final ios - if sts: + if sts and converged: + print(f'IOS converged ({i:d} iterations).') + elif sts and not converged and increased: + print(f'IOS increased ({i:d} iterations).') + elif sts and not converged and not increased: ios, sts = self.meas_ios(group_id, group_data['strengths_init']) if not sts: self._restore_init_conditions( @@ -1105,6 +1129,21 @@ def _dopbba_single_group(self, group_id): ) else: ios_iter.append(ios) + ios = ios[enblbpm] + dios_i = _func(ios_iter[0][enblbpm], ios) + dios_p = _func(ios_iter[-2][enblbpm], ios) + if dios_i < self.params.ios_conv_threshold: + print( + f'Max iterations reached ({i+1:d}), but IOS converged.' + ) + elif dios_p < 0.0: + print( + f'Max iterations reached ({i+1:d}), and IOS increased.' + ) + else: + print( + f'Max iterations reached ({i+1:d}).' + ) group_data['kicks_end'] = self.get_kicks() group_data['ios_iter'] = ios_iter From c3338fe8952c978707ccab832181f422e62408f2 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 15:54:58 -0300 Subject: [PATCH 46/61] MNT: prints and log save --- apsuite/commisslib/measure_pbba.py | 65 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 97c112b5a..06a9144ea 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -476,6 +476,7 @@ def __init__(self, isonline=True): self.data['delta_kl'] = self.params.get_default_dkl( groups=self.data['groups2dopbba'] ) + self.data['log'] = list() self.data['jacobians'] = list() self._model = None self._fam_data = None @@ -573,7 +574,7 @@ def jacobians(self, jacs): def model(self): """.""" if self._model is None: - print('\n Undefined model... setting a default one') + self._log_print('\n Undefined model... setting a default one') self._model = _si.create_accelerator() self._model.cavity_on = True self._model.radiation_on = 1 @@ -759,7 +760,7 @@ def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): max_delta_kl = min(hilim - stren, stren - lolim) msg = f"WARN: {quadname} KL = {stren:.2g}, dKL = {abs(dkl):.2g}. " msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). Max. dKL = {max_delta_kl*2:.2g}." - print(msg) + self._log_print(msg) ok = False return ok @@ -986,7 +987,7 @@ def _get_quads_indices_in_model(self, quadnames): def _do_pbba(self): tini = _datetime.datetime.fromtimestamp(_time.time()) - print( + self._log_print( 'Starting measurement at {:s}'.format( tini.strftime('%Y-%m-%d %Hh%Mm%Ss') ) @@ -994,7 +995,7 @@ def _do_pbba(self): groups = self.data['groups2dopbba'] if not all([self.check_isvalid_dkl(g) for g, _ in enumerate(groups)]): - print('Adjust quad strength or change dKL first.') + self._log_print('Adjust quad strength or change dKL first.') return self.data['jacobians'] = self.calc_ios_jacobians() @@ -1002,36 +1003,36 @@ def _do_pbba(self): sofb = self.devices['sofb'] if sofb.autocorrsts: - print('\nSOFB feedback is enabled. Please desable it first.') + self._log_print('\nSOFB feedback is enabled. Please desable it first.') return for gid, _ in enumerate(groups): if self._stopevt.is_set(): - print('\nStopped!') + self._log_print('\nStopped!') break if not self.havebeam: - print('\nBeam was Lost') + self._log_print('\nBeam was Lost') break - print('\nCorrecting Orbit... ', end='') + self._log_print('\nCorrecting Orbit... ', end='') self.correct_orbit() - print('Ok!') + self._log_print('Ok!') if not self._dopbba_single_group(gid): break - print('\nCorrecting Orbit... ', end='') + self._log_print('\nCorrecting Orbit... ', end='') self.correct_orbit() - print('Ok!') + self._log_print('Ok!') tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) dtime = dtime.split('.')[0] - print('\nFinished! Elapsed time {:s}'.format(dtime)) + self._log_print('\nFinished! Elapsed time {:s}'.format(dtime)) def _dopbba_single_group(self, group_id): """.""" tini = _datetime.datetime.fromtimestamp(_time.time()) strtini = tini.strftime('%Hh%Mm%Ss') - print(f'{strtini:s}: Doing PBBA for Group {group_id:d}') + self._log_print(f'{strtini:s}: Doing PBBA for Group {group_id:d}') enblbpm = self.enbllistbpm # cut jacobian with only enabled bpms jac = (self.data['jacobians'][group_id])[enblbpm, :] @@ -1043,9 +1044,10 @@ def _dopbba_single_group(self, group_id): 'orbit_init': self.get_orbit(), 'kicks_init': self.get_kicks(), 'enbllistbpm': enblbpm.copy(), + 'timestamps': [], } - print(' Cycling:') + self._log_print(' Cycling:') msg, sts = self._do_cycling( group_id, group_data['strengths_init'], @@ -1058,7 +1060,7 @@ def _dopbba_single_group(self, group_id): ) nr_iters = 0 else: # proceed to IOS correction - print(' Correcting IOS:') + self._log_print(' Correcting IOS:') nr_iters = self.params.corr_max_nr_iters ios_iter, dkicks_iter = [], [] @@ -1067,7 +1069,9 @@ def _dopbba_single_group(self, group_id): increased = False _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) for i in range(nr_iters): - print(' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='') + self._log_print( + ' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='' + ) if self._stopevt.is_set(): self._restore_init_conditions( group_id, @@ -1099,21 +1103,21 @@ def _dopbba_single_group(self, group_id): if dios_p < 0.0: prev_dkicks = dkicks_iter[-1] self.set_delta_kicks(prev_dkicks) - print('Done.', end=' ') + self._log_print('Done.', end=' ') converged = False increased = True break init_ios = ios_iter[0][enblbpm] dios_i = _func(ios_iter[0][enblbpm], ios) if dios_i < self.params.ios_conv_threshold: - print('Done.', end=' ') + self._log_print('Done.', end=' ') converged = True break dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - print('Done.', end=' ') + self._log_print('Done.', end=' ') if sts and converged: print(f'IOS converged ({i:d} iterations).') @@ -1133,15 +1137,15 @@ def _dopbba_single_group(self, group_id): dios_i = _func(ios_iter[0][enblbpm], ios) dios_p = _func(ios_iter[-2][enblbpm], ios) if dios_i < self.params.ios_conv_threshold: - print( + self._log_print( f'Max iterations reached ({i+1:d}), but IOS converged.' ) elif dios_p < 0.0: - print( + self._log_print( f'Max iterations reached ({i+1:d}), and IOS increased.' ) else: - print( + self._log_print( f'Max iterations reached ({i+1:d}).' ) @@ -1159,9 +1163,9 @@ def _dopbba_single_group(self, group_id): dtime = dtime.split('.')[0] msg = ' Finished. Status: ' if sts: - print(msg + 'OK! Elapsed time: {:s}'.format(dtime)) + self._log_print(msg + 'OK! Elapsed time: {:s}'.format(dtime)) else: - print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) + self._log_print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) return sts def _calc_inverse_jacobian(self, jacobian, group_id): @@ -1177,7 +1181,9 @@ def _do_cycling(self, group_id, init_strengths): delta_strengths = self.data['delta_kl'][group_id] nr_cycles = self.params.cycling_nr_steps for i in range(nr_cycles): - print(' {:02d}/{:02d} --> '.format(i+1, nr_cycles), end='') + self._log_print( + ' {:02d}/{:02d} --> '.format(i+1, nr_cycles), end='' + ) if self._stopevt.is_set(): return "Event stopped! ", DoParallelBBA.STATUS.Fail if not self.havebeam: @@ -1197,7 +1203,7 @@ def _do_cycling(self, group_id, init_strengths): init_strengths ): return "Fail! ", DoParallelBBA.STATUS.Fail - print('Ok') + self._log_print('Ok') return "", DoParallelBBA.STATUS.Success def _restore_init_conditions(self, @@ -1208,7 +1214,7 @@ def _restore_init_conditions(self, extra_info_before_message="" ): """.""" - print(extra_info_before_message+message) + self._log_print(extra_info_before_message+message) self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) @@ -1232,3 +1238,8 @@ def _restore_init_conditions(self, if correct_orbit: self.correct_orbit() + + def _log_print(self, msg): + """.""" + self.data['log'].append((_time.time(), msg)) + print(msg) From 23b676f081f49699eb9383cb8764bd2acced6f1c Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 3 Jun 2026 16:00:06 -0300 Subject: [PATCH 47/61] FIX/MNT: remnant changes from last mods --- apsuite/commisslib/measure_pbba.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_pbba.py index 06a9144ea..77fc3a7f4 100644 --- a/apsuite/commisslib/measure_pbba.py +++ b/apsuite/commisslib/measure_pbba.py @@ -1043,9 +1043,8 @@ def _dopbba_single_group(self, group_id): 'strengths_init': self.get_quad_strengths(group_id), 'orbit_init': self.get_orbit(), 'kicks_init': self.get_kicks(), - 'enbllistbpm': enblbpm.copy(), - 'timestamps': [], - } + 'enbllistbpm': enblbpm.copy() + } self._log_print(' Cycling:') msg, sts = self._do_cycling( @@ -1107,7 +1106,6 @@ def _dopbba_single_group(self, group_id): converged = False increased = True break - init_ios = ios_iter[0][enblbpm] dios_i = _func(ios_iter[0][enblbpm], ios) if dios_i < self.params.ios_conv_threshold: self._log_print('Done.', end=' ') @@ -1232,7 +1230,7 @@ def _restore_init_conditions(self, abs_tol=0.05*self.params.quad_deltakl, timeout=self.params.wait_quadrupole ): - print( + self._log_print( f' {qname}: Could not be restored to initial strength' ) From 6356b9202b3da415b1acbbc349c6f43b260fdecc Mon Sep 17 00:00:00 2001 From: Vitor Date: Tue, 28 Jul 2026 12:43:06 -0300 Subject: [PATCH 48/61] MNT: (PBBA) Rename file "measure_pbba.py" -> "measure_bba_parallel.py" --- apsuite/commisslib/{measure_pbba.py => measure_bba_parallel.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apsuite/commisslib/{measure_pbba.py => measure_bba_parallel.py} (100%) diff --git a/apsuite/commisslib/measure_pbba.py b/apsuite/commisslib/measure_bba_parallel.py similarity index 100% rename from apsuite/commisslib/measure_pbba.py rename to apsuite/commisslib/measure_bba_parallel.py From 084b5932e10bfeda7b661daa7a58a5b068238818 Mon Sep 17 00:00:00 2001 From: Vitor Date: Tue, 28 Jul 2026 12:59:16 -0300 Subject: [PATCH 49/61] ADD: (ACBBA) Add raw/initial AC-BBA file and class --- apsuite/commisslib/measure_bba_ac.py | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 apsuite/commisslib/measure_bba_ac.py diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py new file mode 100644 index 000000000..41215babe --- /dev/null +++ b/apsuite/commisslib/measure_bba_ac.py @@ -0,0 +1,50 @@ +"""Main module.""" + +import datetime as _datetime +import time as _time +from copy import deepcopy as _dcopy + +from siriuspy.devices import ( + CurrInfoSI as _CurrInfoSI, + PowerSupply as _PowerSupply, + SOFB as _SOFB, +) + +from siriuspy.namesys import SiriusPVName as _PVName + +from ..utils import ( + ParamsBaseClass as _ParamsBaseClass, + ThreadedMeasBaseClass as _BaseClass, +) + +from .measure_bba import BBAParams as _BBAParams + + +class ACBBAParams(_ParamsBaseClass): + """.""" + + BPMNAMES = _BBAParams.BPMNAMES + QUADNAMES = _BBAParams.QUADNAMES + + def __init__(self): + """.""" + super().__init__() + + def __str__(self): + """.""" + stg = "" + return stg + + +class DoACBBA(_BaseClass): + """.""" + + def __init__(self, isonline=True): + """.""" + self.params = ACBBAParams() + super().__init__( + params=self.params, target=self._do_acbba, isonline=isonline + ) + + def _do_acbba(self): + pass From e7166bdb00658823d8a146f5d00795f33b05fb43 Mon Sep 17 00:00:00 2001 From: Vitor Date: Sun, 2 Aug 2026 22:38:51 -0300 Subject: [PATCH 50/61] MNT (PBBA.FORMAT): Applying Ruff standarts to measure_bba_parallel.py --- apsuite/commisslib/measure_bba_parallel.py | 802 ++++++--------------- 1 file changed, 235 insertions(+), 567 deletions(-) diff --git a/apsuite/commisslib/measure_bba_parallel.py b/apsuite/commisslib/measure_bba_parallel.py index 77fc3a7f4..5ae9a61d6 100644 --- a/apsuite/commisslib/measure_bba_parallel.py +++ b/apsuite/commisslib/measure_bba_parallel.py @@ -21,337 +21,14 @@ ThreadedMeasBaseClass as _BaseClass, ) +from .measure_bba import BBAParams as _BBAParams + class ParallelBBAParams(_ParamsBaseClass): """.""" - BPMNAMES = ( - 'SI-01M2:DI-BPM', - 'SI-01C1:DI-BPM-1', - 'SI-01C1:DI-BPM-2', - 'SI-01C2:DI-BPM', - 'SI-01C3:DI-BPM-1', - 'SI-01C3:DI-BPM-2', - 'SI-01C4:DI-BPM', - 'SI-02M1:DI-BPM', - 'SI-02M2:DI-BPM', - 'SI-02C1:DI-BPM-1', - 'SI-02C1:DI-BPM-2', - 'SI-02C2:DI-BPM', - 'SI-02C3:DI-BPM-1', - 'SI-02C3:DI-BPM-2', - 'SI-02C4:DI-BPM', - 'SI-03M1:DI-BPM', - 'SI-03M2:DI-BPM', - 'SI-03C1:DI-BPM-1', - 'SI-03C1:DI-BPM-2', - 'SI-03C2:DI-BPM', - 'SI-03C3:DI-BPM-1', - 'SI-03C3:DI-BPM-2', - 'SI-03C4:DI-BPM', - 'SI-04M1:DI-BPM', - 'SI-04M2:DI-BPM', - 'SI-04C1:DI-BPM-1', - 'SI-04C1:DI-BPM-2', - 'SI-04C2:DI-BPM', - 'SI-04C3:DI-BPM-1', - 'SI-04C3:DI-BPM-2', - 'SI-04C4:DI-BPM', - 'SI-05M1:DI-BPM', - 'SI-05M2:DI-BPM', - 'SI-05C1:DI-BPM-1', - 'SI-05C1:DI-BPM-2', - 'SI-05C2:DI-BPM', - 'SI-05C3:DI-BPM-1', - 'SI-05C3:DI-BPM-2', - 'SI-05C4:DI-BPM', - 'SI-06M1:DI-BPM', - 'SI-06M2:DI-BPM', - 'SI-06C1:DI-BPM-1', - 'SI-06C1:DI-BPM-2', - 'SI-06C2:DI-BPM', - 'SI-06C3:DI-BPM-1', - 'SI-06C3:DI-BPM-2', - 'SI-06C4:DI-BPM', - 'SI-07M1:DI-BPM', - 'SI-07M2:DI-BPM', - 'SI-07C1:DI-BPM-1', - 'SI-07C1:DI-BPM-2', - 'SI-07C2:DI-BPM', - 'SI-07C3:DI-BPM-1', - 'SI-07C3:DI-BPM-2', - 'SI-07C4:DI-BPM', - 'SI-08M1:DI-BPM', - 'SI-08M2:DI-BPM', - 'SI-08C1:DI-BPM-1', - 'SI-08C1:DI-BPM-2', - 'SI-08C2:DI-BPM', - 'SI-08C3:DI-BPM-1', - 'SI-08C3:DI-BPM-2', - 'SI-08C4:DI-BPM', - 'SI-09M1:DI-BPM', - 'SI-09M2:DI-BPM', - 'SI-09C1:DI-BPM-1', - 'SI-09C1:DI-BPM-2', - 'SI-09C2:DI-BPM', - 'SI-09C3:DI-BPM-1', - 'SI-09C3:DI-BPM-2', - 'SI-09C4:DI-BPM', - 'SI-10M1:DI-BPM', - 'SI-10M2:DI-BPM', - 'SI-10C1:DI-BPM-1', - 'SI-10C1:DI-BPM-2', - 'SI-10C2:DI-BPM', - 'SI-10C3:DI-BPM-1', - 'SI-10C3:DI-BPM-2', - 'SI-10C4:DI-BPM', - 'SI-11M1:DI-BPM', - 'SI-11M2:DI-BPM', - 'SI-11C1:DI-BPM-1', - 'SI-11C1:DI-BPM-2', - 'SI-11C2:DI-BPM', - 'SI-11C3:DI-BPM-1', - 'SI-11C3:DI-BPM-2', - 'SI-11C4:DI-BPM', - 'SI-12M1:DI-BPM', - 'SI-12M2:DI-BPM', - 'SI-12C1:DI-BPM-1', - 'SI-12C1:DI-BPM-2', - 'SI-12C2:DI-BPM', - 'SI-12C3:DI-BPM-1', - 'SI-12C3:DI-BPM-2', - 'SI-12C4:DI-BPM', - 'SI-13M1:DI-BPM', - 'SI-13M2:DI-BPM', - 'SI-13C1:DI-BPM-1', - 'SI-13C1:DI-BPM-2', - 'SI-13C2:DI-BPM', - 'SI-13C3:DI-BPM-1', - 'SI-13C3:DI-BPM-2', - 'SI-13C4:DI-BPM', - 'SI-14M1:DI-BPM', - 'SI-14M2:DI-BPM', - 'SI-14C1:DI-BPM-1', - 'SI-14C1:DI-BPM-2', - 'SI-14C2:DI-BPM', - 'SI-14C3:DI-BPM-1', - 'SI-14C3:DI-BPM-2', - 'SI-14C4:DI-BPM', - 'SI-15M1:DI-BPM', - 'SI-15M2:DI-BPM', - 'SI-15C1:DI-BPM-1', - 'SI-15C1:DI-BPM-2', - 'SI-15C2:DI-BPM', - 'SI-15C3:DI-BPM-1', - 'SI-15C3:DI-BPM-2', - 'SI-15C4:DI-BPM', - 'SI-16M1:DI-BPM', - 'SI-16M2:DI-BPM', - 'SI-16C1:DI-BPM-1', - 'SI-16C1:DI-BPM-2', - 'SI-16C2:DI-BPM', - 'SI-16C3:DI-BPM-1', - 'SI-16C3:DI-BPM-2', - 'SI-16C4:DI-BPM', - 'SI-17M1:DI-BPM', - 'SI-17M2:DI-BPM', - 'SI-17C1:DI-BPM-1', - 'SI-17C1:DI-BPM-2', - 'SI-17C2:DI-BPM', - 'SI-17C3:DI-BPM-1', - 'SI-17C3:DI-BPM-2', - 'SI-17C4:DI-BPM', - 'SI-18M1:DI-BPM', - 'SI-18M2:DI-BPM', - 'SI-18C1:DI-BPM-1', - 'SI-18C1:DI-BPM-2', - 'SI-18C2:DI-BPM', - 'SI-18C3:DI-BPM-1', - 'SI-18C3:DI-BPM-2', - 'SI-18C4:DI-BPM', - 'SI-19M1:DI-BPM', - 'SI-19M2:DI-BPM', - 'SI-19C1:DI-BPM-1', - 'SI-19C1:DI-BPM-2', - 'SI-19C2:DI-BPM', - 'SI-19C3:DI-BPM-1', - 'SI-19C3:DI-BPM-2', - 'SI-19C4:DI-BPM', - 'SI-20M1:DI-BPM', - 'SI-20M2:DI-BPM', - 'SI-20C1:DI-BPM-1', - 'SI-20C1:DI-BPM-2', - 'SI-20C2:DI-BPM', - 'SI-20C3:DI-BPM-1', - 'SI-20C3:DI-BPM-2', - 'SI-20C4:DI-BPM', - 'SI-01M1:DI-BPM', - ) - QUADNAMES = ( - 'SI-01M2:PS-QS', - 'SI-01C1:PS-Q1', - 'SI-01C1:PS-QS', - 'SI-01C2:PS-QS', - 'SI-01C3:PS-Q4', - 'SI-01C3:PS-QS', - 'SI-01C4:PS-Q1', - 'SI-02M1:PS-QDB2', - 'SI-02M2:PS-QDB2', - 'SI-02C1:PS-Q1', - 'SI-02C1:PS-QS', - 'SI-02C2:PS-QS', - 'SI-02C3:PS-Q4', - 'SI-02C3:PS-QS', - 'SI-02C4:PS-Q1', - 'SI-03M1:PS-QDP2', - 'SI-03M2:PS-QDP2', - 'SI-03C1:PS-Q1', - 'SI-03C1:PS-QS', - 'SI-03C2:PS-QS', - 'SI-03C3:PS-Q4', - 'SI-03C3:PS-QS', - 'SI-03C4:PS-Q1', - 'SI-04M1:PS-QDB2', - 'SI-04M2:PS-QDB2', - 'SI-04C1:PS-Q1', - 'SI-04C1:PS-QS', - 'SI-04C2:PS-QS', - 'SI-04C3:PS-Q4', - 'SI-04C3:PS-QS', - 'SI-04C4:PS-Q1', - 'SI-05M1:PS-QS', - 'SI-05M2:PS-QS', - 'SI-05C1:PS-Q1', - 'SI-05C1:PS-QS', - 'SI-05C2:PS-QS', - 'SI-05C3:PS-Q4', - 'SI-05C3:PS-QS', - 'SI-05C4:PS-Q1', - 'SI-06M1:PS-QDB2', - 'SI-06M2:PS-QDB2', - 'SI-06C1:PS-Q1', - 'SI-06C1:PS-QS', - 'SI-06C2:PS-QS', - 'SI-06C3:PS-Q4', - 'SI-06C3:PS-QS', - 'SI-06C4:PS-Q1', - 'SI-07M1:PS-QDP2', - 'SI-07M2:PS-QDP2', - 'SI-07C1:PS-Q1', - 'SI-07C1:PS-QS', - 'SI-07C2:PS-QS', - 'SI-07C3:PS-Q4', - 'SI-07C3:PS-QS', - 'SI-07C4:PS-Q1', - 'SI-08M1:PS-QDB2', - 'SI-08M2:PS-QDB2', - 'SI-08C1:PS-Q1', - 'SI-08C1:PS-QS', - 'SI-08C2:PS-QS', - 'SI-08C3:PS-Q4', - 'SI-08C3:PS-QS', - 'SI-08C4:PS-Q1', - 'SI-09M1:PS-QS', - 'SI-09M2:PS-QS', - 'SI-09C1:PS-Q1', - 'SI-09C1:PS-QS', - 'SI-09C2:PS-QS', - 'SI-09C3:PS-Q4', - 'SI-09C3:PS-QS', - 'SI-09C4:PS-Q1', - 'SI-10M1:PS-QDB2', - 'SI-10M2:PS-QDB2', - 'SI-10C1:PS-Q1', - 'SI-10C1:PS-QS', - 'SI-10C2:PS-QS', - 'SI-10C3:PS-Q4', - 'SI-10C3:PS-QS', - 'SI-10C4:PS-Q1', - 'SI-11M1:PS-QDP2', - 'SI-11M2:PS-QDP2', - 'SI-11C1:PS-Q1', - 'SI-11C1:PS-QS', - 'SI-11C2:PS-QS', - 'SI-11C3:PS-Q4', - 'SI-11C3:PS-QS', - 'SI-11C4:PS-Q1', - 'SI-12M1:PS-QDB2', - 'SI-12M2:PS-QDB2', - 'SI-12C1:PS-Q1', - 'SI-12C1:PS-QS', - 'SI-12C2:PS-QS', - 'SI-12C3:PS-Q4', - 'SI-12C3:PS-QS', - 'SI-12C4:PS-Q1', - 'SI-13M1:PS-QS', - 'SI-13M2:PS-QS', - 'SI-13C1:PS-Q1', - 'SI-13C1:PS-QS', - 'SI-13C2:PS-QS', - 'SI-13C3:PS-Q4', - 'SI-13C3:PS-QS', - 'SI-13C4:PS-Q1', - 'SI-14M1:PS-QDB2', - 'SI-14M2:PS-QDB2', - 'SI-14C1:PS-Q1', - 'SI-14C1:PS-QS', - 'SI-14C2:PS-QS', - 'SI-14C3:PS-Q4', - 'SI-14C3:PS-QS', - 'SI-14C4:PS-Q1', - 'SI-15M1:PS-QDP2', - 'SI-15M2:PS-QDP2', - 'SI-15C1:PS-Q1', - 'SI-15C1:PS-QS', - 'SI-15C2:PS-QS', - 'SI-15C3:PS-Q4', - 'SI-15C3:PS-QS', - 'SI-15C4:PS-Q1', - 'SI-16M1:PS-QDB2', - 'SI-16M2:PS-QDB2', - 'SI-16C1:PS-Q1', - 'SI-16C1:PS-QS', - 'SI-16C2:PS-QS', - 'SI-16C3:PS-Q4', - 'SI-16C3:PS-QS', - 'SI-16C4:PS-Q1', - 'SI-17M1:PS-QS', - 'SI-17M2:PS-QS', - 'SI-17C1:PS-Q1', - 'SI-17C1:PS-QS', - 'SI-17C2:PS-QS', - 'SI-17C3:PS-Q4', - 'SI-17C3:PS-QS', - 'SI-17C4:PS-Q1', - 'SI-18M1:PS-QDB2', - 'SI-18M2:PS-QDB2', - 'SI-18C1:PS-Q1', - 'SI-18C1:PS-QS', - 'SI-18C2:PS-QS', - 'SI-18C3:PS-Q4', - 'SI-18C3:PS-QS', - 'SI-18C4:PS-Q1', - 'SI-19M1:PS-QDP2', - 'SI-19M2:PS-QDP2', - 'SI-19C1:PS-Q1', - 'SI-19C1:PS-QS', - 'SI-19C2:PS-QS', - 'SI-19C3:PS-Q4', - 'SI-19C3:PS-QS', - 'SI-19C4:PS-Q1', - 'SI-20M1:PS-QDB2', - 'SI-20M2:PS-QDB2', - 'SI-20C1:PS-Q1', - 'SI-20C1:PS-QS', - 'SI-20C2:PS-QS', - 'SI-20C3:PS-Q4', - 'SI-20C3:PS-QS', - 'SI-20C4:PS-Q1', - 'SI-01M1:PS-QS', - ) - - BPMNAMES = tuple([_PVName(bpm) for bpm in BPMNAMES]) - QUADNAMES = tuple([_PVName(quad) for quad in QUADNAMES]) + BPMNAMES = _BBAParams.BPMNAMES + QUADNAMES = _BBAParams.QUADNAMES def __init__(self): """.""" @@ -374,17 +51,17 @@ def __init__(self): def __str__(self): """.""" - stg = '' - stg += f'quad_deltakl = {self.quad_deltakl:.3f}\n' - stg += f'wait_correctors = {self.wait_correctors:.3f}\n' - stg += f'wait_quadrupole = {self.wait_quadrupole:.3f}\n' - stg += f'timeout_wait_orbit = {self.timeout_wait_orbit:.3f}\n' - stg += f'corr_nr_iters = {self.corr_max_nr_iters:.3f}\n' - stg += f'ios_conv_threshold = {self.ios_conv_threshold:.2e}\n' - stg += f'sofb_nrpoints = {self.sofb_nrpoints:.3f}\n' - stg += f'sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n' - stg += f'sofb_maxorberr = {self.sofb_maxorberr:.3f}\n' - stg += f'cycling_nr_steps = {self.cycling_nr_steps:.3f}\n' + stg = "" + stg += f"quad_deltakl = {self.quad_deltakl:.3f}\n" + stg += f"wait_correctors = {self.wait_correctors:.3f}\n" + stg += f"wait_quadrupole = {self.wait_quadrupole:.3f}\n" + stg += f"timeout_wait_orbit = {self.timeout_wait_orbit:.3f}\n" + stg += f"corr_nr_iters = {self.corr_max_nr_iters:.3f}\n" + stg += f"ios_conv_threshold = {self.ios_conv_threshold:.2e}\n" + stg += f"sofb_nrpoints = {self.sofb_nrpoints:.3f}\n" + stg += f"sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n" + stg += f"sofb_maxorberr = {self.sofb_maxorberr:.3f}\n" + stg += f"cycling_nr_steps = {self.cycling_nr_steps:.3f}\n" return stg @staticmethod @@ -392,26 +69,26 @@ def get_default_groups(ngroups=8): """.""" if ngroups == 2: group_class = [ - [('M2', ''), ('C3', '1'), ('C1', '2'), ('C4', '')], - [('C1', '1'), ('C3', '2'), ('C2', ''), ('M1', '')], + [("M2", ""), ("C3", "1"), ("C1", "2"), ("C4", "")], + [("C1", "1"), ("C3", "2"), ("C2", ""), ("M1", "")], ] elif ngroups in {8, 16}: group_class = [ - [('M2', '')], - [('C3', '1')], - [('C1', '1')], - [('C3', '2')], - [('C1', '2')], - [('C4', '')], - [('C2', '')], - [('M1', '')], + [("M2", "")], + [("C3", "1")], + [("C1", "1")], + [("C3", "2")], + [("C1", "2")], + [("C4", "")], + [("C2", "")], + [("M1", "")], ] else: group_class = [ - [('M2', ''), ('C3', '1')], - [('C1', '1'), ('C3', '2')], - [('C1', '2'), ('C4', '')], - [('C2', ''), ('M1', '')], + [("M2", ""), ("C3", "1")], + [("C1", "1"), ("C3", "2")], + [("C1", "2"), ("C4", "")], + [("C2", ""), ("M1", "")], ] groups = [ @@ -434,11 +111,11 @@ def get_default_groups(ngroups=8): sorted( group, key=lambda x: { - 'Q4': 0, - 'Q1': 1, - 'QDB2': 2, - 'QDP2': 3, - 'QS': 4, + "Q4": 0, + "Q1": 1, + "QDB2": 2, + "QDP2": 3, + "QS": 4, }.get( ParallelBBAParams.QUADNAMES[ ParallelBBAParams.BPMNAMES.index(x) @@ -462,79 +139,77 @@ def get_default_dkl(self, ngroups=8, groups=None): class DoParallelBBA(_BaseClass): """.""" - STATUS = _get_namedtuple('Status', ['Fail', 'Success']) + STATUS = _get_namedtuple("Status", ["Fail", "Success"]) def __init__(self, isonline=True): """.""" super().__init__( params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline ) - self.data['bpmnames'] = list(ParallelBBAParams.BPMNAMES) - self.data['quadnames'] = list(ParallelBBAParams.QUADNAMES) - self.data['measure'] = list() - self.data['groups2dopbba'] = ParallelBBAParams.get_default_groups() - self.data['delta_kl'] = self.params.get_default_dkl( - groups=self.data['groups2dopbba'] + self.data["bpmnames"] = list(ParallelBBAParams.BPMNAMES) + self.data["quadnames"] = list(ParallelBBAParams.QUADNAMES) + self.data["measure"] = list() + self.data["groups2dopbba"] = ParallelBBAParams.get_default_groups() + self.data["delta_kl"] = self.params.get_default_dkl( + groups=self.data["groups2dopbba"] ) - self.data['log'] = list() - self.data['jacobians'] = list() + self.data["log"] = list() + self.data["jacobians"] = list() self._model = None self._fam_data = None if self.isonline: - self.devices['sofb'] = _SOFB(_SOFB.DEVICES.SI) - self.devices['currinfosi'] = _CurrInfoSI() + self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) + self.devices["currinfosi"] = _CurrInfoSI() self.connect_to_quadrupoles() def __str__(self): """.""" - stn = 'Params\n' + stn = "Params\n" stp = self.params.__str__() - stp = ' ' + stp.replace('\n', '\n ') - stn += stp + '\n' + stp = " " + stp.replace("\n", "\n ") + stn += stp + "\n" connected = str(self.connected and len(self.devices.keys()) > 0) - stn += 'Connected? ' + connected + '\n\n' - stn += ' {:^20s} {:^20s} {:^7s}\n'.format( - 'BPM', 'Quad', 'dKL' - ) - tmplt = '{:03d}: {:^20s} {:^20s} {:+.3f}\n' + stn += "Connected? " + connected + "\n\n" + stn += " {:^20s} {:^20s} {:^7s}\n".format("BPM", "Quad", "dKL") + tmplt = "{:03d}: {:^20s} {:^20s} {:+.3f}\n" dta = self.data - for group_id, group in enumerate(self.data['groups2dopbba']): - stn += f'> Group {group_id:03d}\n' + for group_id, group in enumerate(self.data["groups2dopbba"]): + stn += f"> Group {group_id:03d}\n" for j, bpm in enumerate(group): - idx = dta['bpmnames'].index(bpm) + idx = dta["bpmnames"].index(bpm) stn += tmplt.format( idx, - dta['bpmnames'][idx], - dta['quadnames'][idx], - dta['delta_kl'][group_id][j] + dta["bpmnames"][idx], + dta["quadnames"][idx], + dta["delta_kl"][group_id][j], ) - stn += '\n' + stn += "\n" return stn @property def havebeam(self): """.""" - haveb = self.devices['currinfosi'] + haveb = self.devices["currinfosi"] return haveb.connected and haveb.storedbeam @property def measuredbpms(self): """.""" mesured = [] - for group in self.data['measure']: - mesured.extend(group['bpms']) + for group in self.data["measure"]: + mesured.extend(group["bpms"]) return sorted(mesured) # #### pbba groups and deltas ##### @property def groups2dopbba(self): """.""" - return _dcopy(self.data['groups2dopbba']) + return _dcopy(self.data["groups2dopbba"]) @groups2dopbba.setter def groups2dopbba(self, groups): - self.data['groups2dopbba'] = [ + self.data["groups2dopbba"] = [ [_PVName(bpm) for bpm in group if isinstance(bpm, str)] for group in groups if isinstance(group, (list, tuple, _np.ndarray)) @@ -543,38 +218,38 @@ def groups2dopbba(self, groups): @property def delta_kl(self): """.""" - return _dcopy(self.data['delta_kl']) + return _dcopy(self.data["delta_kl"]) @delta_kl.setter def delta_kl(self, value): _max = self.params.quad_deltakl - for i, group in enumerate(self.data['groups2dopbba']): + for i, group in enumerate(self.data["groups2dopbba"]): if len(value[i]) != len(group): raise ValueError( - f'size mismatch between group {i} and given delta_kl' + f"size mismatch between group {i} and given delta_kl" ) if any([abs(v) > _max for v in value[i]]): raise ValueError(f"values for delta kl can't exceed {_max}") - self.data['delta_kl'] = _dcopy(value) + self.data["delta_kl"] = _dcopy(value) # #### model utils ##### @property def jacobians(self): """.""" - return _dcopy(self.data['jacobians']) + return _dcopy(self.data["jacobians"]) @jacobians.setter def jacobians(self, jacs): """.""" - if len(jacs) != len(self.data['groups2dopbba']): - raise ValueError('Size not compatible.') - self.data['jacobians'] = _dcopy(jacs) + if len(jacs) != len(self.data["groups2dopbba"]): + raise ValueError("Size not compatible.") + self.data["jacobians"] = _dcopy(jacs) @property def model(self): """.""" if self._model is None: - self._log_print('\n Undefined model... setting a default one') + self._log_print("\n Undefined model... setting a default one") self._model = _si.create_accelerator() self._model.cavity_on = True self._model.radiation_on = 1 @@ -588,7 +263,7 @@ def model(self): def model(self, value): if not value.cavity_on and value.radiation_on != 1: raise ValueError( - 'cavity_on must be True and radiation_on must be 1' + "cavity_on must be True and radiation_on must be 1" ) self._model = value self._fam_data = _si.families.get_family_data(self._model) @@ -601,12 +276,12 @@ def fam_data(self): # ### quadrupole connection ##### def connect_to_quadrupoles(self): """.""" - for qname in self.data['quadnames']: + for qname in self.data["quadnames"]: if qname in self.devices: continue self.devices[qname] = _PowerSupply( qname, - props2init=('PwrState-Sts', 'KL-SP', 'KL-RB', 'KLRef-Mon') + props2init=("PwrState-Sts", "KL-SP", "KL-RB", "KLRef-Mon"), ) # #### sofb utils ##### @@ -614,7 +289,7 @@ def get_orbit(self): """.""" if not self.havebeam: return - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] nrpts = sofb.nr_points sofb.nr_points = self.params.sofb_nrpoints @@ -629,7 +304,7 @@ def correct_orbit(self): """.""" if not self.havebeam: return - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] sofb.correct_orbit_manually( nr_iters=self.params.sofb_maxcorriter, residue=self.params.sofb_maxorberr, @@ -637,16 +312,16 @@ def correct_orbit(self): def get_kicks(self): """.""" - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] return _np.r_[sofb.kickch, sofb.kickcv, sofb.kickrf] def set_delta_kicks(self, dkicks): """.""" - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] nch, ncv, nrf = sofb._data.nr_ch, sofb._data.nr_cv, 1 if len(dkicks) != nch + ncv + nrf: raise ValueError( - f'invalid dim for dkicks, must have shape=({nch + ncv + nrf},)' + f"invalid dim for dkicks, must have shape=({nch + ncv + nrf},)" ) dch, dcv, drf = dkicks[:nch], dkicks[nch : nch + ncv], dkicks[-1] @@ -656,21 +331,21 @@ def set_delta_kicks(self, dkicks): @property def enbllistbpm(self): """.""" - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] enblx = sofb.bpmxenbl.copy() enbly = sofb.bpmyenbl.copy() return _np.array(_np.hstack([enblx, enbly]), dtype=bool) @enbllistbpm.setter def enbllistbpm(self, value): - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] nbpms = sofb._data.nr_bpms if len(value) != 2 * nbpms: - raise ValueError(f'Invalid size! Must be {2*nbpms}.') + raise ValueError(f"Invalid size! Must be {2 * nbpms}.") if all(v in [0, 1, True, False] for v in value): value = _np.array(value, dtype=bool) else: - raise ValueError('Values must be boolean (0 / 1 or True / False).') + raise ValueError("Values must be boolean (0 / 1 or True / False).") sofb.bpmxenbl = value[:nbpms] sofb.bpmyenbl = value[nbpms:] @@ -678,9 +353,9 @@ def enbllistbpm(self, value): def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): """.""" - bpms = self.data['groups2dopbba'][group_id] - quad_names = self.data['quadnames'] - bpm_names = self.data['bpmnames'] + bpms = self.data["groups2dopbba"][group_id] + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] for strength, bpmname in zip(strengths, bpms): # noqa: B905 quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] @@ -693,20 +368,20 @@ def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] if not quad.wait_float( - 'KLRef-Mon', + "KLRef-Mon", strength, rel_tol=0.0, - abs_tol=0.05*self.params.quad_deltakl, - timeout=self.params.wait_quadrupole + abs_tol=0.05 * self.params.quad_deltakl, + timeout=self.params.wait_quadrupole, ): return DoParallelBBA.STATUS.Fail return DoParallelBBA.STATUS.Success def get_quad_strengths(self, group_id): """.""" - bpms = self.data['groups2dopbba'][group_id] - quad_names = self.data['quadnames'] - bpm_names = self.data['bpmnames'] + bpms = self.data["groups2dopbba"][group_id] + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] strengths = [] for bpmname in bpms: @@ -717,15 +392,15 @@ def get_quad_strengths(self, group_id): def get_quad_strength_limits(self, group_id, margin=0.0005): """.""" - bpms = self.data['groups2dopbba'][group_id] - quad_names = self.data['quadnames'] - bpm_names = self.data['bpmnames'] + bpms = self.data["groups2dopbba"][group_id] + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] limits = [] for bpmname in bpms: quadname = quad_names[bpm_names.index(bpmname)] quad = self.devices[quadname] - pv = quad.pv_object('KL-SP') + pv = quad.pv_object("KL-SP") upp = pv.upper_disp_limit low = pv.lower_disp_limit # Limits are interchanged in some quads: @@ -736,12 +411,12 @@ def get_quad_strength_limits(self, group_id, margin=0.0005): def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): """.""" - bpms = self.data['groups2dopbba'][group_id] - quad_names = self.data['quadnames'] - bpm_names = self.data['bpmnames'] + bpms = self.data["groups2dopbba"][group_id] + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] quadlims = self.get_quad_strength_limits(group_id, margin=margin) - delta_kl = self.data['delta_kl'][group_id] + delta_kl = self.data["delta_kl"][group_id] if init_strengths is None: strengths = self.get_quad_strengths(group_id) @@ -754,20 +429,19 @@ def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): stren = strengths[idx] dkl = delta_kl[idx] lolim, hilim = quadlims[idx] - low = min(stren+dkl/2, stren-dkl/2) - upp = max(stren+dkl/2, stren-dkl/2) + low = min(stren + dkl / 2, stren - dkl / 2) + upp = max(stren + dkl / 2, stren - dkl / 2) if upp > hilim or low < lolim: max_delta_kl = min(hilim - stren, stren - lolim) msg = f"WARN: {quadname} KL = {stren:.2g}, dKL = {abs(dkl):.2g}. " - msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). Max. dKL = {max_delta_kl*2:.2g}." + msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). Max. dKL = {max_delta_kl * 2:.2g}." self._log_print(msg) ok = False return ok - def meas_ios(self, group_id, init_strengths=None): """.""" - delta_strens = self.data['delta_kl'][group_id] + delta_strens = self.data["delta_kl"][group_id] if init_strengths is None: strens_orig = self.get_quad_strengths(group_id) @@ -798,18 +472,18 @@ def calc_ios_jacobians(self, groups_to_calc=None): # noqa: C901 """Calculate the IOS Response Matrices for all groups.""" model = self.model _orbcorr = _OrbitCorr( - model=model, acc='SI', corr_system='SOFB', use6dtrack=True + model=model, acc="SI", corr_system="SOFB", use6dtrack=True ) - quadindices = self._get_quads_indices_in_model(self.data['quadnames']) + quadindices = self._get_quads_indices_in_model(self.data["quadnames"]) def _get_or_set_kl(bname, value=None): _do = getattr if value is None else setattr - bidx = self.data['bpmnames'].index(bname) + bidx = self.data["bpmnames"].index(bname) qidx = quadindices[bidx] - if 'QS' == self.data['quadnames'][bidx].dev: - return _do(model[qidx], 'KsL', value) + if "QS" == self.data["quadnames"][bidx].dev: + return _do(model[qidx], "KsL", value) else: - return _do(model[qidx], 'KL', value) + return _do(model[qidx], "KL", value) def _get_quad_strengths(group): strens = [] @@ -823,17 +497,17 @@ def _set_quad_strengths(group, strengths): jacobians = [] groups_to_calc = ( - _np.arange(len(self.data['groups2dopbba'])) + _np.arange(len(self.data["groups2dopbba"])) if groups_to_calc is None else groups_to_calc ) for group_id in groups_to_calc: - group = self.data['groups2dopbba'][group_id] + group = self.data["groups2dopbba"][group_id] try: - delta_strens = self.data['delta_kl'][group_id] + delta_strens = self.data["delta_kl"][group_id] except Exception as e: str_msg = 'undefined or empty "delta_kl"' - str_msg += f' of group {group_id}' + str_msg += f" of group {group_id}" raise IndexError(str_msg) from e strens_orig = _get_quad_strengths(group) @@ -859,28 +533,29 @@ def _set_quad_strengths(group, strengths): def analyze_groups(self, analyze_coupling=False): """Helper function to analyze the groups' properties.""" - if not self.data['jacobians']: - raise ValueError('Please calculate and set jacobians first.') + if not self.data["jacobians"]: + raise ValueError("Please calculate and set jacobians first.") anl = [] - for group_id in range(len(self.data['groups2dopbba'])): - print(f'Analyzing group: {group_id:d}') + for group_id in range(len(self.data["groups2dopbba"])): + print(f"Analyzing group: {group_id:d}") anl.append(self.analyze_group(group_id, analyze_coupling)) return anl def analyze_group(self, group_id, analyze_coupling=False): """Helper function to analyze group's properties.""" - jacobian = self.data['jacobians'][group_id] + jacobian = self.data["jacobians"][group_id] u_mat, svals, vt_mat = _np.linalg.svd(jacobian) model = self.model - quadindices = self._get_quads_indices_in_model(self.data['quadnames']) - delta_strens = self.data['delta_kl'][group_id] - group = self.data['groups2dopbba'][group_id] + quadindices = self._get_quads_indices_in_model(self.data["quadnames"]) + delta_strens = self.data["delta_kl"][group_id] + group = self.data["groups2dopbba"][group_id] tune_variation = [_pyacc.optics.get_frac_tunes(model)[:2]] if analyze_coupling: + def _get_coupling_parameters(): rad_on, cav_on = model.radiation_on, model.cavity_on model.radiation_on = 0 @@ -897,10 +572,10 @@ def _get_coupling_parameters(): for fac in [1, -2, 1]: for dkl, bpm in zip(delta_strens, group): # noqa: B905 - _id = self.data['bpmnames'].index(bpm) - qname = self.data['quadnames'][_id] + _id = self.data["bpmnames"].index(bpm) + qname = self.data["quadnames"][_id] qidx = quadindices[_id] - if 'QS' in qname: + if "QS" in qname: model[qidx].KsL += fac * dkl / 2 else: model[qidx].KL += fac * dkl / 2 @@ -911,27 +586,27 @@ def _get_coupling_parameters(): std_ratio_variation.append(std_ratio) ret = { - 'u_matrix': u_mat, - 'vt_matrix': vt_mat, - 'svals': svals, - 'tune_variation': _np.array(tune_variation), + "u_matrix": u_mat, + "vt_matrix": vt_mat, + "svals": svals, + "tune_variation": _np.array(tune_variation), } if analyze_coupling: - ret['min_tunesep_variation'] = _np.array(min_tunesep_variation) - ret['std_ratio_variation'] = _np.array(std_ratio_variation) + ret["min_tunesep_variation"] = _np.array(min_tunesep_variation) + ret["std_ratio_variation"] = _np.array(std_ratio_variation) return ret def process_data(self): """.""" - for group_id in range(len(self.data['groups2dopbba'])): + for group_id in range(len(self.data["groups2dopbba"])): self.process_data_single_group(group_id) def process_data_single_group(self, group_id): """.""" - meas_data = self.data['measure'][group_id] - bpmnames = self.data['bpmnames'] + meas_data = self.data["measure"][group_id] + bpmnames = self.data["bpmnames"] nbpms = len(bpmnames) - orbit = meas_data['orbit_end'] + orbit = meas_data["orbit_end"] # #### error estimation ? ##### # ios_iter = meas_data['ios_iter'] @@ -942,18 +617,18 @@ def process_data_single_group(self, group_id): stdx0 = 0.0 stdy0 = 0.0 - for bpm in meas_data['bpms']: + for bpm in meas_data["bpms"]: bpm_idx = bpmnames.index(bpm) self.analysis[bpm] = { - 'x0': orbit[bpm_idx], - 'y0': orbit[bpm_idx + nbpms], - 'stdx0': stdx0, - 'stdy0': stdy0 + "x0": orbit[bpm_idx], + "y0": orbit[bpm_idx + nbpms], + "stdx0": stdx0, + "stdy0": stdy0, } def get_pbba_results(self, error=False): """.""" - bpms = self.data['bpmnames'] + bpms = self.data["bpmnames"] bbax = _np.zeros(len(bpms)) bbay = _np.zeros(len(bpms)) if error: @@ -963,11 +638,11 @@ def get_pbba_results(self, error=False): res = self.analysis.get(bpm) if not res: continue - bbax[idx] = res['x0'] - bbay[idx] = res['y0'] - if error and 'stdx0' in res: - bbaxerr[idx] = res['stdx0'] - bbayerr[idx] = res['stdy0'] + bbax[idx] = res["x0"] + bbay[idx] = res["y0"] + if error and "stdx0" in res: + bbaxerr[idx] = res["stdx0"] + bbayerr[idx] = res["stdy0"] if error: return bbax, bbay, bbaxerr, bbayerr return bbax, bbay @@ -979,8 +654,8 @@ def _get_quads_indices_in_model(self, quadnames): quadindices = [] for qname in quadnames: key = qname.dev - idx = fam_data[key]['devnames'].index(qname) - qindex = fam_data[key]['index'][idx] + idx = fam_data[key]["devnames"].index(qname) + qindex = fam_data[key]["index"][idx] qindex = qindex[0] if len(qindex) == 1 else qindex quadindices.append(qindex) return quadindices @@ -988,78 +663,77 @@ def _get_quads_indices_in_model(self, quadnames): def _do_pbba(self): tini = _datetime.datetime.fromtimestamp(_time.time()) self._log_print( - 'Starting measurement at {:s}'.format( - tini.strftime('%Y-%m-%d %Hh%Mm%Ss') + "Starting measurement at {:s}".format( + tini.strftime("%Y-%m-%d %Hh%Mm%Ss") ) ) - groups = self.data['groups2dopbba'] + groups = self.data["groups2dopbba"] if not all([self.check_isvalid_dkl(g) for g, _ in enumerate(groups)]): - self._log_print('Adjust quad strength or change dKL first.') + self._log_print("Adjust quad strength or change dKL first.") return - self.data['jacobians'] = self.calc_ios_jacobians() - self.data['measure'] = list() + self.data["jacobians"] = self.calc_ios_jacobians() + self.data["measure"] = list() - sofb = self.devices['sofb'] + sofb = self.devices["sofb"] if sofb.autocorrsts: - self._log_print('\nSOFB feedback is enabled. Please desable it first.') + self._log_print( + "\nSOFB feedback is enabled. Please desable it first." + ) return for gid, _ in enumerate(groups): if self._stopevt.is_set(): - self._log_print('\nStopped!') + self._log_print("\nStopped!") break if not self.havebeam: - self._log_print('\nBeam was Lost') + self._log_print("\nBeam was Lost") break - self._log_print('\nCorrecting Orbit... ', end='') + self._log_print("\nCorrecting Orbit... ", end="") self.correct_orbit() - self._log_print('Ok!') + self._log_print("Ok!") if not self._dopbba_single_group(gid): break - self._log_print('\nCorrecting Orbit... ', end='') + self._log_print("\nCorrecting Orbit... ", end="") self.correct_orbit() - self._log_print('Ok!') + self._log_print("Ok!") tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) - dtime = dtime.split('.')[0] - self._log_print('\nFinished! Elapsed time {:s}'.format(dtime)) + dtime = dtime.split(".")[0] + self._log_print("\nFinished! Elapsed time {:s}".format(dtime)) def _dopbba_single_group(self, group_id): """.""" tini = _datetime.datetime.fromtimestamp(_time.time()) - strtini = tini.strftime('%Hh%Mm%Ss') - self._log_print(f'{strtini:s}: Doing PBBA for Group {group_id:d}') + strtini = tini.strftime("%Hh%Mm%Ss") + self._log_print(f"{strtini:s}: Doing PBBA for Group {group_id:d}") enblbpm = self.enbllistbpm # cut jacobian with only enabled bpms - jac = (self.data['jacobians'][group_id])[enblbpm, :] + jac = (self.data["jacobians"][group_id])[enblbpm, :] inv_jac = self._calc_inverse_jacobian(jac, group_id) group_data = { - 'bpms': self.data['groups2dopbba'][group_id], - 'strengths_init': self.get_quad_strengths(group_id), - 'orbit_init': self.get_orbit(), - 'kicks_init': self.get_kicks(), - 'enbllistbpm': enblbpm.copy() - } + "bpms": self.data["groups2dopbba"][group_id], + "strengths_init": self.get_quad_strengths(group_id), + "orbit_init": self.get_orbit(), + "kicks_init": self.get_kicks(), + "enbllistbpm": enblbpm.copy(), + } - self._log_print(' Cycling:') - msg, sts = self._do_cycling( - group_id, - group_data['strengths_init'], - ) + self._log_print(" Cycling:") + msg, sts = self._do_cycling(group_id, group_data["strengths_init"]) if not sts: self._restore_init_conditions( group_id, - group_data['strengths_init'], - extra_info_before_message=msg + group_data["strengths_init"], + extra_info_before_message=msg, ) nr_iters = 0 else: # proceed to IOS correction - self._log_print(' Correcting IOS:') + self._log_print(" Correcting IOS:") nr_iters = self.params.corr_max_nr_iters ios_iter, dkicks_iter = [], [] @@ -1069,65 +743,65 @@ def _dopbba_single_group(self, group_id): _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) for i in range(nr_iters): self._log_print( - ' {:02d}/{:02d} --> '.format(i + 1, nr_iters), end='' + " {:02d}/{:02d} --> ".format(i + 1, nr_iters), end="" ) if self._stopevt.is_set(): self._restore_init_conditions( group_id, - group_data['strengths_init'], - extra_info_before_message="Measurement stopped. " + group_data["strengths_init"], + extra_info_before_message="Measurement stopped. ", ) break if not self.havebeam: self._restore_init_conditions( group_id, - group_data['strengths_init'], - extra_info_before_message="Error: beam is off. " + group_data["strengths_init"], + extra_info_before_message="Error: beam is off. ", ) break - ios, sts = self.meas_ios(group_id, group_data['strengths_init']) + ios, sts = self.meas_ios(group_id, group_data["strengths_init"]) if not sts: self._restore_init_conditions( group_id, - group_data['strengths_init'], - extra_info_before_message="Fail while measuring IOS. " + group_data["strengths_init"], + extra_info_before_message="Fail while measuring IOS. ", ) break ios_iter.append(ios) # save ios (all bpms) ios = ios[enblbpm] # use only enabled bpms for correction # check convergence - if (i > 0): + if i > 0: dios_p = _func(ios_iter[-2][enblbpm], ios) if dios_p < 0.0: prev_dkicks = dkicks_iter[-1] self.set_delta_kicks(prev_dkicks) - self._log_print('Done.', end=' ') + self._log_print("Done.", end=" ") converged = False increased = True break dios_i = _func(ios_iter[0][enblbpm], ios) if dios_i < self.params.ios_conv_threshold: - self._log_print('Done.', end=' ') + self._log_print("Done.", end=" ") converged = True break dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - self._log_print('Done.', end=' ') + self._log_print("Done.", end=" ") if sts and converged: - print(f'IOS converged ({i:d} iterations).') + print(f"IOS converged ({i:d} iterations).") elif sts and not converged and increased: - print(f'IOS increased ({i:d} iterations).') + print(f"IOS increased ({i:d} iterations).") elif sts and not converged and not increased: - ios, sts = self.meas_ios(group_id, group_data['strengths_init']) + ios, sts = self.meas_ios(group_id, group_data["strengths_init"]) if not sts: self._restore_init_conditions( group_id, - group_data['strengths_init'], - extra_info_before_message="Fail while measuring IOS. " + group_data["strengths_init"], + extra_info_before_message="Fail while measuring IOS. ", ) else: ios_iter.append(ios) @@ -1136,34 +810,32 @@ def _dopbba_single_group(self, group_id): dios_p = _func(ios_iter[-2][enblbpm], ios) if dios_i < self.params.ios_conv_threshold: self._log_print( - f'Max iterations reached ({i+1:d}), but IOS converged.' + f"Max iterations reached ({i + 1:d}), but IOS converged." ) elif dios_p < 0.0: self._log_print( - f'Max iterations reached ({i+1:d}), and IOS increased.' + f"Max iterations reached ({i + 1:d}), and IOS increased." ) else: - self._log_print( - f'Max iterations reached ({i+1:d}).' - ) + self._log_print(f"Max iterations reached ({i + 1:d}).") - group_data['kicks_end'] = self.get_kicks() - group_data['ios_iter'] = ios_iter - group_data['dkicks_iter'] = dkicks_iter - group_data['orbit_end'] = self.get_orbit() - group_data['delta_kl'] = self.data['delta_kl'][group_id] - self.data['measure'].append(group_data) + group_data["kicks_end"] = self.get_kicks() + group_data["ios_iter"] = ios_iter + group_data["dkicks_iter"] = dkicks_iter + group_data["orbit_end"] = self.get_orbit() + group_data["delta_kl"] = self.data["delta_kl"][group_id] + self.data["measure"].append(group_data) self.correct_orbit() tfin = _datetime.datetime.fromtimestamp(_time.time()) dtime = str(tfin - tini) - dtime = dtime.split('.')[0] - msg = ' Finished. Status: ' + dtime = dtime.split(".")[0] + msg = " Finished. Status: " if sts: - self._log_print(msg + 'OK! Elapsed time: {:s}'.format(dtime)) + self._log_print(msg + "OK! Elapsed time: {:s}".format(dtime)) else: - self._log_print(msg + 'Fail! Elapsed time: {:s}'.format(dtime)) + self._log_print(msg + "Fail! Elapsed time: {:s}".format(dtime)) return sts def _calc_inverse_jacobian(self, jacobian, group_id): @@ -1176,62 +848,58 @@ def _calc_inverse_jacobian(self, jacobian, group_id): return vt.T @ _np.diag(i_s) @ u.T def _do_cycling(self, group_id, init_strengths): - delta_strengths = self.data['delta_kl'][group_id] + delta_strengths = self.data["delta_kl"][group_id] nr_cycles = self.params.cycling_nr_steps for i in range(nr_cycles): self._log_print( - ' {:02d}/{:02d} --> '.format(i+1, nr_cycles), end='' + " {:02d}/{:02d} --> ".format(i + 1, nr_cycles), end="" ) if self._stopevt.is_set(): return "Event stopped! ", DoParallelBBA.STATUS.Fail if not self.havebeam: return "No beam! ", DoParallelBBA.STATUS.Fail if not self.set_quad_strengths( - group_id, - init_strengths + delta_strengths / 2 + group_id, init_strengths + delta_strengths / 2 ): return "Fail! ", DoParallelBBA.STATUS.Fail if not self.set_quad_strengths( - group_id, - init_strengths - delta_strengths / 2 + group_id, init_strengths - delta_strengths / 2 ): return "Fail! ", DoParallelBBA.STATUS.Fail - if not self.set_quad_strengths( - group_id, - init_strengths - ): + if not self.set_quad_strengths(group_id, init_strengths): return "Fail! ", DoParallelBBA.STATUS.Fail - self._log_print('Ok') + self._log_print("Ok") return "", DoParallelBBA.STATUS.Success - def _restore_init_conditions(self, - group_id, - init_strengths, - message="Restoring initial conditions and exiting...", - correct_orbit=True, - extra_info_before_message="" - ): + def _restore_init_conditions( + self, + group_id, + init_strengths, + message="Restoring initial conditions and exiting...", + correct_orbit=True, + extra_info_before_message="", + ): """.""" - self._log_print(extra_info_before_message+message) + self._log_print(extra_info_before_message + message) self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) - bpms = self.data['groups2dopbba'][group_id] - quad_names = self.data['quadnames'] - bpm_names = self.data['bpmnames'] + bpms = self.data["groups2dopbba"][group_id] + quad_names = self.data["quadnames"] + bpm_names = self.data["bpmnames"] for strength, bpmname in zip(init_strengths, bpms): # noqa: B905 qname = quad_names[bpm_names.index(bpmname)] quad = self.devices[qname] if not quad.wait_float( - 'KLRef-Mon', + "KLRef-Mon", strength, rel_tol=0.0, - abs_tol=0.05*self.params.quad_deltakl, - timeout=self.params.wait_quadrupole + abs_tol=0.05 * self.params.quad_deltakl, + timeout=self.params.wait_quadrupole, ): self._log_print( - f' {qname}: Could not be restored to initial strength' + f" {qname}: Could not be restored to initial strength" ) if correct_orbit: @@ -1239,5 +907,5 @@ def _restore_init_conditions(self, def _log_print(self, msg): """.""" - self.data['log'].append((_time.time(), msg)) + self.data["log"].append((_time.time(), msg)) print(msg) From 393f227ee686129709c593419ebe1e737278c871 Mon Sep 17 00:00:00 2001 From: Vitor Date: Sun, 2 Aug 2026 22:41:12 -0300 Subject: [PATCH 51/61] ADD (COMISS.ACBBA): Add initial code for AC-BBA measurement --- apsuite/commisslib/measure_bba_ac.py | 1192 +++++++++++++++++++++++++- 1 file changed, 1178 insertions(+), 14 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 41215babe..03f873d2b 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -1,50 +1,1214 @@ """Main module.""" -import datetime as _datetime import time as _time +import datetime as _datetime +from functools import reduce as _red +import operator as _opr from copy import deepcopy as _dcopy +import numpy as _np + +from mathphys.functions import ( + get_namedtuple as _get_namedtuple, + load as _load, + save as _save, +) + from siriuspy.devices import ( + SOFB as _SOFB, + StrengthConv as _StrengthConv, CurrInfoSI as _CurrInfoSI, + FamBPMs as _FamBPMs, PowerSupply as _PowerSupply, - SOFB as _SOFB, + RFGen as _RFGen, + Trigger as _Trigger, + Event as _Event, + EVG as _EVG, + Tune as _Tune, ) +from siriuspy.search import LLTimeSearch as _LLTime +from siriuspy.sofb.csdev import SOFBFactory as _SOFBFactory +from siriuspy.clientconfigdb import ConfigDBClient as _ConfigDBClient -from siriuspy.namesys import SiriusPVName as _PVName - -from ..utils import ( +from apsuite.utils import ( ParamsBaseClass as _ParamsBaseClass, ThreadedMeasBaseClass as _BaseClass, ) -from .measure_bba import BBAParams as _BBAParams +from apsuite.commisslib.meas_ac_orm import MeasACORM as _MeasACORM +from apsuite.commisslib.measure_bba import BBAParams as _BBAParams class ACBBAParams(_ParamsBaseClass): - """.""" + """Parameters for AC-BBA.""" - BPMNAMES = _BBAParams.BPMNAMES - QUADNAMES = _BBAParams.QUADNAMES + BPMNAMES = _BBAParams.BPMNAMES[:2] + QUADNAMES = _BBAParams.QUADNAMES[:2] + QUAD_MODULATION_MODE = _get_namedtuple("QuadModulationMode", ["AC", "DC"]) def __init__(self): """.""" super().__init__() + self.timeout_bpms = 60 # [s] + self.timeout_correctors = 20 # [s] + + self.quad_modulation_mode = ACBBAParams.QUAD_MODULATION_MODE.DC + self.quad_delta_kl = 0.02 # [1/m] + self.wait_quadrupole = 0.3 # [s] + + self.cv_freq = 17.0 # [Hz] + self.ch_freq = 23.0 # [Hz] + self.qn_freq = 0.0 # [Hz] + self.qs_freq = 0.0 # [Hz] + + self.excit_time = 4 # [s] + self.corrs_delay = 5e-3 # [s] + + self.ch_kick = 5 # [urad] + self.cv_kick = 5 # [urad] + + self.dorbx = 100.0 # [um] + self.dorby = 100.0 # [um] + + self.use_qs_excitation = False + self.use_qn_excitation = False + + self.measure_bpms_noise = True + self.acq_rate = "FAcq" + self.orm_name = "ref_respmat" + + self.sofb_maxcorriter = 5 + self.sofb_maxorberr = 5 # [um] + self.correct_orbit_each_step = True def __str__(self): """.""" - stg = "" - return stg + st = "BBA-AC measurement parameters." + return st class DoACBBA(_BaseClass): - """.""" + """AC Beam Based Alignment measurement.""" + + STATUS = _get_namedtuple("Status", ["Fail", "Success"]) + + TIMING_STATE_OPTIONS = ( + "trigbpms_source", + "trigbpms_nr_pulses", + "trigbpms_delay_raw", + "trigcorrs_source", + "trigcorrs_nr_pulses", + "trigcorrs_delay_raw", + "trigcorrs_delta_delay_raw", + "evt_mode", + "evt_delay_raw", + ) - def __init__(self, isonline=True): + def __init__(self, isonline=True, func=None, LLTime=_LLTime): """.""" self.params = ACBBAParams() super().__init__( params=self.params, target=self._do_acbba, isonline=isonline ) + self.verbose = True + + self.data["bpmnames"] = list(ACBBAParams.BPMNAMES) + self.data["quadnames"] = list(ACBBAParams.QUADNAMES) + self.data["scancenterx"] = _np.zeros(len(ACBBAParams.BPMNAMES)) + self.data["scancentery"] = _np.zeros(len(ACBBAParams.BPMNAMES)) + + self._bpms2dobba = self.data["bpmnames"] + self.sofb_data = None + self.configdb = None + self._orm = None + + if self.isonline: + self.sofb_data = _SOFBFactory.create("SI") + self.configdb = _ConfigDBClient(config_type="si_bbadata") + self._create_devices() + else: + if func is not None: + func(self) + if LLTime is not None: + self._LLTime = LLTime + + self.data["log"] = [(_time.time(), "Started.")] + + @property + def bpms2dobba(self): + """List of BPMs to perform BBA.""" + return self._bpms2dobba.copy() + + @bpms2dobba.setter + def bpms2dobba(self, bpmnames): + """List of BPMs to perform BBA.""" + if not isinstance(bpmnames, (list, tuple)): + raise TypeError("bpmnames must be a list (or tuple) of strings.") + for bpm in bpmnames: + if bpm not in self.data["bpmnames"]: + msg = f'Invalid BPM: {bpm}. Check "ACBBAParams.BPMNAMES".' + raise ValueError(msg) + self._bpms2dobba = bpmnames + + def _create_devices(self): + """Create and connect to devices.""" + # BPMs + self.bpms = _FamBPMs(mturn_signals2acq="XY", props2init="acq") + self.devices["fambpms"] = self.bpms + + # Quadrupoles + props = ["PwrState-Sts", "KL-SP", "KL-RB", "KLRef-Mon"] + for qname in self.data["quadnames"]: + if qname in self.devices: + continue + self.devices[qname] = _PowerSupply(qname, props2init=props) + + # Kick converters + sofbdata = self.sofb_data + self.devices.update({ + n + ":StrengthConv": _StrengthConv(n, "Ref-Mon") + for n in (sofbdata.ch_names + sofbdata.cv_names) + }) + + # Correctors + props = [ + "Kick-SP", + "OpMode-Sel", + "OpMode-Sts", + "Current-SP", + "Current-RB", + "Current-Mon", + "CurrentRef-Mon", + "CycleType-Sel", + "CycleFreq-SP", + "CycleAmpl-SP", + "CycleOffset-SP", + "CycleAuxParam-SP", + "CycleAuxParam-RB", + "CycleNrCycles-SP", + "CycleAmpl-RB", + "CycleOffset-RB", + "CycleFreq-RB", + "CycleNrCycles-RB", + "CycleType-Sts", + "CycleEnbl-Mon", + "ParamPWMFreq-Cte", + ] + self.devices.update({ + name: _PowerSupply(name, props2init=props) + for name in (sofbdata.ch_names + sofbdata.cv_names) + }) + + # SOFB + self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) + + # CurrInfo + self.devices["currinfo"] = _CurrInfoSI() + + # RF generator + props = ["GeneralFreq-SP", "GeneralFreq-RB"] + self.devices["rfgen"] = _RFGen(props2init=props) + + # Tune + self.devices["tune"] = _Tune(_Tune.DEVICES.SI) + + # BPMs Trigger + props = [ + "Src-Sts", + "NrPulses-RB", + "DelayRaw-RB", + "Src-Sel", + "NrPulses-SP", + "DelayRaw-SP", + ] + self.devices["trigbpms"] = _Trigger("SI-Fam:TI-BPM", props2init=props) + + # Correctors Trigger + props = [ + "Src-Sts", + "NrPulses-RB", + "DelayRaw-RB", + "DeltaDelayRaw-RB", + "Src-Sel", + "NrPulses-SP", + "DelayRaw-SP", + "LowLvlTriggers-Cte", + "DeltaDelayRaw-SP", + ] + self.devices["trigcorrs"] = _Trigger( + "SI-Glob:TI-Mags-Corrs", props2init=props + ) + # Event to start synchronous acquisition: + props = [ + "Mode-Sts", + "DelayRaw-RB", + "Mode-Sel", + "DelayRaw-SP", + "ExtTrig-Cmd", + ] + self.devices["evt"] = _Event("Study", props2init=props) + props = ["ContinuousEvt-Sts", "UpdateEvt-Cmd"] + self.devices["evg"] = _EVG(props2init=props) + + def _setup_orm(self): + """Get the orbit response matrix from configdb server.""" + name = self.params.orm_name + self._orm = _np.array(self.configdb.get_config_value(name)) + def _do_acbba(self): - pass + """.""" + # Initialize data + self.data["measure"] = dict() + self._setup_orm() + + # Start + tini = _datetime.datetime.fromtimestamp(_time.time()) + msg = f"Starting measurement at {tini.strftime('%Y-%m-%d %Hh%Mm%Ss')}" + self._log("\n" + msg) + + if self.devices["sofb"].autocorrsts: + msg = "SOFB feedback is enabled. Please desable it first." + self._log(msg) + return self.STATUS.Fail + + # Get initial timing state + self._log("Getting Timing state... ", end="") + timing_state = self.get_timing_state() + self._log("Done!") + + # Measure BPMs noise + if self.params.measure_bpms_noise: + msg = "Measuring BPMs noise:" + self._log(msg) + measnoise_ok, noise_data = self._do_measure_bpms_noise(tab=1) + self.data["bpms_noise"] = noise_data + if not measnoise_ok: + msg = "Problem measuring BPMs noise." + self._log(msg) + + # Set/Check if Correctors are in SlowRef mode + msg = "Setting Correctors OpMode to SlowRef... " + self._log(msg, end="") + corrs_opmode_ok = self._change_corrs_opmode( + "slowref", self.sofb_data.ch_names + self.sofb_data.cv_names + ) + if not corrs_opmode_ok: + msg = "Fail: Could not set OpMode to SlowRef. Exiting." + self._log(msg) + return self.STATUS.Fail + self._log("Done!") + + # Do AC-BBA for each BPM + nr_bpms = len(self._bpms2dobba) + msg = f"Running AC-BBA for {nr_bpms:03d} BPMs:" + self._log(msg) + stsok = True + for i, bpm in enumerate(self._bpms2dobba): + if self._stopevt.is_set(): + msg = "Stopped!" + self._log(msg, tab=1) + stsok = False + break + if not self.havebeam: + msg = "Beam was lost!" + self._log(msg, tab=1) + stsok = False + break + msg = f'Doing AC-BBA for BPM "{bpm}" ({i + 1:03d}/{nr_bpms:03d}):' + self._log(msg, tab=1) + sts, data_acq = self._do_acbba_single_bpm(bpm, tab=2) + self.data["measure"][bpm] = data_acq + if sts == self.STATUS.Fail: + stsok = False + msg = "Fail!" + self._log(msg, tab=1) + break + self._log("Done!", tab=1) + self._log(f"{'Done' if stsok else 'Fail'}!") + + # Restore Correctors opmode to SlowRef + msg = "Restoring Correctors OpMode to SlowRef... " + self._log(msg, end="") + corrs_opmode_ok = self._change_corrs_opmode( + "slowref", + self.sofb_data.ch_names + self.sofb_data.cv_names, + timeout=self.params.timeout_correctors, + ) + if not corrs_opmode_ok: + msg = "Fail: Could restore OpMode to SlowRef. Exiting." + self._log(msg) + stsok = False + else: + self._log("Done!") + + # Restore timing state + self._log("Restoring Timing state... ", end="") + self.set_timing_state(timing_state) + self._log("Done!") + + # Correct orbit before ending + if self.havebeam: + self._log("Correcting Orbit... ", end="") + self.correct_orbit() + self._log("Ok!") + + # Finish + tfin = _datetime.datetime.fromtimestamp(_time.time()) + dtime = str(tfin - tini).split(".")[0] + msg = f"Measurement finished! ET: {dtime}" + self._log(msg) + return self.STATUS.Success if stsok else self.STATUS.Fail + + def _do_acbba_single_bpm(self, bpmname, **kw): + tab = kw.pop("tab", 0) + + if bpmname not in self.data["bpmnames"]: + msg = f"Invalid BPM: {bpmname}." + self._log(msg, tab=tab) + return self.STATUS.Fail, None + + # correct orbit + if self.params.correct_orbit_each_step: + self._log("Correcting Orbit... ", end="", tab=tab) + self.correct_orbit() + self._log("Ok!") + + quadname = self.data["quadnames"][self.data["bpmnames"].index(bpmname)] + quadmode = self.params.quad_modulation_mode + + chname, cvname, *_ = self._get_correctors_for_bpm(bpmname, self._orm) + + stren_ini = self.get_quad_strength(quadname) + delta_kl = self.params.quad_delta_kl + + data = { + "quadname": quadname, + "chname": chname, + "cvname": cvname, + "pos": None, + "neg": None, + "quadmode": quadmode, + "quad_stren_ini": stren_ini, + "quad_delta_kl": delta_kl, + } + + if quadmode == self.params.QUAD_MODULATION_MODE.AC: + msg = "Quadrupole AC modulation mode is not implemented yet." + self._log(msg, tab=tab) + return self.STATUS.Fail, data + elif quadmode == self.params.QUAD_MODULATION_MODE.DC: + msg = "Setup: Quadrupole modulation mode: DC." + self._log(msg, tab=tab) + + # Set Quad KL = KL0 + dKL + msg = "Step: Positive quadrupole modulation." + self._log(msg, tab=tab) + msg = f'Changing quadrupole "{quadname}" strength... ' + self._log(msg, tab=tab + 1, end="") + sts = self.set_quad_strength( + quadname, stren_ini + delta_kl / 2, tab=tab + 1 + ) + if sts == self.STATUS.Fail: + self.set_quad_strength( + quadname, stren_ini, ignore_timeout=True + ) + return sts, data + self._log("Done!") + + sts, data_pos = self._acquire_data(chname, cvname, tab=tab + 1) + data["pos"] = data_pos + if sts == self.STATUS.Fail: + return sts, data + + # Set Quad KL = KL0 - dKL + msg = "Step: Negative quadrupole modulation." + self._log(msg, tab=tab) + msg = f'Changing quadrupole "{quadname}" strength... ' + self._log(msg, tab=tab + 1, end="") + sts = self.set_quad_strength( + quadname, stren_ini - delta_kl / 2, tab=tab + 1 + ) + if sts == self.STATUS.Fail: + self.set_quad_strength( + quadname, stren_ini, ignore_timeout=True + ) + return sts, data + self._log("Done!") + + sts, data_neg = self._acquire_data(chname, cvname, tab=tab + 1) + data["neg"] = data_neg + if sts == self.STATUS.Fail: + return sts, data + + # Restore Quad KL = KL0 + msg = "Step: Restoring quadrupole strength." + self._log(msg, tab=tab) + msg = f'Changing quadrupole "{quadname}" strength... ' + self._log(msg, tab=tab + 1, end="") + sts = self.set_quad_strength(quadname, stren_ini, tab=tab + 1) + if sts == self.STATUS.Fail: + self.set_quad_strength( + quadname, stren_ini, ignore_timeout=True + ) + return sts, data + self._log("Done!") + + else: + msg = "Invalid Quadrupole modulation mode. Skipping..." + self._log(msg, tab=tab) + return self.STATUS.Fail, data + + return self.STATUS.Success, data + + def _acquire_data(self, ch_name, cv_name, **kw): + """.""" + tab = kw.pop("tab", 0) + corr_names = [ch_name, cv_name] + corr_freqs = [self.params.ch_freq, self.params.cv_freq] + corr_kicks = [self.params.ch_kick, self.params.cv_kick] + excit_time = self.params.excit_time + tout_bpms = self.params.timeout_bpms + tout_corrs = self.params.timeout_correctors + + # Configure BPMs and Timing + t00 = _time.time() + msg = "Configuring BPMs and Timing... " + self._log(msg, tab=tab, end="") + nr_points = self._get_acq_nr_points() + ret = self._config_bpms(nr_points, rate=self.params.acq_rate) + sts = self._check_bpms_configok(ret) + if sts == self.STATUS.Fail: + return sts, None + self._config_timing( + self.params.corrs_delay, + chs=[[ch_name]], + cvs=[[cv_name]], + nr_points=nr_points, + ) + msg = f"Done! ET: {_time.time() - t00:.2f}s" + self._log(msg) + + # Configure correctors + t01 = _time.time() + msg = "Configuring correctors... " + self._log(msg, tab=tab, end="") + self._config_correctors(corr_names, corr_kicks, corr_freqs, excit_time) + msg = f"Done! ET: {_time.time() - t01:.2f}s" + self._log(msg) + + # Configure correctors opmode to Cycle + t02 = _time.time() + msg = f"Changing Corrs. ({ch_name}, {cv_name}) OpMode to Cycle... " + self._log(msg, tab=tab, end="") + if not self._change_corrs_opmode("cycle", corr_names, tab=tab): + msg = "Fail! Could not set OpMode to Cycle." + self._log(msg, tab=tab) + return self.STATUS.Fail, None + msg = f"Done! ET: {_time.time() - t02:.2f}s" + self._log(msg) + + # Trigger Event + t03 = _time.time() + msg = "Sending timing signal... " + self._log(msg, tab=tab, end="") + self.bpms.reset_mturn_initial_state() + self.devices["evt"].cmd_external_trigger() + msg = f"Done! ET: {_time.time() - t03:.2f}s" + self._log(msg) + + # Wait BPMs to update with new data + t04 = _time.time() + msg = "Waiting BPMs to update... " + self._log(msg, tab=tab, end="") + ret = self.bpms.wait_update_mturn(timeout=tout_bpms) + sts = self._check_if_bpms_updated(ret) + sts_str = "Done" if sts == self.STATUS.Success else "Fail" + msg = f"{sts_str}! ET: {_time.time() - t04:.2f}s" + self._log(msg, tab=tab) + + # Save data + t05 = _time.time() + msg = "Saving data... " + self._log(msg, end="", tab=tab) + data = self.get_general_data() + data.update(self.get_bpms_data()) + msg = f"Done! ET: {_time.time() - t05:.2f}s" + self._log(msg) + + # Restore Correctors opmode to SlowRef + t06 = _time.time() + msg = f"Restoring Corrs. ({ch_name}, {cv_name}) OpMode to SlowRef... " + self._log(msg, tab=tab, end="") + if not self._wait_cycle_to_finish(corr_names, timeout=tout_corrs): + msg = "Fail! Cycle still not finished." + self._log(msg) + return self.STATUS.Fail, data + if not self._change_corrs_opmode("slowref", corr_names, tab=tab): + msg = "Fail! Could restore OpMode to SlowRef." + self._log(msg, tab=tab) + return self.STATUS.Fail, data + msg = f"Done! ET: {_time.time() - t06:.2f}s" + self._log(msg) + + return self.STATUS.Success, data + + def _check_bpms_configok(self, ret): + bpmnames = self.data["bpmnames"] + if ret < 0: + idx = -int(ret) - 1 + # msg = f"BPM {idx:d} did not finish last acquisition." + bpmname = bpmnames[idx] + msg = f'"{bpmname}" did not finish last acquisition.' + self._log(msg) + elif ret > 0: + idx = int(ret) - 1 + # msg = f"BPM {idx:d} is not ready for acquisition." + bpmname = bpmnames[idx] + msg = f'"{bpmname}" is not ready for acquisition.' + self._log(msg) + return self.STATUS.Fail if ret else self.STATUS.Success + + def _check_if_bpms_updated(self, ret): + """.""" + if ret != 0: + if ret > 0: + tag = self.bpms.bpm_names[int(ret) - 1] + pos = self.bpms.mturn_signals2acq[int((ret % 1) * 10) - 1] + msg = f'Problem: BPM "{tag}" did not update, signal {pos}.' + elif ret == -1: + msg = "Problem: Initial timestamps were not defined." + elif ret == -2: + msg = "Problem: signals size changed." + self._log(msg) + return self.STATUS.Fail + return self.STATUS.Success + + def _get_correctors_for_bpm(self, bpmname, orm=None): + """Choose a CH and a CV that most affect the target BPM.""" + if orm is None: + orm = self._orm + if orm is None: + raise RuntimeError("Orbit Response Matrix not loaded.") + + sofb = self.sofb_data + bpmnames = self.data["bpmnames"] + if bpmname not in bpmnames: + raise ValueError("Invalid BPM! Check ACBBAParams.BPMNAMES") + bpm_idx = bpmnames.index(bpmname) + + orm_xx = orm[: sofb.nr_bpms, : sofb.nr_ch] + orm_yy = orm[sofb.nr_bpms :, sofb.nr_ch : sofb.nr_chcv] + + ch_idx = int(_np.argmax(_np.abs(orm_xx[bpm_idx, :]))) + cv_idx = int(_np.argmax(_np.abs(orm_yy[bpm_idx, :]))) + + ch_name = sofb.ch_names[ch_idx] + cv_name = sofb.cv_names[cv_idx] + return ch_name, cv_name, ch_idx, cv_idx + sofb.nr_ch + + def _do_measure_bpms_noise(self, **kw): + tab = kw.pop("tab", 0) + tini = _datetime.datetime.fromtimestamp(_time.time()) + + stsok = True + + t00 = _time.time() + msg = "Configuring BPMs and Timing... " + self._log(msg, tab=tab, end="") + nr_points = self._get_acq_nr_points() + ret = self._config_bpms(nr_points, rate=self.params.acq_rate) + sts = self._check_bpms_configok(ret) + self._config_timing() + if sts == self.STATUS.Success: + msg = f"Done! ET: {_time.time() - t00:.2f}s" + self._log(msg) + else: + stsok = False + + t01 = _time.time() + msg = "Sending Trigger signal... " + self._log(msg, tab=tab, end="") + self.bpms.reset_mturn_initial_state() + self.devices["evt"].cmd_external_trigger() + msg = f"Done! ET: {_time.time() - t01:.2f}s" + self._log(msg) + + t02 = _time.time() + msg = "Waiting for BPMs to update... " + self._log(msg, tab=tab, end="") + ret = self.bpms.wait_update_mturn(timeout=self.params.timeout_bpms) + sts = self._check_if_bpms_updated(ret) + if sts == self.STATUS.Success: + sts_str = "Done" + else: + stsok = False + sts_str = "Fail" + self._log(f"{sts_str}! ET: {_time.time() - t02:.2f}s") + + _time.sleep(0.5) + data = self.get_general_data() + data.update(self.get_bpms_data()) + data["ch_freq"] = self.params.ch_freq + data["cv_freq"] = self.params.cv_freq + + tfin = _datetime.datetime.fromtimestamp(_time.time()) + dtime = str(tfin - tini).split(".")[0] + msg = f"{'Done' if stsok else 'Fail'}! ET: {dtime}" + self._log(msg) + return stsok, data + + def _get_acq_nr_points(self): + freq = self.devices["rfgen"].frequency + rate = self.params.acq_rate + n_pts = self.params.excit_time + n_pts += self.params.corrs_delay * 2 + n_pts *= self.bpms.get_sampling_frequency(freq, acq_rate=rate) + n_pts = int(_np.ceil(n_pts)) + return n_pts + + def get_bpms_data(self): + """Get all BPM related data relevant for the measurements. + + Returns: + dict: BPMs data. + + """ + orbx, orby = self.bpms.get_mturn_signals() + bpm0 = self.bpms.devices[0] + rf_freq = self.devices["rfgen"].frequency + + data = dict() + data["orbx"] = orbx + data["orby"] = orby + data["rf_frequency"] = rf_freq + data["acq_rate"] = bpm0.acq_channel_str + data["sampling_frequency"] = self.bpms.get_sampling_frequency(rf_freq) + data["nrsamples_pre"] = bpm0.acq_nrsamples_pre + data["nrsamples_post"] = bpm0.acq_nrsamples_post + data["trig_delay_raw"] = self.devices["trigbpms"].delay_raw + data["switching_mode"] = bpm0.switching_mode_str + data["switching_frequency"] = self.bpms.get_switching_frequency( + rf_freq + ) + return data + + def get_general_data(self): + """Get general purpose data. + + Returns: + dict: general purpose data. + + """ + data = dict() + data["timestamp"] = _time.time() + data["stored_current"] = self.devices["currinfo"].current + data["tunex"] = self.devices["tune"].tunex + data["tuney"] = self.devices["tune"].tuney + return data + + def _config_bpms(self, nr_points, rate=None): + if rate is None: + rate = self.params.acq_rate + return self.bpms.config_mturn_acquisition( + acq_rate=rate, + nr_points_before=0, + nr_points_after=nr_points, + repeat=False, + external=True, + ) + + def get_timing_state(self): + """Get the timing state.""" + state = dict() + for opt in DoACBBA.TIMING_STATE_OPTIONS: + devname, *state_opt = opt.split("_") + state_opt = "_".join(state_opt) + device = self.devices.get(devname, None) + if device is not None: + state[opt] = 0 + state[opt] = getattr(device, state_opt) + return state + + def set_timing_state(self, state): + """Set the timing state.""" + for opt in DoACBBA.TIMING_STATE_OPTIONS: + if opt not in state.keys(): + continue + devname, *state_opt = opt.split("_") + device = self.devices.get(devname, None) + state_opt = "_".join(state_opt) + if device is not None: + setattr(device, state_opt, state[opt]) + continue + _time.sleep(0.1) + self.devices["evg"].cmd_update_events() + + def _config_timing(self, cm_dly=0, chs=None, cvs=None, nr_points=None): + """Configure timing. + + Args: + cm_dly (float, optional): General Delay of correctors; + chs (list, optional): List of lists of CH names. Each list + represent a different run in the same BPM acquisition. + Defaults to None. + cvs (list, optional): List of lists of CV names. Each list + represent a different run in the same BPM acquisition. + Defaults to None. + nr_points (int, optional): number of points of each run. + Defaults to None. + + Raises: + ValueError: Impossible trigger configuration. + ValueError: Invalid trigger name. + + """ + state = dict() + state["trigbpms_source"] = "Study" + state["trigbpms_nr_pulses"] = 1 + state["trigbpms_delay_raw"] = 0.0 + + state["evt_mode"] = "External" + state["evt_delay_raw"] = 0 + + state["trigcorrs_source"] = "Study" + state["trigcorrs_nr_pulses"] = 1 + + rf_freq = self.devices["rfgen"].frequency + ftim = rf_freq / 4 # timing base frequency + dly = int(cm_dly * ftim) + if chs is None or cvs is None or nr_points is None: + state["trigcorrs_delay_raw"] = dly + self.set_timing_state(state) + return + + state["trigcorrs_delay_raw"] = 0 + nr_runs = len(chs) + # Calculate delta_delay for correctors to be as close as possible to a + # multiple of the the sampling period to ensure repeatability of + # experiment along runs excited during single acquisition: + fsamp = self.bpms.get_sampling_frequency(rf_freq, self.params.acq_rate) + runs_delta_dly = _np.arange(nr_runs, dtype=float) + runs_delta_dly *= nr_points / fsamp + runs_delta_dlyr = _np.round(runs_delta_dly * ftim) + + # get low level trigger names to be configured in each run of the + # acquisition: + ll_trigs = [] + for ch, cv in zip(chs, cvs): # noqa: B905 + llt = set() + for c in ch + cv: + trig = self._LLTime.get_trigger_name(c + ":BCKPLN") + llt.add(trig) + ll_trigs.append(llt) + + # check if correctors controlled by the same trigger are requested to + # be triggered in different times during the same acquisition + if len(_red(_opr.or_, ll_trigs)) != _red(_opr.add, map(len, ll_trigs)): + raise ValueError("Impossible trigger configuration requested.") + + trigcorr = self.devices["trigcorrs"] + delta_delay_raw = _np.zeros(trigcorr.delta_delay_raw.size) + low_level = trigcorr.low_level_triggers + for llts, ddlyr in zip(ll_trigs, runs_delta_dlyr): # noqa: B905 + # Find all low level triggers of this sector and set their delay: + for llt in llts: + if llt not in low_level: + raise ValueError(f"Trigger {llt:s} is not valid.") + delta_delay_raw[low_level.index(llt)] = ddlyr + dly + state["trigcorr_delta_delay_raw"] = delta_delay_raw + self.set_timing_state(state) + + def _log(self, msg, *args, **kwargs): + """.""" + if "tab" in kwargs: + tab = kwargs.pop("tab") + msg = " " * tab + msg + if self.verbose: + print(msg, *args, **kwargs) + self.data["log"].append((_time.time(), msg)) + + @property + def havebeam(self): + """.""" + haveb = self.devices["currinfo"] + return haveb.connected and haveb.storedbeam + + def _config_correctors(self, corr_names, kicks, freqs, excit_time): + """.""" + for i, cmn in enumerate(corr_names): + cmo = self.devices[cmn] + conv = self.devices[cmn + ":StrengthConv"].conv_strength_2_current + cmo.cycle_type = cmo.CYCLETYPE.Sine + cmo.cycle_freq = freqs[i] + cmo.cycle_ampl = conv(kicks[i]) + cmo.cycle_offset = cmo.currentref_mon + cmo.cycle_theta_begin = 0 + cmo.cycle_theta_end = 0 + cmo.cycle_num_cycles = int(excit_time * freqs[i]) + # NOTE: There is a bug in the firmware of the power supplies + # (apparently comparison >= should be replaced by > in line 353 of + # the file siggen.c of the repository C28) that makes the endpoint + # of the cycle not be equal to the starting point. So we need to + # add a very small phase at the ending of the senoid to compensate + # for this bug. The code bellow adds a phase compatible with a + # small fraction (0.1) of the phase advance between two points of + # the signal at the end of the cycling. + fsamp = cmo["ParamPWMFreq-Cte"] + params = cmo.cycle_aux_param + params[1] = freqs[i] / fsamp * 360 + params[1] *= 0.1 + cmo.cycle_aux_param = params + + def _change_corrs_opmode(self, mode, corr_names=None, timeout=None, **kw): + """.""" + tab = kw.pop("tab", 0) + if timeout is None: + timeout = self.params.timeout_correctors + + opm_sel = _PowerSupply.OPMODE_SEL + opm_sts = _PowerSupply.OPMODE_STS + mode_sel = opm_sel.Cycle if mode == "cycle" else opm_sel.SlowRef + mode_sts = opm_sts.Cycle if mode == "cycle" else opm_sts.SlowRef + + if corr_names is None: + corr_names = self.sofb_data.ch_names + self.sofb_data.cv_names + + for cmn in corr_names: + cmo = self.devices[cmn] + cmo.opmode = mode_sel + + for cmn in corr_names: + dt_ = _time.time() + cmo = self.devices[cmn] + if not cmo.wait("OpMode-Sts", mode_sts, timeout=timeout): + msg = "\nERR:" + cmn + " did not change to " + mode + self._log(msg, tab=tab) + return False + dt_ -= _time.time() + timeout = max(timeout + dt_, 0) + cmo.current = cmo.current + return True + + def _wait_cycle_to_finish(self, corr_names=None, timeout=None): + """.""" + if timeout is None: + timeout = self.params.timeout_correctors + if corr_names is None: + corr_names = self.sofb_data.ch_names + self.sofb_data.cv_names + + for cmn in corr_names: + cmo = self.devices[cmn] + if not cmo.wait_cycle_to_finish(timeout=timeout): + return False + return True + + def get_quad_strength(self, quadname): + """.""" + if quadname not in self.data["quadnames"]: + raise ValueError(f"Invalid quadrupole: {quadname}.") + quad = self.devices[quadname] + return float(quad.strength) + + def set_quad_strength( + self, quadname, strength, ignore_timeout=False, **kw + ): + """.""" + tab = kw.pop("tab", 0) + if quadname not in self.data["quadnames"]: + raise ValueError(f"Invalid quadrupole: {quadname}.") + quad = self.devices[quadname] + quad.strength = float(strength) + + if ignore_timeout: + return self.STATUS.Success + + if not quad.wait_float( + "KLRef-Mon", + strength, + rel_tol=0.0, + abs_tol=0.05 * self.params.quad_delta_kl, + timeout=self.params.wait_quadrupole, + ): + msg = f'Could not change quadrupole "{quadname}" strength!' + msg += f"\nTryed to set KL = {strength}, " + msg += f"current KL = {quad.strength} [1/m]." + self._log(msg, tab=tab) + return self.STATUS.Fail + return self.STATUS.Success + + def correct_orbit(self): + """.""" + if not self.havebeam: + return + sofb = self.devices["sofb"] + sofb.correct_orbit_manually( + nr_iters=self.params.sofb_maxcorriter, + residue=self.params.sofb_maxorberr, + ) + + def _process_data_single_bpm(self, bpmname): + if bpmname not in self.data["measure"]: + return + + meas = self.data["measure"][bpmname] + data_pos = meas["pos"] + data_neg = meas["neg"] + quadmode = meas["quadmode"] + + if data_pos is None or data_neg is None: + return + + bpmnames = self.data["bpmnames"] + bpmidx = bpmnames.index(bpmname) + + fs = float(data_pos["sampling_frequency"]) + dt = 1.0 / fs + fh = float(data_pos.get("ch_freq", self.params.ch_freq)) + fv = float(data_pos.get("cv_freq", self.params.cv_freq)) + + freqs = _np.array([fh, fv], dtype=float) + + orbx_pos = _np.asarray(data_pos["orbx"], dtype=float) + orby_pos = _np.asarray(data_pos["orby"], dtype=float) + orbx_neg = _np.asarray(data_neg["orbx"], dtype=float) + orby_neg = _np.asarray(data_neg["orby"], dtype=float) + + npts = min( + orbx_pos.shape[0], + orbx_neg.shape[0], + orby_pos.shape[0], + orby_neg.shape[0], + ) + orbx_pos = orbx_pos[:npts] + orby_pos = orby_pos[:npts] + orbx_neg = orbx_neg[:npts] + orby_neg = orby_neg[:npts] + + tim = _np.arange(npts) * dt + + nr_cycles = _np.array( + [ + int(round(self.params.excit_time * fh)), + int(round(self.params.excit_time * fv)), + ], + dtype=int, + ) + + mat = self.fitting_matrix(tim, freqs, num_cycles=nr_cycles) + u, s, vt = _np.linalg.svd(mat, full_matrices=False) + pinv = vt.T / s @ u.T + + dcx_pos = _np.mean(orbx_pos, axis=0) + dcy_pos = _np.mean(orby_pos, axis=0) + dcx_neg = _np.mean(orbx_neg, axis=0) + dcy_neg = _np.mean(orby_neg, axis=0) + + cosx_pos, sinx_pos, _ = self.fit_fourier_components( + orbx_pos - dcx_pos, freqs, dt, pinv=pinv + ) + cosy_pos, siny_pos, _ = self.fit_fourier_components( + orby_pos - dcy_pos, freqs, dt, pinv=pinv + ) + cosx_neg, sinx_neg, _ = self.fit_fourier_components( + orbx_neg - dcx_neg, freqs, dt, pinv=pinv + ) + cosy_neg, siny_neg, _ = self.fit_fourier_components( + orby_neg - dcy_neg, freqs, dt, pinv=pinv + ) + + amp_x_pos, ph_x_pos = self.fit_calc_amp_and_phase(cosx_pos, sinx_pos) + amp_y_pos, ph_y_pos = self.fit_calc_amp_and_phase(cosy_pos, siny_pos) + amp_x_neg, ph_x_neg = self.fit_calc_amp_and_phase(cosx_neg, sinx_neg) + amp_y_neg, ph_y_neg = self.fit_calc_amp_and_phase(cosy_neg, siny_neg) + + phref_h_pos = ph_x_pos[bpmidx] + phref_v_pos = ph_y_pos[bpmidx] + phref_h_neg = ph_x_neg[bpmidx] + phref_v_neg = ph_y_neg[bpmidx] + + if quadmode == self.params.QUAD_MODULATION_MODE.DC: + sgn_xh_pos = _np.sign(_np.cos(ph_x_pos[0] - phref_h_pos)) + sgn_yh_pos = _np.sign(_np.cos(ph_y_pos[0] - phref_h_pos)) + sgn_xv_pos = _np.sign(_np.cos(ph_x_pos[1] - phref_v_pos)) + sgn_yv_pos = _np.sign(_np.cos(ph_y_pos[1] - phref_v_pos)) + sgn_xh_neg = _np.sign(_np.cos(ph_x_neg[0] - phref_h_neg)) + sgn_yh_neg = _np.sign(_np.cos(ph_y_neg[0] - phref_h_neg)) + sgn_xv_neg = _np.sign(_np.cos(ph_x_neg[1] - phref_v_neg)) + sgn_yv_neg = _np.sign(_np.cos(ph_y_neg[1] - phref_v_neg)) + + sgn_xh_pos[sgn_xh_pos == 0] = 1.0 + sgn_yh_pos[sgn_yh_pos == 0] = 1.0 + sgn_xv_pos[sgn_xv_pos == 0] = 1.0 + sgn_yv_pos[sgn_yv_pos == 0] = 1.0 + sgn_xh_neg[sgn_xh_neg == 0] = 1.0 + sgn_yh_neg[sgn_yh_neg == 0] = 1.0 + sgn_xv_neg[sgn_xv_neg == 0] = 1.0 + sgn_yv_neg[sgn_yv_neg == 0] = 1.0 + + sxh_pos = amp_x_pos[0] * sgn_xh_pos + syh_pos = amp_y_pos[0] * sgn_yh_pos + sxv_pos = amp_x_pos[1] * sgn_xv_pos + syv_pos = amp_y_pos[1] * sgn_yv_pos + sxh_neg = amp_x_neg[0] * sgn_xh_neg + syh_neg = amp_y_neg[0] * sgn_yh_neg + sxv_neg = amp_x_neg[1] * sgn_xv_neg + syv_neg = amp_y_neg[1] * sgn_yv_neg + + d_x = dcx_pos - dcx_neg + d_y = dcy_pos - dcy_neg + d_xh = sxh_pos - sxh_neg + d_yh = syh_pos - syh_neg + d_xv = sxv_pos - sxv_neg + d_yv = syv_pos - syv_neg + elif quadmode == self.params.QUAD_MODULATION_MODE.AC: + msg = "AC quadrupole modulation mode not implemented yet." + raise NotImplementedError(msg) + else: + quadmode_str = self.params.QUAD_MODULATION_MODE._field[quadmode] + msg = f"Invalid quadrupole modulation mode: {quadmode_str}" + raise ValueError(msg) + + y_h = d_x * d_yv - d_xv * d_y + y_v = d_xh * d_y - d_x * d_yh + x_h = d_xh * d_yv - d_xv * d_yh + x_v = d_xh * d_yv - d_xv * d_yh + + m_h = _np.polyfit(x_h, y_h, 1)[0] + m_v = _np.polyfit(x_v, y_v, 1)[0] + + x0_pos = ( + dcx_pos[bpmidx] + sxh_pos[bpmidx] * m_h + sxv_pos[bpmidx] * m_v + ) + y0_pos = ( + dcy_pos[bpmidx] + syv_pos[bpmidx] * m_v + syh_pos[bpmidx] * m_h + ) + + x0_neg = ( + dcx_neg[bpmidx] + sxh_neg[bpmidx] * m_h + sxv_neg[bpmidx] * m_v + ) + y0_neg = ( + dcy_neg[bpmidx] + syv_neg[bpmidx] * m_v + syh_neg[bpmidx] * m_h + ) + + x0 = 0.5 * (x0_pos + x0_neg) + y0 = 0.5 * (y0_pos + y0_neg) + + self.analysis[bpmname] = dict( + x0=x0, + y0=y0, + m_h=m_h, + m_v=m_v, + d_x=d_x, + d_y=d_y, + d_xh=d_xh, + d_yh=d_yh, + d_xv=d_xv, + d_yv=d_yv, + amp_x_pos=amp_x_pos, + amp_y_pos=amp_y_pos, + amp_x_neg=amp_x_neg, + amp_y_neg=amp_y_neg, + ph_x_pos=ph_x_pos, + ph_y_pos=ph_y_pos, + ph_x_neg=ph_x_neg, + ph_y_neg=ph_y_neg, + ) + + @staticmethod + def fitting_matrix(tim, freqs, num_cycles=None, idx_ini=None): + """Create the matrix used for fitting of fourier components. + + The ordering of the matrix is the following: + mat[i, 2*j] = cos(2*pi*freqs[j]*tim[i]) + mat[i, 2*j+1] = sin(2*pi*freqs[j]*tim[i]) + + Args: + tim (numpy.ndarray): array with times + freqs (numpy.ndarray): array with frequencies to fit. + num_cycles (numpy.ndarray, optional): number of cycles of each + frequency. If not provided, all data range will be considered. + idx_ini (int|list|tuple|numpy.ndarray, optional): starting index + for fitting. If it is an iterable, must have the same size as + freqs. Defaults to None, which means the first index will be + the starting point. + + Returns: + numpy.ndarray: fitting matrix (len(tim), 2*len(freqs)) + + """ + if idx_ini is None: + idx_ini = _np.zeros(freqs.shape, dtype=int) + elif not isinstance(idx_ini, (list, tuple, _np.ndarray)): + idx_ini = _np.full(freqs.shape, idx_ini, dtype=int) + + mat = _np.zeros((tim.size, 2 * freqs.size)) + mat2 = mat.copy() + arg = 2 * _np.pi * freqs[None, :] * tim[:, None] + cos = _np.cos(arg) + sin = _np.sin(arg) + idx_ini = _np.vstack([idx_ini, idx_ini]).T.ravel() + + if num_cycles is not None: + cond = arg > 2 * _np.pi * num_cycles[None, :] + cos[cond] = 0 + sin[cond] = 0 + mat[:, ::2] = cos + mat[:, 1::2] = sin + + for i, idx in enumerate(idx_ini): + if not idx: + mat2[:, i] = mat[:, i] + else: + mat2[idx:, i] = mat[:-idx, i] + return mat2 + + @classmethod + def fit_fourier_components( + cls, data, freqs, dtim, num_cycles=None, idx_ini=None, pinv=None + ): + """Fit Fourier components in signal for the given frequencies. + + Args: + data (numpy.ndarray, NxM): signal to be fitted consisting of M + columns of data. + freqs (numpy.ndarray, K): K frequencies to fit Fourier components. + dtim (numpy.ndarray, N): time vector for data columns. + num_cycles (num.ndarray, K, optional): number of cycles of each + frequency. If not provided, all data range will be considered. + Not used if pinv is not None. + idx_ini (int|list|tuple|numpy.ndarray, optional): starting index + for fitting. If it is an iterable, must have the same size as + freqs. Defaults to None, which means the first index will be + the starting point. Not used if pinv is not None. + pinv (numpy.ndarray, Mx2K, optional): if provided must be the + pseudo inverve of the fitting matrix. Defaults to None, which + means the fitting matrix and its pseudo-inverse will be + calculated. + + Returns: + cos (numpy.ndarray, KxM): Fourier cosine coefficients. + sin (numpy.ndarray, KxM): Fourier sine coefficients. + pinv (numpy.ndarray, Mx2K): pseudo-inverse of fitting matrix. + + """ + if pinv is None: + tim = _np.arange(data.shape[0]) * dtim + mat = cls.fitting_matrix(tim, freqs, num_cycles, idx_ini) + u, s, vt = _np.linalg.svd(mat, full_matrices=False) + pinv = vt.T / s @ u.T + coeffs = pinv @ data + else: + siz = min(pinv.shape[1], data.shape[0]) + coeffs = pinv[:, :siz] @ data[:siz] + # coeffs, *_ = _np.linalg.lstsq(mat, data, rcond=None) + cos = coeffs[::2] + sin = coeffs[1::2] + return cos, sin, pinv + + @staticmethod + def fit_calc_amp_and_phase(cos, sin): + """.""" + amps = _np.sqrt(cos**2 + sin**2) + phases = _np.arctan2(cos, sin) + return amps, phases From 88ac0640d5188e535d31c001e13b296084a8f4d0 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:22:41 -0300 Subject: [PATCH 52/61] MNT (COMISS.ACBBA): Add function to check if delta_kl is valid --- apsuite/commisslib/measure_bba_ac.py | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 03f873d2b..203b90028 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -937,6 +937,46 @@ def set_quad_strength( return self.STATUS.Fail return self.STATUS.Success + def get_quad_strength_limits(self, quadname, margin=0.0005): + """.""" + if quadname not in self.data["quadnames"]: + raise ValueError(f"Invalid quadrupole: {quadname}.") + quad = self.devices[quadname] + pv = quad.pv_object("KL-SP") + upp = pv.upper_disp_limit + low = pv.lower_disp_limit + # Limits are interchanged in some quads: + lolim = min(upp, low) + margin + hilim = max(upp, low) - margin + return _np.array([lolim, hilim], dtype=float) + + def check_isvalid_delta_kl( + self, quadname, init_strength=None, delta_kl=None + ): + """.""" + if delta_kl is None: + delta_kl = self.params.quad_delta_kl + dkl = delta_kl + + lolim, hilim = self.get_quad_strength_limits(quadname) + + if init_strength is None: + init_strength = self.get_quad_strength(quadname) + kl = init_strength + + low = min(kl + dkl / 2, kl - dkl / 2) + upp = max(kl + dkl / 2, kl - dkl / 2) + + if upp > hilim or low < lolim: + max_delta_kl = min(hilim - kl, kl - lolim) + msg = f"WARN: {quadname} KL = {kl:.2g}, dKL = {abs(dkl):.2g}. " + msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). " + msg += f"Max. dKL = {max_delta_kl * 2:.2g}." + self._log_print(msg) + return False, max_delta_kl + + return True, delta_kl + def correct_orbit(self): """.""" if not self.havebeam: From 6cc424d6e76a0910700af3b714c57ac0b7c3c314 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:24:15 -0300 Subject: [PATCH 53/61] MNT (COMISS.ACBBA): Remove properties used in tests --- apsuite/commisslib/measure_bba_ac.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 203b90028..ff01730f0 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -104,7 +104,7 @@ class DoACBBA(_BaseClass): "evt_delay_raw", ) - def __init__(self, isonline=True, func=None, LLTime=_LLTime): + def __init__(self, isonline=True): """.""" self.params = ACBBAParams() super().__init__( @@ -127,11 +127,6 @@ def __init__(self, isonline=True, func=None, LLTime=_LLTime): self.sofb_data = _SOFBFactory.create("SI") self.configdb = _ConfigDBClient(config_type="si_bbadata") self._create_devices() - else: - if func is not None: - func(self) - if LLTime is not None: - self._LLTime = LLTime self.data["log"] = [(_time.time(), "Started.")] From 0be46442a0ba8229ca7260d280afb8d398779fc8 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:32:24 -0300 Subject: [PATCH 54/61] MNT (COMISS.ACBBA): Improve __str__ of ACBBAParams --- apsuite/commisslib/measure_bba_ac.py | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index ff01730f0..1053c2e07 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -83,8 +83,34 @@ def __init__(self): def __str__(self): """.""" - st = "BBA-AC measurement parameters." - return st + ftmp = '{0:24s} = {1:9.3f} {2:s}\n'.format + dtmp = '{0:24s} = {1:9d} {2:s}\n'.format + stmp = '{0:24s} = {1:9s} {2:s}\n'.format + + stg = '' + stg += "AC-BBA Parameters:\n" + stg += ftmp("timeout_bpms", self.timeout_bpms, "[s]") + stg += ftmp("timeout_correctors", self.timeout_correctors, "[s]") + stg += stmp("quad_modulation_mode", self.quad_modulation_mode, "") + stg += ftmp("quad_delta_kl", self.quad_delta_kl, "[1/m]") + stg += ftmp("wait_quadrupole", self.wait_quadrupole, "[s]") + stg += ftmp("cv_freq", self.cv_freq, "[Hz]") + stg += ftmp("ch_freq", self.ch_freq, "[Hz]") + stg += ftmp("qn_freq", self.qn_freq, "[Hz]") + stg += ftmp("qs_freq", self.qs_freq, "[Hz]") + stg += ftmp("excit_time", self.excit_time, "[s]") + stg += ftmp("corrs_delay", self.corrs_delay, "[s]") + stg += ftmp("ch_kick", self.ch_kick, "[urad]") + stg += ftmp("cv_kick", self.cv_kick, "[urad]") + stg += ftmp("dorbx", self.dorbx, "[um]") + stg += ftmp("dorby", self.dorby, "[um]") + stg += dtmp("use_qs_excitation", int(self.use_qs_excitation), "") + stg += dtmp("use_qn_excitation", int(self.use_qn_excitation), "") + stg += dtmp("measure_bpms_noise", int(self.measure_bpms_noise), "") + stg += stmp("acq_rate", self.acq_rate, "") + stg += stmp("orm_name", self.orm_name, "") + + return stg class DoACBBA(_BaseClass): From b4a441b0bcb19bdbd08b93e0031bbde12d30abe2 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:36:59 -0300 Subject: [PATCH 55/61] BUGFIX (ACBBA): Params __str__ formater --- apsuite/commisslib/measure_bba_ac.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 1053c2e07..6e6a962e3 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -83,15 +83,19 @@ def __init__(self): def __str__(self): """.""" - ftmp = '{0:24s} = {1:9.3f} {2:s}\n'.format - dtmp = '{0:24s} = {1:9d} {2:s}\n'.format - stmp = '{0:24s} = {1:9s} {2:s}\n'.format + ftmp = "{0:24s} = {1:9.3f} {2:s}\n".format + dtmp = "{0:24s} = {1:9d} {2:s}\n".format + stmp = "{0:24s} = {1:9s} {2:s}\n".format - stg = '' + stg = "" stg += "AC-BBA Parameters:\n" stg += ftmp("timeout_bpms", self.timeout_bpms, "[s]") stg += ftmp("timeout_correctors", self.timeout_correctors, "[s]") - stg += stmp("quad_modulation_mode", self.quad_modulation_mode, "") + stg += stmp( + "quad_modulation_mode", + self.QUAD_MODULATION_MODE._fields[self.quad_modulation_mode], + "", + ) stg += ftmp("quad_delta_kl", self.quad_delta_kl, "[1/m]") stg += ftmp("wait_quadrupole", self.wait_quadrupole, "[s]") stg += ftmp("cv_freq", self.cv_freq, "[Hz]") From ac4b7a8919f465ac8517e988c1d621583bb6cbb4 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:47:15 -0300 Subject: [PATCH 56/61] MNT, FIX (ACBBA): Fix configdb config_type --- apsuite/commisslib/measure_bba_ac.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 6e6a962e3..e1e3b8743 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -150,12 +150,12 @@ def __init__(self, isonline=True): self._bpms2dobba = self.data["bpmnames"] self.sofb_data = None - self.configdb = None + self.configdb_orm = None self._orm = None if self.isonline: self.sofb_data = _SOFBFactory.create("SI") - self.configdb = _ConfigDBClient(config_type="si_bbadata") + self.configdb_orm = _ConfigDBClient(config_type="si_orbcorr_respm") self._create_devices() self.data["log"] = [(_time.time(), "Started.")] @@ -279,7 +279,7 @@ def _create_devices(self): def _setup_orm(self): """Get the orbit response matrix from configdb server.""" name = self.params.orm_name - self._orm = _np.array(self.configdb.get_config_value(name)) + self._orm = _np.array(self.configdb_orm.get_config_value(name)) def _do_acbba(self): """.""" From cccb658e623884ae003431f8ee5f387228a31975 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:52:25 -0300 Subject: [PATCH 57/61] MNT (COMISS.ACBBA): Remove bpmnames/quadnames limitation --- apsuite/commisslib/measure_bba_ac.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index e1e3b8743..5329b6c51 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -42,8 +42,8 @@ class ACBBAParams(_ParamsBaseClass): """Parameters for AC-BBA.""" - BPMNAMES = _BBAParams.BPMNAMES[:2] - QUADNAMES = _BBAParams.QUADNAMES[:2] + BPMNAMES = _BBAParams.BPMNAMES + QUADNAMES = _BBAParams.QUADNAMES QUAD_MODULATION_MODE = _get_namedtuple("QuadModulationMode", ["AC", "DC"]) def __init__(self): @@ -997,7 +997,7 @@ def check_isvalid_delta_kl( msg = f"WARN: {quadname} KL = {kl:.2g}, dKL = {abs(dkl):.2g}. " msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). " msg += f"Max. dKL = {max_delta_kl * 2:.2g}." - self._log_print(msg) + self._log(msg) return False, max_delta_kl return True, delta_kl From 8c388bd352d3c81ed41b40077417cde8b842fd20 Mon Sep 17 00:00:00 2001 From: Vitor Date: Mon, 3 Aug 2026 08:55:48 -0300 Subject: [PATCH 58/61] ENH, MNT (ACBBA): Remove delta_kl "half" scale factor --- apsuite/commisslib/measure_bba_ac.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 5329b6c51..2646da044 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -53,7 +53,7 @@ def __init__(self): self.timeout_correctors = 20 # [s] self.quad_modulation_mode = ACBBAParams.QUAD_MODULATION_MODE.DC - self.quad_delta_kl = 0.02 # [1/m] + self.quad_delta_kl = 0.01 # [1/m] self.wait_quadrupole = 0.3 # [s] self.cv_freq = 17.0 # [Hz] @@ -432,7 +432,7 @@ def _do_acbba_single_bpm(self, bpmname, **kw): msg = f'Changing quadrupole "{quadname}" strength... ' self._log(msg, tab=tab + 1, end="") sts = self.set_quad_strength( - quadname, stren_ini + delta_kl / 2, tab=tab + 1 + quadname, stren_ini + delta_kl, tab=tab + 1 ) if sts == self.STATUS.Fail: self.set_quad_strength( @@ -452,7 +452,7 @@ def _do_acbba_single_bpm(self, bpmname, **kw): msg = f'Changing quadrupole "{quadname}" strength... ' self._log(msg, tab=tab + 1, end="") sts = self.set_quad_strength( - quadname, stren_ini - delta_kl / 2, tab=tab + 1 + quadname, stren_ini - delta_kl, tab=tab + 1 ) if sts == self.STATUS.Fail: self.set_quad_strength( @@ -989,8 +989,8 @@ def check_isvalid_delta_kl( init_strength = self.get_quad_strength(quadname) kl = init_strength - low = min(kl + dkl / 2, kl - dkl / 2) - upp = max(kl + dkl / 2, kl - dkl / 2) + low = min(kl + dkl, kl - dkl) + upp = max(kl + dkl, kl - dkl) if upp > hilim or low < lolim: max_delta_kl = min(hilim - kl, kl - lolim) From 2e9aefe54af6571e9acf8038a2491770b7ab6965 Mon Sep 17 00:00:00 2001 From: Vitor Date: Thu, 6 Aug 2026 10:09:48 -0300 Subject: [PATCH 59/61] MNT (ACBBA): Changes from machine-study (03/aug/2026) --- apsuite/commisslib/measure_bba_ac.py | 46 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_bba_ac.py b/apsuite/commisslib/measure_bba_ac.py index 2646da044..61b310d7a 100644 --- a/apsuite/commisslib/measure_bba_ac.py +++ b/apsuite/commisslib/measure_bba_ac.py @@ -54,14 +54,14 @@ def __init__(self): self.quad_modulation_mode = ACBBAParams.QUAD_MODULATION_MODE.DC self.quad_delta_kl = 0.01 # [1/m] - self.wait_quadrupole = 0.3 # [s] + self.wait_quadrupole = 1.0 # [s] self.cv_freq = 17.0 # [Hz] self.ch_freq = 23.0 # [Hz] self.qn_freq = 0.0 # [Hz] self.qs_freq = 0.0 # [Hz] - self.excit_time = 4 # [s] + self.excit_time = 2 # [s] self.corrs_delay = 5e-3 # [s] self.ch_kick = 5 # [urad] @@ -283,6 +283,13 @@ def _setup_orm(self): def _do_acbba(self): """.""" + # Initial checkings + if not all([ + self.check_isvalid_delta_kl(bpm) for bpm in self._bpms2dobba + ]): + self._log("Adjust quad strength or change dKL first.") + return + # Initialize data self.data["measure"] = dict() self._setup_orm() @@ -432,7 +439,7 @@ def _do_acbba_single_bpm(self, bpmname, **kw): msg = f'Changing quadrupole "{quadname}" strength... ' self._log(msg, tab=tab + 1, end="") sts = self.set_quad_strength( - quadname, stren_ini + delta_kl, tab=tab + 1 + quadname, stren_ini + delta_kl / 2, tab=tab + 1 ) if sts == self.STATUS.Fail: self.set_quad_strength( @@ -452,7 +459,7 @@ def _do_acbba_single_bpm(self, bpmname, **kw): msg = f'Changing quadrupole "{quadname}" strength... ' self._log(msg, tab=tab + 1, end="") sts = self.set_quad_strength( - quadname, stren_ini - delta_kl, tab=tab + 1 + quadname, stren_ini - delta_kl / 2, tab=tab + 1 ) if sts == self.STATUS.Fail: self.set_quad_strength( @@ -823,7 +830,7 @@ def _config_timing(self, cm_dly=0, chs=None, cvs=None, nr_points=None): for ch, cv in zip(chs, cvs): # noqa: B905 llt = set() for c in ch + cv: - trig = self._LLTime.get_trigger_name(c + ":BCKPLN") + trig = _LLTime.get_trigger_name(c + ":BCKPLN") llt.add(trig) ll_trigs.append(llt) @@ -976,12 +983,12 @@ def get_quad_strength_limits(self, quadname, margin=0.0005): return _np.array([lolim, hilim], dtype=float) def check_isvalid_delta_kl( - self, quadname, init_strength=None, delta_kl=None + self, bpmname, init_strength=None, delta_kl=None ): """.""" - if delta_kl is None: - delta_kl = self.params.quad_delta_kl - dkl = delta_kl + bpmidx = self.data['bpmnames'].index(bpmname) + quadname = self.data['quadnames'][bpmidx] + max_dkl = self.params.quad_delta_kl if delta_kl is None else delta_kl lolim, hilim = self.get_quad_strength_limits(quadname) @@ -989,18 +996,18 @@ def check_isvalid_delta_kl( init_strength = self.get_quad_strength(quadname) kl = init_strength - low = min(kl + dkl, kl - dkl) - upp = max(kl + dkl, kl - dkl) + low = min(kl + max_dkl / 2, kl - max_dkl / 2) + upp = max(kl + max_dkl / 2, kl - max_dkl / 2) if upp > hilim or low < lolim: - max_delta_kl = min(hilim - kl, kl - lolim) - msg = f"WARN: {quadname} KL = {kl:.2g}, dKL = {abs(dkl):.2g}. " - msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). " - msg += f"Max. dKL = {max_delta_kl * 2:.2g}." + msg = f"WARN: {quadname} KL = {kl:.2g}, dKL = {abs(max_dkl):.2g}." + max_dkl = min(hilim - kl, kl - lolim) + msg += f" Limits: ({lolim:.2g}, {hilim:.2g}). " + msg += f" Max. dKL = {max_dkl * 2:.2g}." self._log(msg) - return False, max_delta_kl + return False, max_dkl - return True, delta_kl + return True, max_dkl def correct_orbit(self): """.""" @@ -1160,6 +1167,11 @@ def _process_data_single_bpm(self, bpmname): y0 = 0.5 * (y0_pos + y0_neg) self.analysis[bpmname] = dict( + tim=tim, + dcx_pos=dcx_pos, + dcy_pos=dcy_pos, + dcx_neg=dcx_neg, + dcy_neg=dcy_neg, x0=x0, y0=y0, m_h=m_h, From d5780903b6a0f47b9faccc8b2d4e4fb696a579e7 Mon Sep 17 00:00:00 2001 From: Vitor Date: Thu, 6 Aug 2026 10:10:42 -0300 Subject: [PATCH 60/61] MNT (PBBA): Changes from machine-study (03/aug/2026) --- apsuite/commisslib/measure_bba_parallel.py | 80 +++++++++++----------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/apsuite/commisslib/measure_bba_parallel.py b/apsuite/commisslib/measure_bba_parallel.py index 5ae9a61d6..0ef1efcef 100644 --- a/apsuite/commisslib/measure_bba_parallel.py +++ b/apsuite/commisslib/measure_bba_parallel.py @@ -738,9 +738,9 @@ def _dopbba_single_group(self, group_id): ios_iter, dkicks_iter = [], [] sts = self.STATUS.Fail - converged = False - increased = False - _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) + # converged = False + # increased = False + # _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) for i in range(nr_iters): self._log_print( " {:02d}/{:02d} --> ".format(i + 1, nr_iters), end="" @@ -769,33 +769,35 @@ def _dopbba_single_group(self, group_id): break ios_iter.append(ios) # save ios (all bpms) ios = ios[enblbpm] # use only enabled bpms for correction + self._log_print(" IOS (rms):", _np.std(ios), '--> ', end="") # check convergence - if i > 0: - dios_p = _func(ios_iter[-2][enblbpm], ios) - if dios_p < 0.0: - prev_dkicks = dkicks_iter[-1] - self.set_delta_kicks(prev_dkicks) - self._log_print("Done.", end=" ") - converged = False - increased = True - break - dios_i = _func(ios_iter[0][enblbpm], ios) - if dios_i < self.params.ios_conv_threshold: - self._log_print("Done.", end=" ") - converged = True - break + # if i > 0: + # dios_p = _func(ios_iter[-2][enblbpm], ios) + # if dios_p < 0.0: + # prev_dkicks = dkicks_iter[-1] + # self.set_delta_kicks(prev_dkicks) + # self._log_print("Done.", end=" ") + # converged = False + # increased = True + # break + # dios_i = _func(ios_iter[0][enblbpm], ios) + # if dios_i < self.params.ios_conv_threshold: + # self._log_print("Done.", end=" ") + # converged = True + # break dkicks = list(-1 * _np.dot(inv_jac, ios)) dkicks_iter.append(dkicks) self.set_delta_kicks(dkicks) - self._log_print("Done.", end=" ") - - if sts and converged: - print(f"IOS converged ({i:d} iterations).") - elif sts and not converged and increased: - print(f"IOS increased ({i:d} iterations).") - elif sts and not converged and not increased: + self._log_print("Done.") + + # if sts and converged: + # print(f"IOS converged ({i:d} iterations).") + # elif sts and not converged and increased: + # print(f"IOS increased ({i:d} iterations).") + # elif sts and not converged and not increased: + if True: ios, sts = self.meas_ios(group_id, group_data["strengths_init"]) if not sts: self._restore_init_conditions( @@ -805,19 +807,19 @@ def _dopbba_single_group(self, group_id): ) else: ios_iter.append(ios) - ios = ios[enblbpm] - dios_i = _func(ios_iter[0][enblbpm], ios) - dios_p = _func(ios_iter[-2][enblbpm], ios) - if dios_i < self.params.ios_conv_threshold: - self._log_print( - f"Max iterations reached ({i + 1:d}), but IOS converged." - ) - elif dios_p < 0.0: - self._log_print( - f"Max iterations reached ({i + 1:d}), and IOS increased." - ) - else: - self._log_print(f"Max iterations reached ({i + 1:d}).") + # ios = ios[enblbpm] + # dios_i = _func(ios_iter[0][enblbpm], ios) + # dios_p = _func(ios_iter[-2][enblbpm], ios) + # if dios_i < self.params.ios_conv_threshold: + # self._log_print( + # f"Max iterations reached ({i + 1:d}), but IOS converged." + # ) + # elif dios_p < 0.0: + # self._log_print( + # f"Max iterations reached ({i + 1:d}), and IOS increased." + # ) + # else: + # self._log_print(f"Max iterations reached ({i + 1:d}).") group_data["kicks_end"] = self.get_kicks() group_data["ios_iter"] = ios_iter @@ -905,7 +907,7 @@ def _restore_init_conditions( if correct_orbit: self.correct_orbit() - def _log_print(self, msg): + def _log_print(self, msg, *args, **kw): """.""" self.data["log"].append((_time.time(), msg)) - print(msg) + print(msg, *args, **kw) From deb44325deb4068731dd11b505d898577eb007c0 Mon Sep 17 00:00:00 2001 From: Vitor Date: Wed, 19 Aug 2026 17:54:43 -0300 Subject: [PATCH 61/61] MNT (PBBA): Delete measure_bba_parallel.py --- apsuite/commisslib/measure_bba_parallel.py | 913 --------------------- 1 file changed, 913 deletions(-) delete mode 100644 apsuite/commisslib/measure_bba_parallel.py diff --git a/apsuite/commisslib/measure_bba_parallel.py b/apsuite/commisslib/measure_bba_parallel.py deleted file mode 100644 index 0ef1efcef..000000000 --- a/apsuite/commisslib/measure_bba_parallel.py +++ /dev/null @@ -1,913 +0,0 @@ -"""Main module.""" - -import datetime as _datetime -import time as _time -from copy import deepcopy as _dcopy - -import numpy as _np -import pyaccel as _pyacc -from mathphys.functions import get_namedtuple as _get_namedtuple -from pymodels import si as _si -from siriuspy.devices import ( - CurrInfoSI as _CurrInfoSI, - PowerSupply as _PowerSupply, - SOFB as _SOFB, -) -from siriuspy.namesys import SiriusPVName as _PVName - -from ..orbcorr.orbit_correction import OrbitCorr as _OrbitCorr -from ..utils import ( - ParamsBaseClass as _ParamsBaseClass, - ThreadedMeasBaseClass as _BaseClass, -) - -from .measure_bba import BBAParams as _BBAParams - - -class ParallelBBAParams(_ParamsBaseClass): - """.""" - - BPMNAMES = _BBAParams.BPMNAMES - QUADNAMES = _BBAParams.QUADNAMES - - def __init__(self): - """.""" - super().__init__() - - self.quad_deltakl = 0.01 # [1/m] - - self.wait_correctors = 0.3 # [s] - self.wait_quadrupole = 0.3 # [s] - self.timeout_wait_orbit = 3 # [s] - - self.corr_max_nr_iters = 8 - self.ios_conv_threshold = 0.1 - - self.sofb_nrpoints = 20 - self.sofb_maxcorriter = 5 - self.sofb_maxorberr = 5 # [um] - - self.cycling_nr_steps = 2 - - def __str__(self): - """.""" - stg = "" - stg += f"quad_deltakl = {self.quad_deltakl:.3f}\n" - stg += f"wait_correctors = {self.wait_correctors:.3f}\n" - stg += f"wait_quadrupole = {self.wait_quadrupole:.3f}\n" - stg += f"timeout_wait_orbit = {self.timeout_wait_orbit:.3f}\n" - stg += f"corr_nr_iters = {self.corr_max_nr_iters:.3f}\n" - stg += f"ios_conv_threshold = {self.ios_conv_threshold:.2e}\n" - stg += f"sofb_nrpoints = {self.sofb_nrpoints:.3f}\n" - stg += f"sofb_maxcorriter = {self.sofb_maxcorriter:.3f}\n" - stg += f"sofb_maxorberr = {self.sofb_maxorberr:.3f}\n" - stg += f"cycling_nr_steps = {self.cycling_nr_steps:.3f}\n" - return stg - - @staticmethod - def get_default_groups(ngroups=8): - """.""" - if ngroups == 2: - group_class = [ - [("M2", ""), ("C3", "1"), ("C1", "2"), ("C4", "")], - [("C1", "1"), ("C3", "2"), ("C2", ""), ("M1", "")], - ] - elif ngroups in {8, 16}: - group_class = [ - [("M2", "")], - [("C3", "1")], - [("C1", "1")], - [("C3", "2")], - [("C1", "2")], - [("C4", "")], - [("C2", "")], - [("M1", "")], - ] - else: - group_class = [ - [("M2", ""), ("C3", "1")], - [("C1", "1"), ("C3", "2")], - [("C1", "2"), ("C4", "")], - [("C2", ""), ("M1", "")], - ] - - groups = [ - sorted([ - e - for e in ParallelBBAParams.BPMNAMES - if (e.sub[2:], e.idx) in k - ]) - for k in group_class - ] - - if ngroups == 16: - groups_ = [] - for grp in groups: - groups_.append(grp[::2]) - groups_.append(grp[1::2]) - groups = groups_ - - groups = [ - sorted( - group, - key=lambda x: { - "Q4": 0, - "Q1": 1, - "QDB2": 2, - "QDP2": 3, - "QS": 4, - }.get( - ParallelBBAParams.QUADNAMES[ - ParallelBBAParams.BPMNAMES.index(x) - ].dev, - 999, - ), - ) - for group in groups - ] - return groups - - def get_default_dkl(self, ngroups=8, groups=None): - """.""" - groups = self.get_default_groups(ngroups) if groups is None else groups - dkl = [_np.ones(len(g)) * self.quad_deltakl for g in groups] - for d in dkl: - d[::2] *= -1 - return dkl - - -class DoParallelBBA(_BaseClass): - """.""" - - STATUS = _get_namedtuple("Status", ["Fail", "Success"]) - - def __init__(self, isonline=True): - """.""" - super().__init__( - params=ParallelBBAParams(), target=self._do_pbba, isonline=isonline - ) - self.data["bpmnames"] = list(ParallelBBAParams.BPMNAMES) - self.data["quadnames"] = list(ParallelBBAParams.QUADNAMES) - self.data["measure"] = list() - self.data["groups2dopbba"] = ParallelBBAParams.get_default_groups() - self.data["delta_kl"] = self.params.get_default_dkl( - groups=self.data["groups2dopbba"] - ) - self.data["log"] = list() - self.data["jacobians"] = list() - self._model = None - self._fam_data = None - - if self.isonline: - self.devices["sofb"] = _SOFB(_SOFB.DEVICES.SI) - self.devices["currinfosi"] = _CurrInfoSI() - self.connect_to_quadrupoles() - - def __str__(self): - """.""" - stn = "Params\n" - stp = self.params.__str__() - stp = " " + stp.replace("\n", "\n ") - stn += stp + "\n" - connected = str(self.connected and len(self.devices.keys()) > 0) - stn += "Connected? " + connected + "\n\n" - stn += " {:^20s} {:^20s} {:^7s}\n".format("BPM", "Quad", "dKL") - tmplt = "{:03d}: {:^20s} {:^20s} {:+.3f}\n" - dta = self.data - for group_id, group in enumerate(self.data["groups2dopbba"]): - stn += f"> Group {group_id:03d}\n" - for j, bpm in enumerate(group): - idx = dta["bpmnames"].index(bpm) - stn += tmplt.format( - idx, - dta["bpmnames"][idx], - dta["quadnames"][idx], - dta["delta_kl"][group_id][j], - ) - stn += "\n" - return stn - - @property - def havebeam(self): - """.""" - haveb = self.devices["currinfosi"] - return haveb.connected and haveb.storedbeam - - @property - def measuredbpms(self): - """.""" - mesured = [] - for group in self.data["measure"]: - mesured.extend(group["bpms"]) - return sorted(mesured) - - # #### pbba groups and deltas ##### - @property - def groups2dopbba(self): - """.""" - return _dcopy(self.data["groups2dopbba"]) - - @groups2dopbba.setter - def groups2dopbba(self, groups): - self.data["groups2dopbba"] = [ - [_PVName(bpm) for bpm in group if isinstance(bpm, str)] - for group in groups - if isinstance(group, (list, tuple, _np.ndarray)) - ] - - @property - def delta_kl(self): - """.""" - return _dcopy(self.data["delta_kl"]) - - @delta_kl.setter - def delta_kl(self, value): - _max = self.params.quad_deltakl - for i, group in enumerate(self.data["groups2dopbba"]): - if len(value[i]) != len(group): - raise ValueError( - f"size mismatch between group {i} and given delta_kl" - ) - if any([abs(v) > _max for v in value[i]]): - raise ValueError(f"values for delta kl can't exceed {_max}") - self.data["delta_kl"] = _dcopy(value) - - # #### model utils ##### - @property - def jacobians(self): - """.""" - return _dcopy(self.data["jacobians"]) - - @jacobians.setter - def jacobians(self, jacs): - """.""" - if len(jacs) != len(self.data["groups2dopbba"]): - raise ValueError("Size not compatible.") - self.data["jacobians"] = _dcopy(jacs) - - @property - def model(self): - """.""" - if self._model is None: - self._log_print("\n Undefined model... setting a default one") - self._model = _si.create_accelerator() - self._model.cavity_on = True - self._model.radiation_on = 1 - self._model = _si.fitted_models.vertical_dispersion_and_coupling( - self._model - ) - self._fam_data = _si.families.get_family_data(self._model) - return self._model - - @model.setter - def model(self, value): - if not value.cavity_on and value.radiation_on != 1: - raise ValueError( - "cavity_on must be True and radiation_on must be 1" - ) - self._model = value - self._fam_data = _si.families.get_family_data(self._model) - - @property - def fam_data(self): - """.""" - return self._fam_data - - # ### quadrupole connection ##### - def connect_to_quadrupoles(self): - """.""" - for qname in self.data["quadnames"]: - if qname in self.devices: - continue - self.devices[qname] = _PowerSupply( - qname, - props2init=("PwrState-Sts", "KL-SP", "KL-RB", "KLRef-Mon"), - ) - - # #### sofb utils ##### - def get_orbit(self): - """.""" - if not self.havebeam: - return - sofb = self.devices["sofb"] - nrpts = sofb.nr_points - sofb.nr_points = self.params.sofb_nrpoints - - sofb.cmd_reset() - sofb.wait_buffer(self.params.timeout_wait_orbit) - - sofb.nr_points = nrpts - - return _np.hstack([sofb.orbx, sofb.orby]) - - def correct_orbit(self): - """.""" - if not self.havebeam: - return - sofb = self.devices["sofb"] - sofb.correct_orbit_manually( - nr_iters=self.params.sofb_maxcorriter, - residue=self.params.sofb_maxorberr, - ) - - def get_kicks(self): - """.""" - sofb = self.devices["sofb"] - return _np.r_[sofb.kickch, sofb.kickcv, sofb.kickrf] - - def set_delta_kicks(self, dkicks): - """.""" - sofb = self.devices["sofb"] - nch, ncv, nrf = sofb._data.nr_ch, sofb._data.nr_cv, 1 - if len(dkicks) != nch + ncv + nrf: - raise ValueError( - f"invalid dim for dkicks, must have shape=({nch + ncv + nrf},)" - ) - dch, dcv, drf = dkicks[:nch], dkicks[nch : nch + ncv], dkicks[-1] - - sofb.deltakickch, sofb.deltakickcv, sofb.deltakickrf = dch, dcv, drf - sofb.cmd_applycorr_all() - - @property - def enbllistbpm(self): - """.""" - sofb = self.devices["sofb"] - enblx = sofb.bpmxenbl.copy() - enbly = sofb.bpmyenbl.copy() - return _np.array(_np.hstack([enblx, enbly]), dtype=bool) - - @enbllistbpm.setter - def enbllistbpm(self, value): - sofb = self.devices["sofb"] - nbpms = sofb._data.nr_bpms - if len(value) != 2 * nbpms: - raise ValueError(f"Invalid size! Must be {2 * nbpms}.") - if all(v in [0, 1, True, False] for v in value): - value = _np.array(value, dtype=bool) - else: - raise ValueError("Values must be boolean (0 / 1 or True / False).") - sofb.bpmxenbl = value[:nbpms] - sofb.bpmyenbl = value[nbpms:] - - # #### pbba utils ##### - - def set_quad_strengths(self, group_id, strengths, ignore_timeout=False): - """.""" - bpms = self.data["groups2dopbba"][group_id] - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - for strength, bpmname in zip(strengths, bpms): # noqa: B905 - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - quad.strength = strength - - if ignore_timeout: - return DoParallelBBA.STATUS.Success - - for strength, bpmname in zip(strengths, bpms): # noqa: B905 - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - if not quad.wait_float( - "KLRef-Mon", - strength, - rel_tol=0.0, - abs_tol=0.05 * self.params.quad_deltakl, - timeout=self.params.wait_quadrupole, - ): - return DoParallelBBA.STATUS.Fail - return DoParallelBBA.STATUS.Success - - def get_quad_strengths(self, group_id): - """.""" - bpms = self.data["groups2dopbba"][group_id] - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - - strengths = [] - for bpmname in bpms: - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - strengths.append(quad.strength) - return _np.array(strengths) - - def get_quad_strength_limits(self, group_id, margin=0.0005): - """.""" - bpms = self.data["groups2dopbba"][group_id] - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - - limits = [] - for bpmname in bpms: - quadname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[quadname] - pv = quad.pv_object("KL-SP") - upp = pv.upper_disp_limit - low = pv.lower_disp_limit - # Limits are interchanged in some quads: - lolim = min(upp, low) + margin - hilim = max(upp, low) - margin - limits.append([lolim, hilim]) - return _np.array(limits, dtype=float) - - def check_isvalid_dkl(self, group_id, init_strengths=None, margin=0.0005): - """.""" - bpms = self.data["groups2dopbba"][group_id] - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - - quadlims = self.get_quad_strength_limits(group_id, margin=margin) - delta_kl = self.data["delta_kl"][group_id] - - if init_strengths is None: - strengths = self.get_quad_strengths(group_id) - else: - strengths = init_strengths - - ok = True - for idx, bpmname in enumerate(bpms): - quadname = quad_names[bpm_names.index(bpmname)] - stren = strengths[idx] - dkl = delta_kl[idx] - lolim, hilim = quadlims[idx] - low = min(stren + dkl / 2, stren - dkl / 2) - upp = max(stren + dkl / 2, stren - dkl / 2) - if upp > hilim or low < lolim: - max_delta_kl = min(hilim - stren, stren - lolim) - msg = f"WARN: {quadname} KL = {stren:.2g}, dKL = {abs(dkl):.2g}. " - msg += f"Limits: ({lolim:.2g}, {hilim:.2g}). Max. dKL = {max_delta_kl * 2:.2g}." - self._log_print(msg) - ok = False - return ok - - def meas_ios(self, group_id, init_strengths=None): - """.""" - delta_strens = self.data["delta_kl"][group_id] - - if init_strengths is None: - strens_orig = self.get_quad_strengths(group_id) - _time.sleep(self.params.wait_quadrupole) - else: - strens_orig = init_strengths - - if not self.set_quad_strengths( - group_id, strens_orig + delta_strens / 2 - ): - return None, DoParallelBBA.STATUS.Fail - - orb_pos = self.get_orbit() - - if not self.set_quad_strengths( - group_id, strens_orig - delta_strens / 2 - ): - return None, DoParallelBBA.STATUS.Fail - - orb_neg = self.get_orbit() - - if not self.set_quad_strengths(group_id, strens_orig): - return None, DoParallelBBA.STATUS.Fail - - return orb_pos - orb_neg, DoParallelBBA.STATUS.Success - - def calc_ios_jacobians(self, groups_to_calc=None): # noqa: C901 - """Calculate the IOS Response Matrices for all groups.""" - model = self.model - _orbcorr = _OrbitCorr( - model=model, acc="SI", corr_system="SOFB", use6dtrack=True - ) - quadindices = self._get_quads_indices_in_model(self.data["quadnames"]) - - def _get_or_set_kl(bname, value=None): - _do = getattr if value is None else setattr - bidx = self.data["bpmnames"].index(bname) - qidx = quadindices[bidx] - if "QS" == self.data["quadnames"][bidx].dev: - return _do(model[qidx], "KsL", value) - else: - return _do(model[qidx], "KL", value) - - def _get_quad_strengths(group): - strens = [] - for bname in group: - strens.append(_get_or_set_kl(bname)) - return _np.array(strens) - - def _set_quad_strengths(group, strengths): - for strength, bname in zip(strengths, group): # noqa: B905 - _get_or_set_kl(bname, strength) - - jacobians = [] - groups_to_calc = ( - _np.arange(len(self.data["groups2dopbba"])) - if groups_to_calc is None - else groups_to_calc - ) - for group_id in groups_to_calc: - group = self.data["groups2dopbba"][group_id] - try: - delta_strens = self.data["delta_kl"][group_id] - except Exception as e: - str_msg = 'undefined or empty "delta_kl"' - str_msg += f" of group {group_id}" - raise IndexError(str_msg) from e - strens_orig = _get_quad_strengths(group) - - _set_quad_strengths(group, strens_orig + delta_strens / 2) - try: - jac_pos = _orbcorr.get_jacobian_matrix() - except Exception as err: - _set_quad_strengths(group, strens_orig) - raise err - - _set_quad_strengths(group, strens_orig - delta_strens / 2) - try: - jac_neg = _orbcorr.get_jacobian_matrix() - except Exception as err: - _set_quad_strengths(group, strens_orig) - raise err - - _set_quad_strengths(group, strens_orig) - jac = jac_pos - jac_neg - jac[:, -1] *= 1e6 # rescale: [m/Hz] -> [um/Hz] - jacobians.append(jac) - return jacobians - - def analyze_groups(self, analyze_coupling=False): - """Helper function to analyze the groups' properties.""" - if not self.data["jacobians"]: - raise ValueError("Please calculate and set jacobians first.") - - anl = [] - for group_id in range(len(self.data["groups2dopbba"])): - print(f"Analyzing group: {group_id:d}") - anl.append(self.analyze_group(group_id, analyze_coupling)) - return anl - - def analyze_group(self, group_id, analyze_coupling=False): - """Helper function to analyze group's properties.""" - jacobian = self.data["jacobians"][group_id] - u_mat, svals, vt_mat = _np.linalg.svd(jacobian) - - model = self.model - quadindices = self._get_quads_indices_in_model(self.data["quadnames"]) - delta_strens = self.data["delta_kl"][group_id] - group = self.data["groups2dopbba"][group_id] - - tune_variation = [_pyacc.optics.get_frac_tunes(model)[:2]] - - if analyze_coupling: - - def _get_coupling_parameters(): - rad_on, cav_on = model.radiation_on, model.cavity_on - model.radiation_on = 0 - model.cavity_on = False - ed = _pyacc.optics.calc_edwards_teng(model)[0] - mtsp, ratio = _pyacc.optics.estimate_coupling_parameters(ed) - model.radiation_on = rad_on - model.cavity_on = cav_on - return mtsp, _np.std(ratio) - - min_tunesep, std_ratio = _get_coupling_parameters() - min_tunesep_variation = [min_tunesep] - std_ratio_variation = [std_ratio] - - for fac in [1, -2, 1]: - for dkl, bpm in zip(delta_strens, group): # noqa: B905 - _id = self.data["bpmnames"].index(bpm) - qname = self.data["quadnames"][_id] - qidx = quadindices[_id] - if "QS" in qname: - model[qidx].KsL += fac * dkl / 2 - else: - model[qidx].KL += fac * dkl / 2 - tune_variation.append(_pyacc.optics.get_frac_tunes(model)[:2]) - if analyze_coupling: - min_tunesep, std_ratio = _get_coupling_parameters() - min_tunesep_variation.append(min_tunesep) - std_ratio_variation.append(std_ratio) - - ret = { - "u_matrix": u_mat, - "vt_matrix": vt_mat, - "svals": svals, - "tune_variation": _np.array(tune_variation), - } - if analyze_coupling: - ret["min_tunesep_variation"] = _np.array(min_tunesep_variation) - ret["std_ratio_variation"] = _np.array(std_ratio_variation) - return ret - - def process_data(self): - """.""" - for group_id in range(len(self.data["groups2dopbba"])): - self.process_data_single_group(group_id) - - def process_data_single_group(self, group_id): - """.""" - meas_data = self.data["measure"][group_id] - bpmnames = self.data["bpmnames"] - nbpms = len(bpmnames) - orbit = meas_data["orbit_end"] - - # #### error estimation ? ##### - # ios_iter = meas_data['ios_iter'] - # ios_init = ios_iter[0] - # iosx_init, iosy_init = ios_init[:nbpms], ios_init[nbpms:] - # ios_end = ios_iter[-1] - # iosx_end, iosy_end = ios_end[:nbpms], ios_end[nbpms:] - stdx0 = 0.0 - stdy0 = 0.0 - - for bpm in meas_data["bpms"]: - bpm_idx = bpmnames.index(bpm) - self.analysis[bpm] = { - "x0": orbit[bpm_idx], - "y0": orbit[bpm_idx + nbpms], - "stdx0": stdx0, - "stdy0": stdy0, - } - - def get_pbba_results(self, error=False): - """.""" - bpms = self.data["bpmnames"] - bbax = _np.zeros(len(bpms)) - bbay = _np.zeros(len(bpms)) - if error: - bbaxerr = _np.zeros(len(bpms)) - bbayerr = _np.zeros(len(bpms)) - for idx, bpm in enumerate(bpms): - res = self.analysis.get(bpm) - if not res: - continue - bbax[idx] = res["x0"] - bbay[idx] = res["y0"] - if error and "stdx0" in res: - bbaxerr[idx] = res["stdx0"] - bbayerr[idx] = res["stdy0"] - if error: - return bbax, bbay, bbaxerr, bbayerr - return bbax, bbay - - # #### private methods #### - def _get_quads_indices_in_model(self, quadnames): - """.""" - fam_data = self.fam_data - quadindices = [] - for qname in quadnames: - key = qname.dev - idx = fam_data[key]["devnames"].index(qname) - qindex = fam_data[key]["index"][idx] - qindex = qindex[0] if len(qindex) == 1 else qindex - quadindices.append(qindex) - return quadindices - - def _do_pbba(self): - tini = _datetime.datetime.fromtimestamp(_time.time()) - self._log_print( - "Starting measurement at {:s}".format( - tini.strftime("%Y-%m-%d %Hh%Mm%Ss") - ) - ) - - groups = self.data["groups2dopbba"] - if not all([self.check_isvalid_dkl(g) for g, _ in enumerate(groups)]): - self._log_print("Adjust quad strength or change dKL first.") - return - - self.data["jacobians"] = self.calc_ios_jacobians() - self.data["measure"] = list() - - sofb = self.devices["sofb"] - if sofb.autocorrsts: - self._log_print( - "\nSOFB feedback is enabled. Please desable it first." - ) - return - - for gid, _ in enumerate(groups): - if self._stopevt.is_set(): - self._log_print("\nStopped!") - break - if not self.havebeam: - self._log_print("\nBeam was Lost") - break - self._log_print("\nCorrecting Orbit... ", end="") - self.correct_orbit() - self._log_print("Ok!") - if not self._dopbba_single_group(gid): - break - - self._log_print("\nCorrecting Orbit... ", end="") - self.correct_orbit() - self._log_print("Ok!") - - tfin = _datetime.datetime.fromtimestamp(_time.time()) - dtime = str(tfin - tini) - dtime = dtime.split(".")[0] - self._log_print("\nFinished! Elapsed time {:s}".format(dtime)) - - def _dopbba_single_group(self, group_id): - """.""" - tini = _datetime.datetime.fromtimestamp(_time.time()) - strtini = tini.strftime("%Hh%Mm%Ss") - self._log_print(f"{strtini:s}: Doing PBBA for Group {group_id:d}") - - enblbpm = self.enbllistbpm # cut jacobian with only enabled bpms - jac = (self.data["jacobians"][group_id])[enblbpm, :] - inv_jac = self._calc_inverse_jacobian(jac, group_id) - - group_data = { - "bpms": self.data["groups2dopbba"][group_id], - "strengths_init": self.get_quad_strengths(group_id), - "orbit_init": self.get_orbit(), - "kicks_init": self.get_kicks(), - "enbllistbpm": enblbpm.copy(), - } - - self._log_print(" Cycling:") - msg, sts = self._do_cycling(group_id, group_data["strengths_init"]) - if not sts: - self._restore_init_conditions( - group_id, - group_data["strengths_init"], - extra_info_before_message=msg, - ) - nr_iters = 0 - else: # proceed to IOS correction - self._log_print(" Correcting IOS:") - nr_iters = self.params.corr_max_nr_iters - - ios_iter, dkicks_iter = [], [] - sts = self.STATUS.Fail - # converged = False - # increased = False - # _func = lambda a, b: (_np.std(a) - _np.std(b)) / _np.std(a) - for i in range(nr_iters): - self._log_print( - " {:02d}/{:02d} --> ".format(i + 1, nr_iters), end="" - ) - if self._stopevt.is_set(): - self._restore_init_conditions( - group_id, - group_data["strengths_init"], - extra_info_before_message="Measurement stopped. ", - ) - break - if not self.havebeam: - self._restore_init_conditions( - group_id, - group_data["strengths_init"], - extra_info_before_message="Error: beam is off. ", - ) - break - ios, sts = self.meas_ios(group_id, group_data["strengths_init"]) - if not sts: - self._restore_init_conditions( - group_id, - group_data["strengths_init"], - extra_info_before_message="Fail while measuring IOS. ", - ) - break - ios_iter.append(ios) # save ios (all bpms) - ios = ios[enblbpm] # use only enabled bpms for correction - self._log_print(" IOS (rms):", _np.std(ios), '--> ', end="") - - # check convergence - # if i > 0: - # dios_p = _func(ios_iter[-2][enblbpm], ios) - # if dios_p < 0.0: - # prev_dkicks = dkicks_iter[-1] - # self.set_delta_kicks(prev_dkicks) - # self._log_print("Done.", end=" ") - # converged = False - # increased = True - # break - # dios_i = _func(ios_iter[0][enblbpm], ios) - # if dios_i < self.params.ios_conv_threshold: - # self._log_print("Done.", end=" ") - # converged = True - # break - - dkicks = list(-1 * _np.dot(inv_jac, ios)) - dkicks_iter.append(dkicks) - self.set_delta_kicks(dkicks) - self._log_print("Done.") - - # if sts and converged: - # print(f"IOS converged ({i:d} iterations).") - # elif sts and not converged and increased: - # print(f"IOS increased ({i:d} iterations).") - # elif sts and not converged and not increased: - if True: - ios, sts = self.meas_ios(group_id, group_data["strengths_init"]) - if not sts: - self._restore_init_conditions( - group_id, - group_data["strengths_init"], - extra_info_before_message="Fail while measuring IOS. ", - ) - else: - ios_iter.append(ios) - # ios = ios[enblbpm] - # dios_i = _func(ios_iter[0][enblbpm], ios) - # dios_p = _func(ios_iter[-2][enblbpm], ios) - # if dios_i < self.params.ios_conv_threshold: - # self._log_print( - # f"Max iterations reached ({i + 1:d}), but IOS converged." - # ) - # elif dios_p < 0.0: - # self._log_print( - # f"Max iterations reached ({i + 1:d}), and IOS increased." - # ) - # else: - # self._log_print(f"Max iterations reached ({i + 1:d}).") - - group_data["kicks_end"] = self.get_kicks() - group_data["ios_iter"] = ios_iter - group_data["dkicks_iter"] = dkicks_iter - group_data["orbit_end"] = self.get_orbit() - group_data["delta_kl"] = self.data["delta_kl"][group_id] - self.data["measure"].append(group_data) - - self.correct_orbit() - - tfin = _datetime.datetime.fromtimestamp(_time.time()) - dtime = str(tfin - tini) - dtime = dtime.split(".")[0] - msg = " Finished. Status: " - if sts: - self._log_print(msg + "OK! Elapsed time: {:s}".format(dtime)) - else: - self._log_print(msg + "Fail! Elapsed time: {:s}".format(dtime)) - return sts - - def _calc_inverse_jacobian(self, jacobian, group_id): - """.""" - u, s, vt = _np.linalg.svd(jacobian, full_matrices=False) - nr_svals = 2 * len(self.groups2dopbba[group_id]) - i_s = _np.zeros_like(s) - i_s[:nr_svals] = 1.0 / s[:nr_svals] - - return vt.T @ _np.diag(i_s) @ u.T - - def _do_cycling(self, group_id, init_strengths): - delta_strengths = self.data["delta_kl"][group_id] - nr_cycles = self.params.cycling_nr_steps - for i in range(nr_cycles): - self._log_print( - " {:02d}/{:02d} --> ".format(i + 1, nr_cycles), end="" - ) - if self._stopevt.is_set(): - return "Event stopped! ", DoParallelBBA.STATUS.Fail - if not self.havebeam: - return "No beam! ", DoParallelBBA.STATUS.Fail - if not self.set_quad_strengths( - group_id, init_strengths + delta_strengths / 2 - ): - return "Fail! ", DoParallelBBA.STATUS.Fail - if not self.set_quad_strengths( - group_id, init_strengths - delta_strengths / 2 - ): - return "Fail! ", DoParallelBBA.STATUS.Fail - if not self.set_quad_strengths(group_id, init_strengths): - return "Fail! ", DoParallelBBA.STATUS.Fail - self._log_print("Ok") - return "", DoParallelBBA.STATUS.Success - - def _restore_init_conditions( - self, - group_id, - init_strengths, - message="Restoring initial conditions and exiting...", - correct_orbit=True, - extra_info_before_message="", - ): - """.""" - self._log_print(extra_info_before_message + message) - - self.set_quad_strengths(group_id, init_strengths, ignore_timeout=True) - - bpms = self.data["groups2dopbba"][group_id] - quad_names = self.data["quadnames"] - bpm_names = self.data["bpmnames"] - - for strength, bpmname in zip(init_strengths, bpms): # noqa: B905 - qname = quad_names[bpm_names.index(bpmname)] - quad = self.devices[qname] - if not quad.wait_float( - "KLRef-Mon", - strength, - rel_tol=0.0, - abs_tol=0.05 * self.params.quad_deltakl, - timeout=self.params.wait_quadrupole, - ): - self._log_print( - f" {qname}: Could not be restored to initial strength" - ) - - if correct_orbit: - self.correct_orbit() - - def _log_print(self, msg, *args, **kw): - """.""" - self.data["log"].append((_time.time(), msg)) - print(msg, *args, **kw)