From 4a0fdb038a2b24227523c0b44ed262654d919c5d Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 12 Apr 2024 12:58:03 -0300 Subject: [PATCH 001/144] COMMIS.ENH: initial draft of TbT measurement class --- apsuite/commisslib/measure_tbt_data.py | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 apsuite/commisslib/measure_tbt_data.py diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py new file mode 100644 index 00000000..31ee1521 --- /dev/null +++ b/apsuite/commisslib/measure_tbt_data.py @@ -0,0 +1,52 @@ +""".""" +import numpy as _np +from scipy.optimize import curve_fit as _curve_fit +import matplotlib.pyplot as _mplt +import datetime as _datetime +from siriuspy.devices import PowerSupplyPU + +from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ + AcqBPMsSignalsParams as _AcqBPMsSignalsParams + +class MeasureTbTData(_AcqBPMsSignals): + """.""" + def __init__(self, filename='', isonline=False): + super.__init__(params=TbTDataParams(), isonline=isonline, + ispost_mortem=False) + self._fname = filename + + + def create_devices(self): + super().create_devices() + self.devices['pingh'] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) + self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) + + + @property + def fname(self): + """.""" + return self._fname + + @fname.setter + def fname(self, val): + self._fname = val + + def prepare_pingers(self): + """.""" + pingh, pingv = self.devices['pingh'], self.devices['pingv'] + params = self.params + hkick, vkick = params['hkick'], params['vkick'] + pingh.strength = hkick / 1e3 # [urad] + pingv.strength = vkick / 1e3 # [urad] + # MISSING: set to listen to the same event as BPMs + + def do_measurement(self): + currinfo = self.devices['currinfo'] + self.prepare_pingers() + current_before = currinfo.current() + self.acquire_data() + self.data['current_before'] = current_before + self.data['current_after'] = self.data.pop('stored_current') + self.data['trajx'] = self.data.pop('orbx') + self.data['trajy'] = self.data.pop('orby') From bc0ba35970da0e6c72e4b59e649423a10bceae48 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 12 Apr 2024 12:59:51 -0300 Subject: [PATCH 002/144] COMMISS.TBT.ENH: add harmonic analysis methods --- apsuite/commisslib/measure_tbt_data.py | 157 +++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 31ee1521..5f1aed24 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -8,6 +8,163 @@ from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ AcqBPMsSignalsParams as _AcqBPMsSignalsParams +class TbTDataAnalysis(_AcqBPMsSignals): + """.""" + + def __init__(self, filename='', isonline=False): + """Analysis of betatron motion and linear optics using Turn-by-turn + data""" + super().__init__(isonline=isonline, ispost_mortem=False) + + @property + def fname(self): + """.""" + return self._fname + + @fname.setter + def fname(self, val): + self._fname = val + + @property + def trajx(self): + """.""" + return self._trajx + + @trajx.setter + def trajx(self, val): + self._trajx = val + + @property + def trajy(self): + """.""" + return self._trajy + + @trajy.setter + def trajx(self, val): + self._trajy = val + + def linear_optics_analysis(self): + raise NotImplementedError + + def harmonic_analysis(self): + """.""" + raise NotImplementedError + + def principal_components_analysis(self): + raise NotImplementedError + + def independent_component_analysis(self): + raise NotImplementedError + + def equilibrium_params_analysis(self): + raise NotImplementedError + + # plotting methods + def plot_traj_spectrum(): + raise NotImplementedError + + + def _get_tune_guess(self, matrix): + """.""" + matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) + tunes = tune[:, None] * _np.ones(matrix.shape[-1])[None, :] + peak_idcs = _np.abs(matrix_dft).argmax(axis=0) + tune_peaks = [tunes[idc, col] for col, idc in enumerate(peak_idcs)] + return _np.mean(tune_peaks) # tune guess + + def _get_amplitudes_and_phases_guess(self, matrix, tune): + """Calculate initial guesses for harmonic TbT model as + Eqs. (5.2)-(5.4) of Ref. [2]""" + N = matrix.shape[0] + ilist = _np.arange(N) + cos = _np.cos(2 * _np.pi * tune * ilist) + sin = _np.sin(2 * _np.pi * tune * ilist) + C, S = _np.dot(cos, matrix), _np.dot(sin, matrix) + C *= 2/N + S *= 2/N + amplitudes = _np.sqrt(C**2 + S**2) + phases = _np.unwrap(_np.arctan2(C, S)) + return amplitudes, phases + + def harmonic_tbt_model(self, ilist, amplitude, tune, phase): + """Harmonic motion model for positions seen at a given BPM""" + return amplitude * _np.cos(2 * _np.pi * tune * ilist + phase) + + def harmonic_tbt_model_vectorized(self, ilist, amplitude, tune, phase): + """Harmonic motion model for positions seen at a given BPM""" + return amplitude[None, :] * _np.cos(2 * _np.pi * tune * ilist[:, None] + phase[None, :]) + + def fit_harmonic_model(self, matrix, amp_guesses, + tune_guess, phase_guesses): + """Fits harmonic TbT model to data. + + Args: + matrix (N,M)-array: data matrix containing N turns and BPMs + amp_guesses M-array: amplitude guess for each BPM time-series + tune_guess (float): betatron tune guess + phase_guesses M-array: phase guess for each BPM + + Returns: + params (3, M)-array: fitted ampliude, tune and phase for each BPM. + each column corresponds to a BPM, each row + corresponds to amplitudes, tune and phase + """ + ilist = _np.arange(matrix.shape[0]) + params = _np.zeros((3, matrix.shape[-1])) + for bpm_idx, bpm_data in enumerate(matrix.T): + p0=[amp_guesses[bpm_idx],tune_guess,phase_guesses[bpm_idx]] + popt, *_ = _curve_fit(f=self.harmonic_tbt_model, + xdata=ilist,ydata=bpm_data, + p0=p0) + params[:, bpm_idx] = popt + return params + + def calculate_betafunc_and_action(self, amplitudes, nominal_beta): + """Calculates beta function and betatron action as in Eq. (9) + of Ref. [1]""" + action = _np.sum(amplitudes**4) + action /= _np.sum(amplitudes**2 * nominal_beta) + beta = amplitudes**2/action + return beta, action + +class TbTDataParams(_AcqBPMsSignalsParams): + """.""" + + def __init__(self, hkick=None, vkick=None): + super().__init__() + self.nrpoints_before = 100 + self.nrpoints_after = 2000 + self.acq_rate = 'TbT' + self.signals2acq = 'XYS' + self._hkick = hkick + self._vkick = vkick + + + def __str__(self): + stg = super().__str__() + ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format + stg += ftmp('hkick', self.hkick, '[urad]') + stg += ftmp('vkick', self.vkick, '[urad]') + return stg + + @property + def hkick(self): + """.""" + return self._hkick + + @hkick.setter + def hkick(self, val): + self._hkick = val + + @property + def vkick(self): + """.""" + return self._vkick + + @vkick.setter + def vkick(self, val): + self._vkick = val + class MeasureTbTData(_AcqBPMsSignals): """.""" def __init__(self, filename='', isonline=False): From 70de76f8dbe9e5ac2ca9b1ea639f74593c325c09 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Apr 2024 10:06:40 -0300 Subject: [PATCH 003/144] COMMIS.TBT.BUG: add None type exceptions in str method --- apsuite/commisslib/measure_tbt_data.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 5f1aed24..7b41d337 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -143,8 +143,15 @@ def __init__(self, hkick=None, vkick=None): def __str__(self): stg = super().__str__() ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format - stg += ftmp('hkick', self.hkick, '[urad]') - stg += ftmp('vkick', self.vkick, '[urad]') + stmp = '{0:26s} = {1:9} {2:s}\n'.format + if self.hkick is None: + stg += stmp('hkick', 'same', '(current value will not be changed)') + else: + stg += ftmp('hkick', self.hkick, '[urad]') + if self.hkick is None: + stg += stmp('vkick', 'same', '(current value will not be changed)') + else: + stg += ftmp('vkick', self.vkick, '[urad]') return stg @property From 594756b42a79013e6a9b57eea050be126f02adce Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Apr 2024 10:08:21 -0300 Subject: [PATCH 004/144] COMMISS.TBT.BUG: fix MeasTbTData initialization --- apsuite/commisslib/measure_tbt_data.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 7b41d337..dbdf3f77 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -175,10 +175,13 @@ def vkick(self, val): class MeasureTbTData(_AcqBPMsSignals): """.""" def __init__(self, filename='', isonline=False): - super.__init__(params=TbTDataParams(), isonline=isonline, - ispost_mortem=False) + """.""" + self.params = TbTDataParams() + self.isonline = isonline self._fname = filename + if self.isonline: + self.create_devices() def create_devices(self): super().create_devices() @@ -186,7 +189,6 @@ def create_devices(self): PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) - @property def fname(self): """.""" @@ -199,8 +201,7 @@ def fname(self, val): def prepare_pingers(self): """.""" pingh, pingv = self.devices['pingh'], self.devices['pingv'] - params = self.params - hkick, vkick = params['hkick'], params['vkick'] + hkick, vkick = self.params['hkick'], self.params['vkick'] pingh.strength = hkick / 1e3 # [urad] pingv.strength = vkick / 1e3 # [urad] # MISSING: set to listen to the same event as BPMs From 44d38027463015cc2dbce0bb8bee52cba75c27e4 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Apr 2024 14:11:22 -0300 Subject: [PATCH 005/144] COMMISS.TBT.ENH: add restoring strengths methods, remove input to params, and add checking of strengths --- apsuite/commisslib/measure_tbt_data.py | 47 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index dbdf3f77..d8819295 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -180,9 +180,6 @@ def __init__(self, filename='', isonline=False): self.isonline = isonline self._fname = filename - if self.isonline: - self.create_devices() - def create_devices(self): super().create_devices() self.devices['pingh'] = PowerSupplyPU( @@ -198,19 +195,49 @@ def fname(self): def fname(self, val): self._fname = val - def prepare_pingers(self): + def get_magnets_state(self): """.""" pingh, pingv = self.devices['pingh'], self.devices['pingv'] - hkick, vkick = self.params['hkick'], self.params['vkick'] - pingh.strength = hkick / 1e3 # [urad] - pingv.strength = vkick / 1e3 # [urad] - # MISSING: set to listen to the same event as BPMs + hkick, vkick = pingh.strength, pingv.strength + return hkick, vkick + + def recover_magnets_state(self, hkick, vkick): + """.""" + self.set_magnets_state(hkick, vkick) + + def set_magnets_state(self, hkick, vkick): + """.""" + pingh, pingv = self.devices['pingh'], self.devices['pingv'] + if hkick is not None: + pingh.strength = hkick + if vkick is not None: + pingv.strength = vkick + + def prepare_magnets(self): + """.""" + hkick, vkick = self.params.hkick, self.params.vkick + hkick = hkick/1e6 if hkick is not None else None + vkick = vkick/1e6 if vkick is not None else None + self.set_magnets_state(hkick, vkick) def do_measurement(self): + """.""" currinfo = self.devices['currinfo'] - self.prepare_pingers() + init_timing_state = self.get_timing_state() + init_magnets_state = self.get_magnets_state() current_before = currinfo.current() - self.acquire_data() + self.prepare_timing() + self.prepare_magnets() + self.data['measurement_error'] = False # error flag + try: + self.acquire_data() # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + except Exception as e: + print(f'An error occurred during acquisition: {e}') + self.data['measurement_error'] = True + self.recover_timing_state(init_timing_state) + self.recover_magnets_state(init_magnets_state) self.data['current_before'] = current_before self.data['current_after'] = self.data.pop('stored_current') self.data['trajx'] = self.data.pop('orbx') From f1d8fb9d32ba8384db97d7fed041a3623fc48d25 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Apr 2024 14:14:18 -0300 Subject: [PATCH 006/144] COMMISS.TBT.ENH: change ordering to params, meas, analysis --- apsuite/commisslib/measure_tbt_data.py | 243 +++++++++++++------------ 1 file changed, 127 insertions(+), 116 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d8819295..b48abce1 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -8,6 +8,133 @@ from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ AcqBPMsSignalsParams as _AcqBPMsSignalsParams + +class TbTDataParams(_AcqBPMsSignalsParams): + """.""" + + def __init__(self): + self.signals2acq = 'XYS' + self.acq_rate = 'TbT' + self.timeout = 40 # [s] + + self.nrpoints_before = 100 + self.nrpoints_after = 2000 + self.acq_repeat = False + self.trigbpm_delay = None + self.trigbpm_nrpulses = 1 + + self.timing_event = 'Linac' + self.event_mode = 'Injection' + self.event_delay = None + self.do_pulse_evg = False + + self._hkick = None # [urad] + self._vkick = None # [urad] + + + def __str__(self): + stg = super().__str__() + ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format + stmp = '{0:26s} = {1:9} {2:s}\n'.format + if self.hkick is None: + stg += stmp('hkick', 'same', '(current value will not be changed)') + else: + stg += ftmp('hkick', self.hkick, '[urad]') + if self.hkick is None: + stg += stmp('vkick', 'same', '(current value will not be changed)') + else: + stg += ftmp('vkick', self.vkick, '[urad]') + return stg + + @property + def hkick(self): + """.""" + return self._hkick + + @hkick.setter + def hkick(self, val): + self._hkick = val + + @property + def vkick(self): + """.""" + return self._vkick + + @vkick.setter + def vkick(self, val): + self._vkick = val + +class MeasureTbTData(_AcqBPMsSignals): + """.""" + def __init__(self, filename='', isonline=False): + """.""" + self.params = TbTDataParams() + self.isonline = isonline + self._fname = filename + + def create_devices(self): + super().create_devices() + self.devices['pingh'] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) + self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) + + @property + def fname(self): + """.""" + return self._fname + + @fname.setter + def fname(self, val): + self._fname = val + + def get_magnets_state(self): + """.""" + pingh, pingv = self.devices['pingh'], self.devices['pingv'] + hkick, vkick = pingh.strength, pingv.strength + return hkick, vkick + + def recover_magnets_state(self, hkick, vkick): + """.""" + self.set_magnets_state(hkick, vkick) + + def set_magnets_state(self, hkick, vkick): + """.""" + pingh, pingv = self.devices['pingh'], self.devices['pingv'] + if hkick is not None: + pingh.strength = hkick + if vkick is not None: + pingv.strength = vkick + + def prepare_magnets(self): + """.""" + hkick, vkick = self.params.hkick, self.params.vkick + hkick = hkick/1e6 if hkick is not None else None + vkick = vkick/1e6 if vkick is not None else None + self.set_magnets_state(hkick, vkick) + + def do_measurement(self): + """.""" + currinfo = self.devices['currinfo'] + init_timing_state = self.get_timing_state() + init_magnets_state = self.get_magnets_state() + current_before = currinfo.current() + self.prepare_timing() + self.prepare_magnets() + self.data['measurement_error'] = False # error flag + try: + self.acquire_data() # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + except Exception as e: + print(f'An error occurred during acquisition: {e}') + self.data['measurement_error'] = True + self.recover_timing_state(init_timing_state) + self.recover_magnets_state(init_magnets_state) + self.data['current_before'] = current_before + self.data['current_after'] = self.data.pop('stored_current') + self.data['trajx'] = self.data.pop('orbx') + self.data['trajy'] = self.data.pop('orby') + class TbTDataAnalysis(_AcqBPMsSignals): """.""" @@ -126,119 +253,3 @@ def calculate_betafunc_and_action(self, amplitudes, nominal_beta): action /= _np.sum(amplitudes**2 * nominal_beta) beta = amplitudes**2/action return beta, action - -class TbTDataParams(_AcqBPMsSignalsParams): - """.""" - - def __init__(self, hkick=None, vkick=None): - super().__init__() - self.nrpoints_before = 100 - self.nrpoints_after = 2000 - self.acq_rate = 'TbT' - self.signals2acq = 'XYS' - self._hkick = hkick - self._vkick = vkick - - - def __str__(self): - stg = super().__str__() - ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format - stmp = '{0:26s} = {1:9} {2:s}\n'.format - if self.hkick is None: - stg += stmp('hkick', 'same', '(current value will not be changed)') - else: - stg += ftmp('hkick', self.hkick, '[urad]') - if self.hkick is None: - stg += stmp('vkick', 'same', '(current value will not be changed)') - else: - stg += ftmp('vkick', self.vkick, '[urad]') - return stg - - @property - def hkick(self): - """.""" - return self._hkick - - @hkick.setter - def hkick(self, val): - self._hkick = val - - @property - def vkick(self): - """.""" - return self._vkick - - @vkick.setter - def vkick(self, val): - self._vkick = val - -class MeasureTbTData(_AcqBPMsSignals): - """.""" - def __init__(self, filename='', isonline=False): - """.""" - self.params = TbTDataParams() - self.isonline = isonline - self._fname = filename - - def create_devices(self): - super().create_devices() - self.devices['pingh'] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) - self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) - - @property - def fname(self): - """.""" - return self._fname - - @fname.setter - def fname(self, val): - self._fname = val - - def get_magnets_state(self): - """.""" - pingh, pingv = self.devices['pingh'], self.devices['pingv'] - hkick, vkick = pingh.strength, pingv.strength - return hkick, vkick - - def recover_magnets_state(self, hkick, vkick): - """.""" - self.set_magnets_state(hkick, vkick) - - def set_magnets_state(self, hkick, vkick): - """.""" - pingh, pingv = self.devices['pingh'], self.devices['pingv'] - if hkick is not None: - pingh.strength = hkick - if vkick is not None: - pingv.strength = vkick - - def prepare_magnets(self): - """.""" - hkick, vkick = self.params.hkick, self.params.vkick - hkick = hkick/1e6 if hkick is not None else None - vkick = vkick/1e6 if vkick is not None else None - self.set_magnets_state(hkick, vkick) - - def do_measurement(self): - """.""" - currinfo = self.devices['currinfo'] - init_timing_state = self.get_timing_state() - init_magnets_state = self.get_magnets_state() - current_before = currinfo.current() - self.prepare_timing() - self.prepare_magnets() - self.data['measurement_error'] = False # error flag - try: - self.acquire_data() # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - except Exception as e: - print(f'An error occurred during acquisition: {e}') - self.data['measurement_error'] = True - self.recover_timing_state(init_timing_state) - self.recover_magnets_state(init_magnets_state) - self.data['current_before'] = current_before - self.data['current_after'] = self.data.pop('stored_current') - self.data['trajx'] = self.data.pop('orbx') - self.data['trajy'] = self.data.pop('orby') From 67412f79569eacde8d41957b1521665b5c306557 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Apr 2024 17:20:06 -0300 Subject: [PATCH 007/144] COMMISS.TBT.WIP: trying to create magnets timing objects & minor formating fixes --- apsuite/commisslib/measure_tbt_data.py | 106 ++++++++++++++++++++----- 1 file changed, 86 insertions(+), 20 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b48abce1..d8465cd8 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -3,7 +3,7 @@ from scipy.optimize import curve_fit as _curve_fit import matplotlib.pyplot as _mplt import datetime as _datetime -from siriuspy.devices import PowerSupplyPU +from siriuspy.devices import PowerSupplyPU, Trigger from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ AcqBPMsSignalsParams as _AcqBPMsSignalsParams @@ -13,6 +13,7 @@ class TbTDataParams(_AcqBPMsSignalsParams): """.""" def __init__(self): + """.""" self.signals2acq = 'XYS' self.acq_rate = 'TbT' self.timeout = 40 # [s] @@ -31,8 +32,8 @@ def __init__(self): self._hkick = None # [urad] self._vkick = None # [urad] - def __str__(self): + """.""" stg = super().__str__() ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format stmp = '{0:26s} = {1:9} {2:s}\n'.format @@ -64,8 +65,13 @@ def vkick(self): def vkick(self, val): self._vkick = val + class MeasureTbTData(_AcqBPMsSignals): """.""" + + PINGERH_TRIGGER = 'SI-01SA:TI-InjDpKckr' + PINGERV_TRIGGER = 'SI-19C4:TI-PingV' + def __init__(self, filename='', isonline=False): """.""" self.params = TbTDataParams() @@ -73,10 +79,13 @@ def __init__(self, filename='', isonline=False): self._fname = filename def create_devices(self): + """.""" super().create_devices() self.devices['pingh'] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) + self.devices['trigpingh'] = Trigger(self.PINGERH_TRIGGER) self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) + self.devices['trigpingv'] = Trigger(self.PINGERV_TRIGGER) @property def fname(self): @@ -87,13 +96,43 @@ def fname(self): def fname(self, val): self._fname = val - def get_magnets_state(self): + def get_timing_state(self): + """.""" + state = super().get_timing_state() + trigpingh = self.devices['trigpingh'] + state['trigpingh_source'] = trigpingh.source + state['trigpingh_nrpulses'] = trigpingh.nr_pulses + state['trigpingh_delay'] = trigpingh.delay + trigpingv = self.devices['trigpingv'] + state['trigpingv_source'] = trigpingv.source + state['trigpingv_nrpulses'] = trigpingv.nr_pulses + state['trigpingv_delay'] = trigpingv.delay + + def recover_timing_state(self, state): + """.""" + return super().recover_timing_state(state) + + def prepare_timing(self, state=None): + """.""" + super().prepare_timing(state) + trigpingh = self.devices['trigpingh'] + trigpingh.source = state['trigpingh_source'] + trigpingh.nr_pulses = state['trigpingh_nrpulses'] + if trigpingh.delay is not None: + trigpingh.delay = state['trigpingh_delay'] + trigpingv = self.devices['trigpingv'] + trigpingv.source = state['trigpingv_source'] + trigpingv.nr_pulses = state['trigpingv_nrpulses'] + if trigpingv.delay is not None: + trigpingv.delay = state['trigpingv_delay'] + + def get_magnets_strength(self): """.""" pingh, pingv = self.devices['pingh'], self.devices['pingv'] hkick, vkick = pingh.strength, pingv.strength return hkick, vkick - def recover_magnets_state(self, hkick, vkick): + def recover_magnets_strength(self, hkick, vkick): """.""" self.set_magnets_state(hkick, vkick) @@ -116,7 +155,7 @@ def do_measurement(self): """.""" currinfo = self.devices['currinfo'] init_timing_state = self.get_timing_state() - init_magnets_state = self.get_magnets_state() + init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() self.prepare_timing() self.prepare_magnets() @@ -129,19 +168,36 @@ def do_measurement(self): print(f'An error occurred during acquisition: {e}') self.data['measurement_error'] = True self.recover_timing_state(init_timing_state) - self.recover_magnets_state(init_magnets_state) + self.recover_magnets_strength(init_magnets_strength) self.data['current_before'] = current_before self.data['current_after'] = self.data.pop('stored_current') self.data['trajx'] = self.data.pop('orbx') self.data['trajy'] = self.data.pop('orby') + def get_fname(self): + """.""" + hkick, vkick = self.params.hkick, self.params.vkick + tm = self.data['timestamp'] + fmt = '%Y-%m-%d-%H-%M-%S' + tmstp = _datetime.datetime.fromtimestamp(tm).strftime(fmt) + stg = f'tbt_hkick={hkick:3d}_vkick={vkick:3d}_urad_{tmstp}' + return stg + + class TbTDataAnalysis(_AcqBPMsSignals): """.""" def __init__(self, filename='', isonline=False): - """Analysis of betatron motion and linear optics using Turn-by-turn - data""" - super().__init__(isonline=isonline, ispost_mortem=False) + """Analysis of linear optics using Turn-by-turn data.""" + self.params = TbTDataParams() + self.isonline = isonline + self._ispost_mortem = False + + self._fname = filename + self._trajx, self._trajy = None, None + self._trajsum = None + + # load if fname, load method @property def fname(self): @@ -167,10 +223,11 @@ def trajy(self): return self._trajy @trajy.setter - def trajx(self, val): + def trajy(self, val): self._trajy = val def linear_optics_analysis(self): + """.""" raise NotImplementedError def harmonic_analysis(self): @@ -178,19 +235,22 @@ def harmonic_analysis(self): raise NotImplementedError def principal_components_analysis(self): + """.""" raise NotImplementedError def independent_component_analysis(self): + """.""" raise NotImplementedError def equilibrium_params_analysis(self): + """.""" raise NotImplementedError # plotting methods def plot_traj_spectrum(): + """.""" raise NotImplementedError - def _get_tune_guess(self, matrix): """.""" matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) @@ -200,8 +260,10 @@ def _get_tune_guess(self, matrix): return _np.mean(tune_peaks) # tune guess def _get_amplitudes_and_phases_guess(self, matrix, tune): - """Calculate initial guesses for harmonic TbT model as - Eqs. (5.2)-(5.4) of Ref. [2]""" + """Calculate initial amplitude & phase guesses for harmonic TbT model. + + Implements Eqs. (5.2)-(5.4) from Ref. [2] + """ N = matrix.shape[0] ilist = _np.arange(N) cos = _np.cos(2 * _np.pi * tune * ilist) @@ -214,12 +276,14 @@ def _get_amplitudes_and_phases_guess(self, matrix, tune): return amplitudes, phases def harmonic_tbt_model(self, ilist, amplitude, tune, phase): - """Harmonic motion model for positions seen at a given BPM""" + """Harmonic motion model for positions seen at a given BPM.""" return amplitude * _np.cos(2 * _np.pi * tune * ilist + phase) def harmonic_tbt_model_vectorized(self, ilist, amplitude, tune, phase): - """Harmonic motion model for positions seen at a given BPM""" - return amplitude[None, :] * _np.cos(2 * _np.pi * tune * ilist[:, None] + phase[None, :]) + """Harmonic motion model for positions seen at a given BPM.""" + model = amplitude[None, :] + model *= _np.cos(2 * _np.pi * tune * ilist[:, None] + phase[None, :]) + return model def fit_harmonic_model(self, matrix, amp_guesses, tune_guess, phase_guesses): @@ -239,16 +303,18 @@ def fit_harmonic_model(self, matrix, amp_guesses, ilist = _np.arange(matrix.shape[0]) params = _np.zeros((3, matrix.shape[-1])) for bpm_idx, bpm_data in enumerate(matrix.T): - p0=[amp_guesses[bpm_idx],tune_guess,phase_guesses[bpm_idx]] + p0 = [amp_guesses[bpm_idx], tune_guess, phase_guesses[bpm_idx]] popt, *_ = _curve_fit(f=self.harmonic_tbt_model, - xdata=ilist,ydata=bpm_data, + xdata=ilist, ydata=bpm_data, p0=p0) params[:, bpm_idx] = popt return params def calculate_betafunc_and_action(self, amplitudes, nominal_beta): - """Calculates beta function and betatron action as in Eq. (9) - of Ref. [1]""" + """Calculates beta function and betatron action. + + As in Eq. (9) of Ref. [1] + """ action = _np.sum(amplitudes**4) action /= _np.sum(amplitudes**2 * nominal_beta) beta = amplitudes**2/action From 55eab248f8225b9d84cad7440ae11163fd02f7ee Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Tue, 16 Apr 2024 09:35:28 -0300 Subject: [PATCH 008/144] COMMISS.TBT.ENH: add magnets timing properties --- apsuite/commisslib/measure_tbt_data.py | 86 +++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d8465cd8..f766b87c 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -32,19 +32,41 @@ def __init__(self): self._hkick = None # [urad] self._vkick = None # [urad] + self._trigpingh_delay = None + self._trigpingh_nrpulses = 1 + self._trigpingv_delay = None + self._trigpingv_nrpulses = 1 + def __str__(self): """.""" stg = super().__str__() ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format + dtmp = '{0:26s} = {1:9d} {2:s}\n'.format stmp = '{0:26s} = {1:9} {2:s}\n'.format if self.hkick is None: stg += stmp('hkick', 'same', '(current value will not be changed)') else: stg += ftmp('hkick', self.hkick, '[urad]') - if self.hkick is None: + dly = self.trigpingh_delay + if dly is None: + stg += stmp('trigpingh_delay', + 'same', + '(current value will not be changed)') + else: + stg += ftmp('trigpingh_delay', dly, '[us]') + stg += dtmp('trigpingh_nrpulses', self.trigpingh_nrpulses, '') + if self.vkick is None: stg += stmp('vkick', 'same', '(current value will not be changed)') else: stg += ftmp('vkick', self.vkick, '[urad]') + dly = self.trigpingv_delay + if dly is None: + stg += stmp('trigpingv_delay', + 'same', + '(current value will not be changed)') + else: + stg += ftmp('trigpingv_delay', dly, '[us]') + stg += dtmp('trigpingv_nrpulses', self.trigpingv_nrpulses, '') return stg @property @@ -65,6 +87,42 @@ def vkick(self): def vkick(self, val): self._vkick = val + @property + def trigpingh_delay(self): + """.""" + return self._trigpingh_delay + + @trigpingh_delay.setter + def trigpingh_delay(self, val): + self._trigpingh_delay = val + + @property + def trigpingh_nrpulses(self): + """.""" + return self._trigpingh_nrpulses + + @trigpingh_nrpulses.setter + def trigpingh_nrpulses(self, val): + self._trigpingh_nrpulses = val + + @property + def trigpingv_delay(self): + """.""" + return self._trigpingh_delay + + @trigpingv_delay.setter + def trigpingv_delay(self, val): + self._trigpingh_delay = val + + @property + def trigpingv_nrpulses(self): + """.""" + return self._trigpingv_nrpulses + + @trigpingv_nrpulses.setter + def trigpingv_nrpulses(self, val): + self._trigpingv_nrpulses = val + class MeasureTbTData(_AcqBPMsSignals): """.""" @@ -114,17 +172,25 @@ def recover_timing_state(self, state): def prepare_timing(self, state=None): """.""" - super().prepare_timing(state) + super().prepare_timing(state) # BPM trigger timing + # magnets trigger timing below + prms = self.params + trigpingh = self.devices['trigpingh'] - trigpingh.source = state['trigpingh_source'] - trigpingh.nr_pulses = state['trigpingh_nrpulses'] - if trigpingh.delay is not None: - trigpingh.delay = state['trigpingh_delay'] + trigpingh.source = state.get('trigpingh_source', prms.timing_event) + trigpingh.nr_pulses = state.get('trigpingh_nrpulses', + prms.trigpingh_nrpulses) + dly = state.get('trigpingh_delay', prms.trigpingh_delay) + if dly is not None: + trigpingh.delay = dly + trigpingv = self.devices['trigpingv'] - trigpingv.source = state['trigpingv_source'] - trigpingv.nr_pulses = state['trigpingv_nrpulses'] - if trigpingv.delay is not None: - trigpingv.delay = state['trigpingv_delay'] + trigpingv.source = state.get('trigpingv_source', prms.timing_event) + trigpingh.nr_pulses = state.get('trigpingv_nrpulses', + prms.trigpingv_nrpulses) + dly = state.get('trigpingv_delay', prms.trigpingv_delay) + if dly is not None: + trigpingv.delay = dly def get_magnets_strength(self): """.""" From 8eef2025340de5404b9572f50a49ab9517c6e144 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 13:33:43 -0300 Subject: [PATCH 009/144] COMMISS.TBT.ENH: remove unecessary setters for properties --- apsuite/commisslib/measure_tbt_data.py | 66 +++----------------------- 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f766b87c..4f044d0a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -29,13 +29,13 @@ def __init__(self): self.event_delay = None self.do_pulse_evg = False - self._hkick = None # [urad] - self._vkick = None # [urad] + self.hkick = None # [urad] + self.vkick = None # [urad] - self._trigpingh_delay = None - self._trigpingh_nrpulses = 1 - self._trigpingv_delay = None - self._trigpingv_nrpulses = 1 + self.trigpingh_delay = None + self.trigpingh_nrpulses = 1 + self.trigpingv_delay = None + self.trigpingv_nrpulses = 1 def __str__(self): """.""" @@ -69,60 +69,6 @@ def __str__(self): stg += dtmp('trigpingv_nrpulses', self.trigpingv_nrpulses, '') return stg - @property - def hkick(self): - """.""" - return self._hkick - - @hkick.setter - def hkick(self, val): - self._hkick = val - - @property - def vkick(self): - """.""" - return self._vkick - - @vkick.setter - def vkick(self, val): - self._vkick = val - - @property - def trigpingh_delay(self): - """.""" - return self._trigpingh_delay - - @trigpingh_delay.setter - def trigpingh_delay(self, val): - self._trigpingh_delay = val - - @property - def trigpingh_nrpulses(self): - """.""" - return self._trigpingh_nrpulses - - @trigpingh_nrpulses.setter - def trigpingh_nrpulses(self, val): - self._trigpingh_nrpulses = val - - @property - def trigpingv_delay(self): - """.""" - return self._trigpingh_delay - - @trigpingv_delay.setter - def trigpingv_delay(self, val): - self._trigpingh_delay = val - - @property - def trigpingv_nrpulses(self): - """.""" - return self._trigpingv_nrpulses - - @trigpingv_nrpulses.setter - def trigpingv_nrpulses(self, val): - self._trigpingv_nrpulses = val - class MeasureTbTData(_AcqBPMsSignals): """.""" From aac09db5a2f3e15bd07a6d27780b0f24e94fbc8e Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 13:43:27 -0300 Subject: [PATCH 010/144] COMMIS.TBT.ENH: correct initialization of derived classes, remove repeated initialization of parameters --- apsuite/commisslib/measure_tbt_data.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 4f044d0a..b038bcb6 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -14,24 +14,17 @@ class TbTDataParams(_AcqBPMsSignalsParams): def __init__(self): """.""" + super().__init__() self.signals2acq = 'XYS' self.acq_rate = 'TbT' - self.timeout = 40 # [s] - self.nrpoints_before = 100 self.nrpoints_after = 2000 - self.acq_repeat = False - self.trigbpm_delay = None - self.trigbpm_nrpulses = 1 self.timing_event = 'Linac' self.event_mode = 'Injection' - self.event_delay = None - self.do_pulse_evg = False self.hkick = None # [urad] self.vkick = None # [urad] - self.trigpingh_delay = None self.trigpingh_nrpulses = 1 self.trigpingv_delay = None @@ -78,8 +71,8 @@ class MeasureTbTData(_AcqBPMsSignals): def __init__(self, filename='', isonline=False): """.""" + super().__init__(isonline=isonline, ispost_mortem=False) self.params = TbTDataParams() - self.isonline = isonline self._fname = filename def create_devices(self): From 792ad625a5c7afead82e93740423307fdd17d243 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 13:44:36 -0300 Subject: [PATCH 011/144] COMMIS.TBT.ENH: remove fname property from meas class --- apsuite/commisslib/measure_tbt_data.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b038bcb6..fc4fa67e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -69,11 +69,10 @@ class MeasureTbTData(_AcqBPMsSignals): PINGERH_TRIGGER = 'SI-01SA:TI-InjDpKckr' PINGERV_TRIGGER = 'SI-19C4:TI-PingV' - def __init__(self, filename='', isonline=False): + def __init__(self, isonline=False): """.""" super().__init__(isonline=isonline, ispost_mortem=False) self.params = TbTDataParams() - self._fname = filename def create_devices(self): """.""" @@ -84,15 +83,6 @@ def create_devices(self): self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) self.devices['trigpingv'] = Trigger(self.PINGERV_TRIGGER) - @property - def fname(self): - """.""" - return self._fname - - @fname.setter - def fname(self, val): - self._fname = val - def get_timing_state(self): """.""" state = super().get_timing_state() From 79824bb756c361b89a67cb82e1e5c76def5aba96 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 13:49:31 -0300 Subject: [PATCH 012/144] COMMISS.TBT.BUG: fix `get_timing_state` s return --- apsuite/commisslib/measure_tbt_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index fc4fa67e..15b55d02 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -94,6 +94,7 @@ def get_timing_state(self): state['trigpingv_source'] = trigpingv.source state['trigpingv_nrpulses'] = trigpingv.nr_pulses state['trigpingv_delay'] = trigpingv.delay + return state def recover_timing_state(self, state): """.""" From 8328ef32dde802339f176f60596ab3ee1a5bef0e Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:03:33 -0300 Subject: [PATCH 013/144] COMMISS.TBT.ENH: keep standard `orbx` & `orby` keys --- apsuite/commisslib/measure_tbt_data.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 15b55d02..9e42728d 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -105,7 +105,6 @@ def prepare_timing(self, state=None): super().prepare_timing(state) # BPM trigger timing # magnets trigger timing below prms = self.params - trigpingh = self.devices['trigpingh'] trigpingh.source = state.get('trigpingh_source', prms.timing_event) trigpingh.nr_pulses = state.get('trigpingh_nrpulses', @@ -167,8 +166,6 @@ def do_measurement(self): self.recover_magnets_strength(init_magnets_strength) self.data['current_before'] = current_before self.data['current_after'] = self.data.pop('stored_current') - self.data['trajx'] = self.data.pop('orbx') - self.data['trajy'] = self.data.pop('orby') def get_fname(self): """.""" From a4fefd34b232a905ec020f033fd45b2fe3c1a2b9 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:07:20 -0300 Subject: [PATCH 014/144] COMMISS.TBT.ENH: minor changes in `get_default_fname` --- apsuite/commisslib/measure_tbt_data.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9e42728d..31ce3664 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -167,13 +167,14 @@ def do_measurement(self): self.data['current_before'] = current_before self.data['current_after'] = self.data.pop('stored_current') - def get_fname(self): + def get_default_fname(self): """.""" hkick, vkick = self.params.hkick, self.params.vkick tm = self.data['timestamp'] fmt = '%Y-%m-%d-%H-%M-%S' tmstp = _datetime.datetime.fromtimestamp(tm).strftime(fmt) - stg = f'tbt_hkick={hkick:3d}_vkick={vkick:3d}_urad_{tmstp}' + stg = f'hkick_{int(round(hkick)):3d}_vkick_{int(round(vkick)):3d}' + stg += f'_urad_{tmstp}' return stg From 282a6921fad35c2ae31126a4113ecde6cf2d2e41 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:40:55 -0300 Subject: [PATCH 015/144] COMMISS.TBT.ENH: simplify magnets preparation during meas --- apsuite/commisslib/measure_tbt_data.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 31ce3664..cb394426 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -123,28 +123,19 @@ def prepare_timing(self, state=None): def get_magnets_strength(self): """.""" - pingh, pingv = self.devices['pingh'], self.devices['pingv'] - hkick, vkick = pingh.strength, pingv.strength - return hkick, vkick - - def recover_magnets_strength(self, hkick, vkick): - """.""" - self.set_magnets_state(hkick, vkick) + return self.devices['pingh'].strength, self.devices['pingv'].strength - def set_magnets_state(self, hkick, vkick): + def set_magnets_strength(self, hkick=None, vkick=None): """.""" pingh, pingv = self.devices['pingh'], self.devices['pingv'] if hkick is not None: pingh.strength = hkick + else: + pingh.strength = self.params.hkick / 1e6 if vkick is not None: pingv.strength = vkick - - def prepare_magnets(self): - """.""" - hkick, vkick = self.params.hkick, self.params.vkick - hkick = hkick/1e6 if hkick is not None else None - vkick = vkick/1e6 if vkick is not None else None - self.set_magnets_state(hkick, vkick) + else: + pingv.strength = self.params.vkick / 1e6 def do_measurement(self): """.""" @@ -153,7 +144,7 @@ def do_measurement(self): init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() self.prepare_timing() - self.prepare_magnets() + self.set_magnets_strength() # gets strengths from params self.data['measurement_error'] = False # error flag try: self.acquire_data() # BPMs signals + relevant info are acquired @@ -163,7 +154,7 @@ def do_measurement(self): print(f'An error occurred during acquisition: {e}') self.data['measurement_error'] = True self.recover_timing_state(init_timing_state) - self.recover_magnets_strength(init_magnets_strength) + self.set_magnets_strength(init_magnets_strength) # restore strengths self.data['current_before'] = current_before self.data['current_after'] = self.data.pop('stored_current') From 021c505e1e459465d7531fbc5c1823220d4121ad Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:47:34 -0300 Subject: [PATCH 016/144] COMMIS.ACQBPMS.ENH: remove `do_pulse_evg` and related function --- apsuite/commisslib/meas_bpms_signals.py | 26 ++----------------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index c6ff7286..554e4d29 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -4,7 +4,7 @@ import numpy as _np import scipy.fft as _sp_fft import scipy.signal as _sp_sig -from siriuspy.devices import CurrInfoSI, Event, EVG, FamBPMs, RFGen, Trigger, \ +from siriuspy.devices import CurrInfoSI, Event, FamBPMs, RFGen, Trigger, \ Tune from siriuspy.search import HLTimeSearch as _HLTimeSearch @@ -19,7 +19,6 @@ def __init__(self): """.""" self.trigbpm_delay = None self.trigbpm_nrpulses = 1 - self.do_pulse_evg = True self._timing_event = 'Study' self.event_delay = None self.event_mode = 'External' @@ -43,7 +42,6 @@ def __str__(self): else: stg += ftmp('trigbpm_delay', dly, '[us]') stg += dtmp('trigbpm_nrpulses', self.trigbpm_nrpulses, '') - stg += stmp('do_pulse_evg', str(self.do_pulse_evg), '') stg += stmp('timing_event', self.timing_event, '') dly = self.event_delay if dly is None: @@ -131,7 +129,6 @@ def create_devices(self): trigname = self.PSM_TRIGGER self.devices['trigbpm'] = Trigger(trigname) self.devices['evt_study'] = Event('Study') - self.devices['evg'] = EVG() self.devices['rfgen'] = RFGen() def get_timing_state(self): @@ -142,8 +139,6 @@ def get_timing_state(self): state['trigbpm_source'] = trigbpm.source state['trigbpm_nrpulses'] = trigbpm.nr_pulses state['trigbpm_delay'] = trigbpm.delay - if self.params.do_pulse_evg: - state['evg_nrpulses'] = self.devices['evg'].nrpulses evt = self._get_event(self.params.timing_event) if evt is not None: @@ -176,23 +171,6 @@ def prepare_timing(self, state=None): evt.delay = dly evt.mode = state.get('evt_mode', self.params.event_mode) - nrpul = 1 if self.params.do_pulse_evg else None - nrpul = state.get('evg_nrpulses', nrpul) - if nrpul is not None: - evg = self.devices['evg'] - evg.set_nrpulses(nrpul) - evg.cmd_update_events() - - def trigger_timing_signal(self): - """.""" - if not self.params.do_pulse_evg: - return - evt = self._get_event(self.params.timing_event) - if evt is not None and evt.mode_str == 'External': - evt.cmd_external_trigger() - else: - self.devices['evg'].cmd_turn_on_injection() - def prepare_bpms_acquisition(self): """.""" fambpms = self.devices['fambpms'] @@ -214,7 +192,7 @@ def acquire_data(self): print(tag + ' is not ready for acquisition.') fambpms.reset_mturn_initial_state() - self.trigger_timing_signal() + # user must trigger timing event time0 = _time.time() ret = fambpms.wait_update_mturn(timeout=self.params.timeout) From 6419c3c47c3cbce1c9d392d043d06a91abb63277 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:49:09 -0300 Subject: [PATCH 017/144] COMMIS.ACQBPMS.ENH: add timing state to saved data during acquisition --- apsuite/commisslib/meas_bpms_signals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 554e4d29..36391f4b 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -250,6 +250,7 @@ def get_data(self): data['switching_frequency'] = fbpms.get_switching_frequency(rf_freq) data['tunex_enable'] = tune.enablex data['tuney_enable'] = tune.enabley + data['timing_state'] = self.get_timing_state() return data @staticmethod From 53405764d58309cbcbdff86d346dfd0c8b73eb6b Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 16 Apr 2024 14:52:47 -0300 Subject: [PATCH 018/144] COMMIS.TBT.ENH: remove redundant function --- apsuite/commisslib/measure_tbt_data.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index cb394426..1276502a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -96,10 +96,6 @@ def get_timing_state(self): state['trigpingv_delay'] = trigpingv.delay return state - def recover_timing_state(self, state): - """.""" - return super().recover_timing_state(state) - def prepare_timing(self, state=None): """.""" super().prepare_timing(state) # BPM trigger timing From 14651efa0f54ddc8f3ee84e461befb318d3b0161 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Tue, 16 Apr 2024 17:17:35 -0300 Subject: [PATCH 019/144] COMMISS.TBT.ENH: add selection of magnets to pulse & stregnths setting temporization --- apsuite/commisslib/measure_tbt_data.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1276502a..1fdcab10 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -3,6 +3,7 @@ from scipy.optimize import curve_fit as _curve_fit import matplotlib.pyplot as _mplt import datetime as _datetime +import time as _time from siriuspy.devices import PowerSupplyPU, Trigger from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ @@ -23,12 +24,13 @@ def __init__(self): self.timing_event = 'Linac' self.event_mode = 'Injection' + self.pingers2kick = 'H' # 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None - self.trigpingh_nrpulses = 1 + self.trigpingh_nrpulses = 1 if 'h' in self.pingers2kick.lower() else 0 self.trigpingv_delay = None - self.trigpingv_nrpulses = 1 + self.trigpingv_nrpulses = 1 if 'v' in self.pingers2kick.lower() else 0 def __str__(self): """.""" @@ -77,11 +79,15 @@ def __init__(self, isonline=False): def create_devices(self): """.""" super().create_devices() - self.devices['pingh'] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) - self.devices['trigpingh'] = Trigger(self.PINGERH_TRIGGER) - self.devices['pinghv'] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) - self.devices['trigpingv'] = Trigger(self.PINGERV_TRIGGER) + for pinger in self.pingers2kick: + if pinger.lower == 'h': + self.devices['pingh'] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) + self.devices['trigpingh'] = Trigger(self.PINGERH_TRIGGER) + if pinger.lower() == 'v': + self.devices['pinghv'] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_PING_V) + self.devices['trigpingv'] = Trigger(self.PINGERV_TRIGGER) def get_timing_state(self): """.""" @@ -128,10 +134,12 @@ def set_magnets_strength(self, hkick=None, vkick=None): pingh.strength = hkick else: pingh.strength = self.params.hkick / 1e6 + _time.sleep(0.5) if vkick is not None: pingv.strength = vkick else: pingv.strength = self.params.vkick / 1e6 + _time.sleep(0.5) def do_measurement(self): """.""" From 274822a4ee3c3f50f594c925588a803365d0ce74 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 10:29:56 -0300 Subject: [PATCH 020/144] COMMISS.TBT.ENH: check for valid `pingers2kick' settings --- apsuite/commisslib/measure_tbt_data.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1fdcab10..b5b847b7 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -24,7 +24,7 @@ def __init__(self): self.timing_event = 'Linac' self.event_mode = 'Injection' - self.pingers2kick = 'H' # 'H', 'V' or 'HV' + self._pingers2kick = 'H' # 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None @@ -64,6 +64,18 @@ def __str__(self): stg += dtmp('trigpingv_nrpulses', self.trigpingv_nrpulses, '') return stg + @property + def pingers2kick(self): + """.""" + return self._pingers2kick + + @pingers2kick.setter + def pingers2kick(self, value): + if value.lower() not in "hv": + raise ValueError('Invalid pinger keyword. Set "H", "V" or "HV"') + else: + self._pingers2kick = value + class MeasureTbTData(_AcqBPMsSignals): """.""" From cb1586a5327c84be7c3bebbb69938f4d4b3b8050 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 13:58:10 -0300 Subject: [PATCH 021/144] COMMISS.TBT.ENH: handle `None` type in `pingers2kick` & improve the logic of magnets strengths setting --- apsuite/commisslib/measure_tbt_data.py | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b5b847b7..529c28c1 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -24,13 +24,14 @@ def __init__(self): self.timing_event = 'Linac' self.event_mode = 'Injection' - self._pingers2kick = 'H' # 'H', 'V' or 'HV' + self._pingers2kick = None # 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None - self.trigpingh_nrpulses = 1 if 'h' in self.pingers2kick.lower() else 0 self.trigpingv_delay = None - self.trigpingv_nrpulses = 1 if 'v' in self.pingers2kick.lower() else 0 + pingers2kick = str(self._pingers2kick).lower() + self.trigpingh_nrpulses = 1 if 'h' in pingers2kick else 0 + self.trigpingv_nrpulses = 1 if 'v' in pingers2kick else 0 def __str__(self): """.""" @@ -38,6 +39,7 @@ def __str__(self): ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format dtmp = '{0:26s} = {1:9d} {2:s}\n'.format stmp = '{0:26s} = {1:9} {2:s}\n'.format + stg += stmp('pingers2kick', self.pingers2kick, '') if self.hkick is None: stg += stmp('hkick', 'same', '(current value will not be changed)') else: @@ -71,8 +73,8 @@ def pingers2kick(self): @pingers2kick.setter def pingers2kick(self, value): - if value.lower() not in "hv": - raise ValueError('Invalid pinger keyword. Set "H", "V" or "HV"') + if (value is not None) and (value.lower() not in "hv"): + raise ValueError('Invalid keyword. Set "None", "H", "V" or "HV"') else: self._pingers2kick = value @@ -142,15 +144,12 @@ def get_magnets_strength(self): def set_magnets_strength(self, hkick=None, vkick=None): """.""" pingh, pingv = self.devices['pingh'], self.devices['pingv'] - if hkick is not None: - pingh.strength = hkick - else: - pingh.strength = self.params.hkick / 1e6 - _time.sleep(0.5) - if vkick is not None: - pingv.strength = vkick - else: - pingv.strength = self.params.vkick / 1e6 + if hkick is None: + hkick = self.params.hkick / 1e3 # [urad] -> [mrad] + pingh.strength = hkick + if vkick is None: + vkick = self.params.vkick / 1e3 # [urad] -> [mrad] + pingv.strength = vkick _time.sleep(0.5) def do_measurement(self): From ad96347ce73d34005adf1b539f63e8facab230ce Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 14:32:07 -0300 Subject: [PATCH 022/144] COMMIS.TBT.ENH: fix formatting --- apsuite/commisslib/measure_tbt_data.py | 162 +++++++++++++------------ 1 file changed, 87 insertions(+), 75 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 529c28c1..62f12795 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1,9 +1,11 @@ """.""" -import numpy as _np -from scipy.optimize import curve_fit as _curve_fit -import matplotlib.pyplot as _mplt + import datetime as _datetime import time as _time + +import matplotlib.pyplot as _mplt +import numpy as _np +from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU, Trigger from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ @@ -16,13 +18,13 @@ class TbTDataParams(_AcqBPMsSignalsParams): def __init__(self): """.""" super().__init__() - self.signals2acq = 'XYS' - self.acq_rate = 'TbT' + self.signals2acq = "XYS" + self.acq_rate = "TbT" self.nrpoints_before = 100 self.nrpoints_after = 2000 - self.timing_event = 'Linac' - self.event_mode = 'Injection' + self.timing_event = "Linac" + self.event_mode = "Injection" self._pingers2kick = None # 'H', 'V' or 'HV' self.hkick = None # [urad] @@ -30,40 +32,44 @@ def __init__(self): self.trigpingh_delay = None self.trigpingv_delay = None pingers2kick = str(self._pingers2kick).lower() - self.trigpingh_nrpulses = 1 if 'h' in pingers2kick else 0 - self.trigpingv_nrpulses = 1 if 'v' in pingers2kick else 0 + self.trigpingh_nrpulses = 1 if "h" in pingers2kick else 0 + self.trigpingv_nrpulses = 1 if "v" in pingers2kick else 0 def __str__(self): """.""" stg = super().__str__() - ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format - dtmp = '{0:26s} = {1:9d} {2:s}\n'.format - stmp = '{0:26s} = {1:9} {2:s}\n'.format - stg += stmp('pingers2kick', self.pingers2kick, '') + ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format + dtmp = "{0:26s} = {1:9d} {2:s}\n".format + stmp = "{0:26s} = {1:9} {2:s}\n".format + stg += stmp("pingers2kick", self.pingers2kick, "") if self.hkick is None: - stg += stmp('hkick', 'same', '(current value will not be changed)') + stg += stmp("hkick", "same", "(current value will not be changed)") else: - stg += ftmp('hkick', self.hkick, '[urad]') + stg += ftmp("hkick", self.hkick, "[urad]") dly = self.trigpingh_delay if dly is None: - stg += stmp('trigpingh_delay', - 'same', - '(current value will not be changed)') + stg += stmp( + "trigpingh_delay", + "same", + "(current value will not be changed)", + ) else: - stg += ftmp('trigpingh_delay', dly, '[us]') - stg += dtmp('trigpingh_nrpulses', self.trigpingh_nrpulses, '') + stg += ftmp("trigpingh_delay", dly, "[us]") + stg += dtmp("trigpingh_nrpulses", self.trigpingh_nrpulses, "") if self.vkick is None: - stg += stmp('vkick', 'same', '(current value will not be changed)') + stg += stmp("vkick", "same", "(current value will not be changed)") else: - stg += ftmp('vkick', self.vkick, '[urad]') + stg += ftmp("vkick", self.vkick, "[urad]") dly = self.trigpingv_delay if dly is None: - stg += stmp('trigpingv_delay', - 'same', - '(current value will not be changed)') + stg += stmp( + "trigpingv_delay", + "same", + "(current value will not be changed)", + ) else: - stg += ftmp('trigpingv_delay', dly, '[us]') - stg += dtmp('trigpingv_nrpulses', self.trigpingv_nrpulses, '') + stg += ftmp("trigpingv_delay", dly, "[us]") + stg += dtmp("trigpingv_nrpulses", self.trigpingv_nrpulses, "") return stg @property @@ -82,8 +88,8 @@ def pingers2kick(self, value): class MeasureTbTData(_AcqBPMsSignals): """.""" - PINGERH_TRIGGER = 'SI-01SA:TI-InjDpKckr' - PINGERV_TRIGGER = 'SI-19C4:TI-PingV' + PINGERH_TRIGGER = "SI-01SA:TI-InjDpKckr" + PINGERV_TRIGGER = "SI-19C4:TI-PingV" def __init__(self, isonline=False): """.""" @@ -94,26 +100,28 @@ def create_devices(self): """.""" super().create_devices() for pinger in self.pingers2kick: - if pinger.lower == 'h': - self.devices['pingh'] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_INJ_DPKCKR) - self.devices['trigpingh'] = Trigger(self.PINGERH_TRIGGER) - if pinger.lower() == 'v': - self.devices['pinghv'] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_PING_V) - self.devices['trigpingv'] = Trigger(self.PINGERV_TRIGGER) + if pinger.lower == "h": + self.devices["pingh"] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_INJ_DPKCKR + ) + self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) + if pinger.lower() == "v": + self.devices["pinghv"] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_PING_V + ) + self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) def get_timing_state(self): """.""" state = super().get_timing_state() - trigpingh = self.devices['trigpingh'] - state['trigpingh_source'] = trigpingh.source - state['trigpingh_nrpulses'] = trigpingh.nr_pulses - state['trigpingh_delay'] = trigpingh.delay - trigpingv = self.devices['trigpingv'] - state['trigpingv_source'] = trigpingv.source - state['trigpingv_nrpulses'] = trigpingv.nr_pulses - state['trigpingv_delay'] = trigpingv.delay + trigpingh = self.devices["trigpingh"] + state["trigpingh_source"] = trigpingh.source + state["trigpingh_nrpulses"] = trigpingh.nr_pulses + state["trigpingh_delay"] = trigpingh.delay + trigpingv = self.devices["trigpingv"] + state["trigpingv_source"] = trigpingv.source + state["trigpingv_nrpulses"] = trigpingv.nr_pulses + state["trigpingv_delay"] = trigpingv.delay return state def prepare_timing(self, state=None): @@ -121,29 +129,31 @@ def prepare_timing(self, state=None): super().prepare_timing(state) # BPM trigger timing # magnets trigger timing below prms = self.params - trigpingh = self.devices['trigpingh'] - trigpingh.source = state.get('trigpingh_source', prms.timing_event) - trigpingh.nr_pulses = state.get('trigpingh_nrpulses', - prms.trigpingh_nrpulses) - dly = state.get('trigpingh_delay', prms.trigpingh_delay) + trigpingh = self.devices["trigpingh"] + trigpingh.source = state.get("trigpingh_source", prms.timing_event) + trigpingh.nr_pulses = state.get( + "trigpingh_nrpulses", prms.trigpingh_nrpulses + ) + dly = state.get("trigpingh_delay", prms.trigpingh_delay) if dly is not None: trigpingh.delay = dly - trigpingv = self.devices['trigpingv'] - trigpingv.source = state.get('trigpingv_source', prms.timing_event) - trigpingh.nr_pulses = state.get('trigpingv_nrpulses', - prms.trigpingv_nrpulses) - dly = state.get('trigpingv_delay', prms.trigpingv_delay) + trigpingv = self.devices["trigpingv"] + trigpingv.source = state.get("trigpingv_source", prms.timing_event) + trigpingh.nr_pulses = state.get( + "trigpingv_nrpulses", prms.trigpingv_nrpulses + ) + dly = state.get("trigpingv_delay", prms.trigpingv_delay) if dly is not None: trigpingv.delay = dly def get_magnets_strength(self): """.""" - return self.devices['pingh'].strength, self.devices['pingv'].strength + return self.devices["pingh"].strength, self.devices["pingv"].strength def set_magnets_strength(self, hkick=None, vkick=None): """.""" - pingh, pingv = self.devices['pingh'], self.devices['pingv'] + pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is None: hkick = self.params.hkick / 1e3 # [urad] -> [mrad] pingh.strength = hkick @@ -154,24 +164,25 @@ def set_magnets_strength(self, hkick=None, vkick=None): def do_measurement(self): """.""" - currinfo = self.devices['currinfo'] + currinfo = self.devices["currinfo"] init_timing_state = self.get_timing_state() init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() self.prepare_timing() self.set_magnets_strength() # gets strengths from params - self.data['measurement_error'] = False # error flag + self.data["measurement_error"] = False # error flag try: - self.acquire_data() # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. + self.acquire_data() + # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. except Exception as e: - print(f'An error occurred during acquisition: {e}') - self.data['measurement_error'] = True + print(f"An error occurred during acquisition: {e}") + self.data["measurement_error"] = True self.recover_timing_state(init_timing_state) self.set_magnets_strength(init_magnets_strength) # restore strengths - self.data['current_before'] = current_before - self.data['current_after'] = self.data.pop('stored_current') + self.data["current_before"] = current_before + self.data["current_after"] = self.data.pop("stored_current") def get_default_fname(self): """.""" @@ -187,7 +198,7 @@ def get_default_fname(self): class TbTDataAnalysis(_AcqBPMsSignals): """.""" - def __init__(self, filename='', isonline=False): + def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" self.params = TbTDataParams() self.isonline = isonline @@ -269,8 +280,8 @@ def _get_amplitudes_and_phases_guess(self, matrix, tune): cos = _np.cos(2 * _np.pi * tune * ilist) sin = _np.sin(2 * _np.pi * tune * ilist) C, S = _np.dot(cos, matrix), _np.dot(sin, matrix) - C *= 2/N - S *= 2/N + C *= 2 / N + S *= 2 / N amplitudes = _np.sqrt(C**2 + S**2) phases = _np.unwrap(_np.arctan2(C, S)) return amplitudes, phases @@ -285,8 +296,9 @@ def harmonic_tbt_model_vectorized(self, ilist, amplitude, tune, phase): model *= _np.cos(2 * _np.pi * tune * ilist[:, None] + phase[None, :]) return model - def fit_harmonic_model(self, matrix, amp_guesses, - tune_guess, phase_guesses): + def fit_harmonic_model( + self, matrix, amp_guesses, tune_guess, phase_guesses + ): """Fits harmonic TbT model to data. Args: @@ -304,9 +316,9 @@ def fit_harmonic_model(self, matrix, amp_guesses, params = _np.zeros((3, matrix.shape[-1])) for bpm_idx, bpm_data in enumerate(matrix.T): p0 = [amp_guesses[bpm_idx], tune_guess, phase_guesses[bpm_idx]] - popt, *_ = _curve_fit(f=self.harmonic_tbt_model, - xdata=ilist, ydata=bpm_data, - p0=p0) + popt, *_ = _curve_fit( + f=self.harmonic_tbt_model, xdata=ilist, ydata=bpm_data, p0=p0 + ) params[:, bpm_idx] = popt return params @@ -317,5 +329,5 @@ def calculate_betafunc_and_action(self, amplitudes, nominal_beta): """ action = _np.sum(amplitudes**4) action /= _np.sum(amplitudes**2 * nominal_beta) - beta = amplitudes**2/action + beta = amplitudes**2 / action return beta, action From 099cb2a06c26f80cfaca6ee2080aabc0fd7702f0 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 14:34:10 -0300 Subject: [PATCH 023/144] COMMISS.TBT.ENH: add prefix for kicked beam data, the acq rate and info about which magnets were pulsed in `get_default_fname` --- apsuite/commisslib/measure_tbt_data.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 62f12795..bb9f3a4c 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -186,12 +186,19 @@ def do_measurement(self): def get_default_fname(self): """.""" - hkick, vkick = self.params.hkick, self.params.vkick - tm = self.data['timestamp'] - fmt = '%Y-%m-%d-%H-%M-%S' + prms = self.params + stg = 'kicked_data' + stg += f'_{prms.acq_rate}_rate' + hkick, vkick = prms.hkick, prms.vkick + pingers2kick = prms.pingers2kick + if pingers2kick is not None: + for plane in pingers2kick: + kick = hkick if plane == 'h' else vkick + stg += f"{plane}kick_{int(round(kick)):3d}_urad" + tm = self.data["timestamp"] + fmt = "%Y-%m-%d-%H-%M-%S" tmstp = _datetime.datetime.fromtimestamp(tm).strftime(fmt) - stg = f'hkick_{int(round(hkick)):3d}_vkick_{int(round(vkick)):3d}' - stg += f'_urad_{tmstp}' + stg += f"{tmstp}" return stg From 1cd811b1de5e2e699cb5948c33365249ea58f84b Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 14:49:21 -0300 Subject: [PATCH 024/144] COMMIS.TBT.ENH: remove unecessary initializations and properties. --- apsuite/commisslib/measure_tbt_data.py | 40 ++------------------------ 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index bb9f3a4c..61b0c657 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -202,47 +202,13 @@ def get_default_fname(self): return stg -class TbTDataAnalysis(_AcqBPMsSignals): +class TbTDataAnalysis(MeasureTbTData): """.""" def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" - self.params = TbTDataParams() - self.isonline = isonline - self._ispost_mortem = False - - self._fname = filename - self._trajx, self._trajy = None, None - self._trajsum = None - - # load if fname, load method - - @property - def fname(self): - """.""" - return self._fname - - @fname.setter - def fname(self, val): - self._fname = val - - @property - def trajx(self): - """.""" - return self._trajx - - @trajx.setter - def trajx(self, val): - self._trajx = val - - @property - def trajy(self): - """.""" - return self._trajy - - @trajy.setter - def trajy(self, val): - self._trajy = val + super().__init__(isonline=isonline) + self.fname = filename def linear_optics_analysis(self): """.""" From 6bb84226b95aacb7966a1b19a77d41dbb4a1ea87 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 15:41:32 -0300 Subject: [PATCH 025/144] COMMISS.TBT.BUG: fix pingh trigger device creation --- apsuite/commisslib/measure_tbt_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 61b0c657..66d7ad47 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -104,7 +104,7 @@ def create_devices(self): self.devices["pingh"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_INJ_DPKCKR ) - self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) + self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) if pinger.lower() == "v": self.devices["pinghv"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_PING_V From 84774403063637e6ca2bbf9222252f13d439f8e2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 15:42:54 -0300 Subject: [PATCH 026/144] COMMISS.TBT.ENH: wait magnets ramp during strength setting --- apsuite/commisslib/measure_tbt_data.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 66d7ad47..42feb24e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -151,16 +151,24 @@ def get_magnets_strength(self): """.""" return self.devices["pingh"].strength, self.devices["pingv"].strength - def set_magnets_strength(self, hkick=None, vkick=None): + def set_magnets_strength( + self, hkick=None, vkick=None, magnets_wait_time=None + ): """.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is None: hkick = self.params.hkick / 1e3 # [urad] -> [mrad] - pingh.strength = hkick + pingh.set_strength(hkick, tol=0.1 * hkick, timeout=0, wait_mon=False) if vkick is None: vkick = self.params.vkick / 1e3 # [urad] -> [mrad] - pingv.strength = vkick - _time.sleep(0.5) + pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) + # wait magnets ramp + pingh.set_strength( + hkick, tol=0.05 * hkick, timeout=magnets_wait_time, wait_mon=False + ) + pingv.set_strength( + vkick, tol=0.05 * vkick, timeout=magnets_wait_time, wait_mon=False + ) def do_measurement(self): """.""" From b1f7ca98630c91671e41209a2ed5f92c4152ae8e Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 15:43:19 -0300 Subject: [PATCH 027/144] COMMISS.TBT.ENH: formatting fix --- apsuite/commisslib/measure_tbt_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 42feb24e..8449bb4f 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -195,13 +195,13 @@ def do_measurement(self): def get_default_fname(self): """.""" prms = self.params - stg = 'kicked_data' - stg += f'_{prms.acq_rate}_rate' + stg = "kicked_data" + stg += f"_{prms.acq_rate}_rate" hkick, vkick = prms.hkick, prms.vkick pingers2kick = prms.pingers2kick if pingers2kick is not None: for plane in pingers2kick: - kick = hkick if plane == 'h' else vkick + kick = hkick if plane == "h" else vkick stg += f"{plane}kick_{int(round(kick)):3d}_urad" tm = self.data["timestamp"] fmt = "%Y-%m-%d-%H-%M-%S" From 9c408633f1791f272b6eb01c9cd6aaf81ff7e150 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 17 Apr 2024 17:13:43 -0300 Subject: [PATCH 028/144] COMMIS.ACQBPM.BUG: update EVG events after event setting --- apsuite/commisslib/meas_bpms_signals.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 36391f4b..ca341c97 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -4,7 +4,7 @@ import numpy as _np import scipy.fft as _sp_fft import scipy.signal as _sp_sig -from siriuspy.devices import CurrInfoSI, Event, FamBPMs, RFGen, Trigger, \ +from siriuspy.devices import CurrInfoSI, Event, EVG, FamBPMs, RFGen, Trigger, \ Tune from siriuspy.search import HLTimeSearch as _HLTimeSearch @@ -129,6 +129,7 @@ def create_devices(self): trigname = self.PSM_TRIGGER self.devices['trigbpm'] = Trigger(trigname) self.devices['evt_study'] = Event('Study') + self.devices['evg'] = EVG() self.devices['rfgen'] = RFGen() def get_timing_state(self): @@ -170,6 +171,7 @@ def prepare_timing(self, state=None): if dly is not None: evt.delay = dly evt.mode = state.get('evt_mode', self.params.event_mode) + self.devices['evg'].cmd_update_events() def prepare_bpms_acquisition(self): """.""" @@ -192,7 +194,8 @@ def acquire_data(self): print(tag + ' is not ready for acquisition.') fambpms.reset_mturn_initial_state() - # user must trigger timing event + + # NOTE: user must trigger timing event time0 = _time.time() ret = fambpms.wait_update_mturn(timeout=self.params.timeout) From 88c9caeac129a60bfe362a219964ba007e8c3619 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 09:59:33 -0300 Subject: [PATCH 029/144] COMMISS.ACQBPM.ENH: signal user to trigger timing event. --- apsuite/commisslib/meas_bpms_signals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index ca341c97..8f464d5f 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -196,6 +196,7 @@ def acquire_data(self): fambpms.reset_mturn_initial_state() # NOTE: user must trigger timing event + print('Ready for acquisition. Waiting for trigger event.') time0 = _time.time() ret = fambpms.wait_update_mturn(timeout=self.params.timeout) From 1d9001e33148a1129fe560a843a9d38d08cfd5a2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 10:05:02 -0300 Subject: [PATCH 030/144] COMMISS.TBT.ENH: `magnets_wait_time` -> `magnets_timeout`. --- apsuite/commisslib/measure_tbt_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 8449bb4f..f08cd82e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -152,7 +152,7 @@ def get_magnets_strength(self): return self.devices["pingh"].strength, self.devices["pingv"].strength def set_magnets_strength( - self, hkick=None, vkick=None, magnets_wait_time=None + self, hkick=None, vkick=None, magnets_timeout=None ): """.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] @@ -164,10 +164,10 @@ def set_magnets_strength( pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) # wait magnets ramp pingh.set_strength( - hkick, tol=0.05 * hkick, timeout=magnets_wait_time, wait_mon=False + hkick, tol=0.05 * hkick, timeout=magnets_timeout, wait_mon=False ) pingv.set_strength( - vkick, tol=0.05 * vkick, timeout=magnets_wait_time, wait_mon=False + vkick, tol=0.05 * vkick, timeout=magnets_timeout, wait_mon=False ) def do_measurement(self): From b695282c0be9127f402bb329c2964a79636a9ffe Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 13:23:25 -0300 Subject: [PATCH 031/144] COMMISS.TBT.ENH: fix magnets waiting time, return flag indicating scucess or failure --- apsuite/commisslib/measure_tbt_data.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f08cd82e..555d12ee 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -154,7 +154,7 @@ def get_magnets_strength(self): def set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None ): - """.""" + """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is None: hkick = self.params.hkick / 1e3 # [urad] -> [mrad] @@ -162,14 +162,25 @@ def set_magnets_strength( if vkick is None: vkick = self.params.vkick / 1e3 # [urad] -> [mrad] pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) - # wait magnets ramp - pingh.set_strength( + + # wait magnets ramp and check if set + t0 = _time.time() + pingh_ok = pingh.set_strength( hkick, tol=0.05 * hkick, timeout=magnets_timeout, wait_mon=False ) - pingv.set_strength( + elapsed_time = _time.time() - t0 + magnets_timeout -= elapsed_time + pingv_ok = pingv.set_strength( vkick, tol=0.05 * vkick, timeout=magnets_timeout, wait_mon=False ) + if (not pingh_ok) or (not pingv_ok): + bad_pingers = "pingh " if not pingh_ok else "" + bad_pingers += "pingv" if not pingv_ok else "" + print(f"Some magnets were not set.\n\tBad pingers: {bad_pingers}") + return False + + return True def do_measurement(self): """.""" currinfo = self.devices["currinfo"] From 84c7c0ddca845ab19cfb86111cd509ed5aaad039 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 14:15:15 -0300 Subject: [PATCH 032/144] COMMISS.TBT.ENH: improve handling of `pingers2kick` --- apsuite/commisslib/measure_tbt_data.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 555d12ee..ea267ecb 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -26,14 +26,13 @@ def __init__(self): self.timing_event = "Linac" self.event_mode = "Injection" - self._pingers2kick = None # 'H', 'V' or 'HV' + self._pingers2kick = "None" # 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None self.trigpingv_delay = None - pingers2kick = str(self._pingers2kick).lower() - self.trigpingh_nrpulses = 1 if "h" in pingers2kick else 0 - self.trigpingv_nrpulses = 1 if "v" in pingers2kick else 0 + self.trigpingh_nrpulses = 1 if "h" in self.pingers2kick else 0 + self.trigpingv_nrpulses = 1 if "v" in self.pingers2kick else 0 def __str__(self): """.""" @@ -78,11 +77,14 @@ def pingers2kick(self): return self._pingers2kick @pingers2kick.setter - def pingers2kick(self, value): - if (value is not None) and (value.lower() not in "hv"): - raise ValueError('Invalid keyword. Set "None", "H", "V" or "HV"') + def pingers2kick(self, val): + val = str(val).lower() + if val not in ["none", "h", "v", "hv"]: + raise ValueError('Invalid keyword. Set "None", "H", "V" or "HV".') else: - self._pingers2kick = value + self._pingers2kick = val + self.trigpingh_nrpulses = 1 if "h" in val else 0 + self.trigpingv_nrpulses = 1 if "v" in val else 0 class MeasureTbTData(_AcqBPMsSignals): @@ -100,12 +102,12 @@ def create_devices(self): """.""" super().create_devices() for pinger in self.pingers2kick: - if pinger.lower == "h": + if pinger == "h": self.devices["pingh"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_INJ_DPKCKR ) self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) - if pinger.lower() == "v": + if pinger == "v": self.devices["pinghv"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_PING_V ) @@ -210,7 +212,7 @@ def get_default_fname(self): stg += f"_{prms.acq_rate}_rate" hkick, vkick = prms.hkick, prms.vkick pingers2kick = prms.pingers2kick - if pingers2kick is not None: + if pingers2kick != 'none': for plane in pingers2kick: kick = hkick if plane == "h" else vkick stg += f"{plane}kick_{int(round(kick)):3d}_urad" From 025e2891916ecfd250556384126753a603444b1c Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 14:16:20 -0300 Subject: [PATCH 033/144] COMMIS.TBT.ENH: improve checking and waiting the setting of magnets strengths and measurement workflow --- apsuite/commisslib/measure_tbt_data.py | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ea267ecb..215ca4e4 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -183,6 +183,7 @@ def set_magnets_strength( return False return True + def do_measurement(self): """.""" currinfo = self.devices["currinfo"] @@ -190,20 +191,36 @@ def do_measurement(self): init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() self.prepare_timing() - self.set_magnets_strength() # gets strengths from params - self.data["measurement_error"] = False # error flag - try: - self.acquire_data() - # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - except Exception as e: - print(f"An error occurred during acquisition: {e}") - self.data["measurement_error"] = True + mags_ok = self.set_magnets_strength() # gets strengths from params + if mags_ok: + print("Magnets strengths were succesfully set.") + strenghts = self.get_magnets_strength() + try: + self.acquire_data() + # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + self.data["current_before"] = current_before + self.data["current_after"] = self.data.pop("stored_current") + self.data["init_magnets_strengths"] = init_magnets_strength + self.data["manets_strengths"] = strenghts * 1e3 # [urad] + print("Acquisition was succesful.") + except Exception as e: + print(f"An error occurred during acquisition: {e}") + else: + print("Did not measure. Restoring magnets & timing initial state.") + self.recover_timing_state(init_timing_state) - self.set_magnets_strength(init_magnets_strength) # restore strengths - self.data["current_before"] = current_before - self.data["current_after"] = self.data.pop("stored_current") + mags_ok = self.set_magnets_strength(init_magnets_strength) # restore + if not mags_ok: + msg = "Magnets strengths were not restored to initial values." + msg += "Restore manually." + print(msg) + print("Initial strengths:") + print(f"\t pingh:{init_magnets_strength[0]:.4f} [mrad]") + print(f"\t pingv:{init_magnets_strength[0]:.4f} [mrad]") + else: + print("Magnets strengths succesfully restored to initial values.") def get_default_fname(self): """.""" From d34c5500f11c969a3175f88ccb1201ff792e0a5a Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 14:28:41 -0300 Subject: [PATCH 034/144] COMMISS.TBT.BUG: create pingers devices regardless of `pingers2kick` --- apsuite/commisslib/measure_tbt_data.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 215ca4e4..f7393852 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -101,17 +101,14 @@ def __init__(self, isonline=False): def create_devices(self): """.""" super().create_devices() - for pinger in self.pingers2kick: - if pinger == "h": - self.devices["pingh"] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_INJ_DPKCKR - ) - self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) - if pinger == "v": - self.devices["pinghv"] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_PING_V - ) - self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) + self.devices["pingh"] = PowerSupplyPU( + PowerSupplyPU.DEVICES.SI_INJ_DPKCKR + ) + self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) + + self.devices["pinghv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) + self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) + return def get_timing_state(self): """.""" @@ -229,7 +226,7 @@ def get_default_fname(self): stg += f"_{prms.acq_rate}_rate" hkick, vkick = prms.hkick, prms.vkick pingers2kick = prms.pingers2kick - if pingers2kick != 'none': + if pingers2kick != "none": for plane in pingers2kick: kick = hkick if plane == "h" else vkick stg += f"{plane}kick_{int(round(kick)):3d}_urad" From 2fdf8e54dc0bdb1af55ce95bad02427642f227a7 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 14:57:33 -0300 Subject: [PATCH 035/144] COMMISS.TBT.ENH: keep both kicks info in naming system & flag `"inactive"` when not pulsing. --- apsuite/commisslib/measure_tbt_data.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f7393852..9ef75145 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -162,7 +162,7 @@ def set_magnets_strength( vkick = self.params.vkick / 1e3 # [urad] -> [mrad] pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) - # wait magnets ramp and check if set + # wait magnets ramp and check if correctly set t0 = _time.time() pingh_ok = pingh.set_strength( hkick, tol=0.05 * hkick, timeout=magnets_timeout, wait_mon=False @@ -222,14 +222,19 @@ def do_measurement(self): def get_default_fname(self): """.""" prms = self.params - stg = "kicked_data" + stg = "kickedbeam_data" stg += f"_{prms.acq_rate}_rate" - hkick, vkick = prms.hkick, prms.vkick + + hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) pingers2kick = prms.pingers2kick - if pingers2kick != "none": - for plane in pingers2kick: - kick = hkick if plane == "h" else vkick - stg += f"{plane}kick_{int(round(kick)):3d}_urad" + if pingers2kick == "none": + stg += "hkick_inactive_vkick_inactive" + else: + stg += f"hkick_{hkick:3d}_urad" if "h" in pingers2kick else \ + "hkick_inactive" + stg += f"vkick_{vkick:3d}_urad" if "h" in pingers2kick else \ + "vkick_inactive" + tm = self.data["timestamp"] fmt = "%Y-%m-%d-%H-%M-%S" tmstp = _datetime.datetime.fromtimestamp(tm).strftime(fmt) From e30cfb9b9da032a5af931a9c3b0a2def3e8956ab Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 15:15:57 -0300 Subject: [PATCH 036/144] COMMISS.TBT.ENH: add `magnets_timeout` property in params & improve redability of `__str__` method --- apsuite/commisslib/measure_tbt_data.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9ef75145..0d5d9ef0 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -26,21 +26,28 @@ def __init__(self): self.timing_event = "Linac" self.event_mode = "Injection" - self._pingers2kick = "None" # 'H', 'V' or 'HV' + self._pingers2kick = "none" # 'none' 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None self.trigpingv_delay = None self.trigpingh_nrpulses = 1 if "h" in self.pingers2kick else 0 self.trigpingv_nrpulses = 1 if "v" in self.pingers2kick else 0 + self.magnets_timeout = 5. def __str__(self): """.""" - stg = super().__str__() + stg = "BPMs & timing params\n" + stg += "\n" + stg += super().__str__() ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format dtmp = "{0:26s} = {1:9d} {2:s}\n".format stmp = "{0:26s} = {1:9} {2:s}\n".format + stg += "\n" + stg += "Pingers params\n" + stg += "\n" stg += stmp("pingers2kick", self.pingers2kick, "") + stg += ftmp("magnets_timeout", self.magnets_timeout, "[s]") if self.hkick is None: stg += stmp("hkick", "same", "(current value will not be changed)") else: @@ -163,6 +170,8 @@ def set_magnets_strength( pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) # wait magnets ramp and check if correctly set + if magnets_timeout is None: + magnets_timeout = self.params.magnets_timeout t0 = _time.time() pingh_ok = pingh.set_strength( hkick, tol=0.05 * hkick, timeout=magnets_timeout, wait_mon=False From f635e3ef30a84afbf9fe23ef932d637cfb524a82 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 16:23:26 -0300 Subject: [PATCH 037/144] COMMISS.TBT.BUG: prevent attempt at rounding a `None` type --- apsuite/commisslib/measure_tbt_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 0d5d9ef0..9ce0e267 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -234,11 +234,11 @@ def get_default_fname(self): stg = "kickedbeam_data" stg += f"_{prms.acq_rate}_rate" - hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) pingers2kick = prms.pingers2kick if pingers2kick == "none": stg += "hkick_inactive_vkick_inactive" else: + hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) stg += f"hkick_{hkick:3d}_urad" if "h" in pingers2kick else \ "hkick_inactive" stg += f"vkick_{vkick:3d}_urad" if "h" in pingers2kick else \ From 369e0315ce0870ba231a909055b5013f9d63f67d Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 18 Apr 2024 16:28:47 -0300 Subject: [PATCH 038/144] COMMISS.TBT.ENH: handle also `"vh"` as keyword for `pingers2kick` --- apsuite/commisslib/measure_tbt_data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9ce0e267..39a728d6 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -86,8 +86,10 @@ def pingers2kick(self): @pingers2kick.setter def pingers2kick(self, val): val = str(val).lower() - if val not in ["none", "h", "v", "hv"]: - raise ValueError('Invalid keyword. Set "None", "H", "V" or "HV".') + if val not in ["none", "h", "v", "hv", "vh"]: + raise ValueError( + 'Invalid keyword. Set "None", "H", "V", "HV" or "VH".' + ) else: self._pingers2kick = val self.trigpingh_nrpulses = 1 if "h" in val else 0 From 58916888dbb484acc8146aeaa6a412f768323904 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 19 Apr 2024 10:13:32 -0300 Subject: [PATCH 039/144] COMMISS.TBT.ENH: linear fit for fourier components instead of raw-data projection for amplitude guess --- apsuite/commisslib/measure_tbt_data.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 39a728d6..497db043 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -294,21 +294,20 @@ def _get_tune_guess(self, matrix): tune_peaks = [tunes[idc, col] for col, idc in enumerate(peak_idcs)] return _np.mean(tune_peaks) # tune guess - def _get_amplitudes_and_phases_guess(self, matrix, tune): - """Calculate initial amplitude & phase guesses for harmonic TbT model. - - Implements Eqs. (5.2)-(5.4) from Ref. [2] - """ - N = matrix.shape[0] - ilist = _np.arange(N) + def _get_fourier_components(self, matrix, tune): + """Performs linear fit for Fourier components amplitudes.""" + ilist = _np.arange(matrix.shape[0]) cos = _np.cos(2 * _np.pi * tune * ilist) sin = _np.sin(2 * _np.pi * tune * ilist) - C, S = _np.dot(cos, matrix), _np.dot(sin, matrix) - C *= 2 / N - S *= 2 / N - amplitudes = _np.sqrt(C**2 + S**2) - phases = _np.unwrap(_np.arctan2(C, S)) - return amplitudes, phases + + coeff_mat = _np.concatenate((cos[:, None], sin[:, None]), axis=1) + fourier_components = _np.linalg.pinv(coeff_mat) @ matrix + # amplitudes = _np.sqrt(_np.sum(fourier_components**2, axis=0)) + # phases = _np.arctan2( + # fourier_components[0, :], fourier_components[-1, :] + # ) + return fourier_components + def harmonic_tbt_model(self, ilist, amplitude, tune, phase): """Harmonic motion model for positions seen at a given BPM.""" From a430e5edbdaf6de676e26092fc62d5c0e3540a58 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 19 Apr 2024 14:54:41 -0300 Subject: [PATCH 040/144] COMMISS.TBT.ENH: "vectorize" harmonic model fitting --- apsuite/commisslib/measure_tbt_data.py | 66 ++++++++++++-------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 497db043..9bf833cc 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -302,48 +302,44 @@ def _get_fourier_components(self, matrix, tune): coeff_mat = _np.concatenate((cos[:, None], sin[:, None]), axis=1) fourier_components = _np.linalg.pinv(coeff_mat) @ matrix - # amplitudes = _np.sqrt(_np.sum(fourier_components**2, axis=0)) - # phases = _np.arctan2( - # fourier_components[0, :], fourier_components[-1, :] - # ) - return fourier_components + return fourier_components - def harmonic_tbt_model(self, ilist, amplitude, tune, phase): - """Harmonic motion model for positions seen at a given BPM.""" - return amplitude * _np.cos(2 * _np.pi * tune * ilist + phase) + def _get_amplitude_phase(self, fourier_components): + amplitudes = _np.sqrt(_np.sum(fourier_components**2, axis=0)) + phases = _np.arctan2( + fourier_components[0, :], fourier_components[-1, :] + ) + return amplitudes, phases - def harmonic_tbt_model_vectorized(self, ilist, amplitude, tune, phase): + def harmonic_tbt_model(self, ilist, *args, return_ravel=True): """Harmonic motion model for positions seen at a given BPM.""" - model = amplitude[None, :] - model *= _np.cos(2 * _np.pi * tune * ilist[:, None] + phase[None, :]) - return model + nbpms = (len(args)-1) // 2 + tune = args[0] + amps = _np.array(args[1:nbpms+1]) + phases = _np.array(args[-nbpms:]) + x = ilist.reshape((-1, nbpms), order="F") + wr = 2 * _np.pi * tune + mod = amps[None, :] * _np.sin(wr * x + phases[None, :]) + if return_ravel: + return mod.ravel() + return mod def fit_harmonic_model( - self, matrix, amp_guesses, tune_guess, phase_guesses + self, matrix, tune_guess, amplitudes_guess, phases_guess ): - """Fits harmonic TbT model to data. - - Args: - matrix (N,M)-array: data matrix containing N turns and BPMs - amp_guesses M-array: amplitude guess for each BPM time-series - tune_guess (float): betatron tune guess - phase_guesses M-array: phase guess for each BPM - - Returns: - params (3, M)-array: fitted ampliude, tune and phase for each BPM. - each column corresponds to a BPM, each row - corresponds to amplitudes, tune and phase - """ - ilist = _np.arange(matrix.shape[0]) - params = _np.zeros((3, matrix.shape[-1])) - for bpm_idx, bpm_data in enumerate(matrix.T): - p0 = [amp_guesses[bpm_idx], tune_guess, phase_guesses[bpm_idx]] - popt, *_ = _curve_fit( - f=self.harmonic_tbt_model, xdata=ilist, ydata=bpm_data, p0=p0 - ) - params[:, bpm_idx] = popt - return params + """Fits harmonic TbT model to data.""" + bpmdata = matrix.ravel() + nturns, nbpms = matrix.shape[0], matrix.shape[1] + xdata = _np.tile(_np.arange(nturns), nbpms).ravel() + p0 = _np.concatenate( + ([tune_guess], amplitudes_guess, phases_guess) + ).tolist() + + popt, pcov = _curve_fit( + f=self.harmonic_tbt_model, xdata=xdata, ydata=bpmdata, p0=p0 + ) + return popt, _np.diagonal(pcov) def calculate_betafunc_and_action(self, amplitudes, nominal_beta): """Calculates beta function and betatron action. From 0b37d47bf315f96633fd380fdacce065d43a0a3e Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 22 Apr 2024 13:36:56 -0300 Subject: [PATCH 041/144] COMMISS.TBT.ENH: fix turning magnets on/off --- apsuite/commisslib/measure_tbt_data.py | 107 +++++++++++++++++-------- 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9bf833cc..ad2c558b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -26,13 +26,11 @@ def __init__(self): self.timing_event = "Linac" self.event_mode = "Injection" - self._pingers2kick = "none" # 'none' 'H', 'V' or 'HV' + self._pingers2kick = "none" # 'H', 'V' or 'HV' self.hkick = None # [urad] self.vkick = None # [urad] self.trigpingh_delay = None self.trigpingv_delay = None - self.trigpingh_nrpulses = 1 if "h" in self.pingers2kick else 0 - self.trigpingv_nrpulses = 1 if "v" in self.pingers2kick else 0 self.magnets_timeout = 5. def __str__(self): @@ -61,7 +59,6 @@ def __str__(self): ) else: stg += ftmp("trigpingh_delay", dly, "[us]") - stg += dtmp("trigpingh_nrpulses", self.trigpingh_nrpulses, "") if self.vkick is None: stg += stmp("vkick", "same", "(current value will not be changed)") else: @@ -75,7 +72,6 @@ def __str__(self): ) else: stg += ftmp("trigpingv_delay", dly, "[us]") - stg += dtmp("trigpingv_nrpulses", self.trigpingv_nrpulses, "") return stg @property @@ -85,15 +81,7 @@ def pingers2kick(self): @pingers2kick.setter def pingers2kick(self, val): - val = str(val).lower() - if val not in ["none", "h", "v", "hv", "vh"]: - raise ValueError( - 'Invalid keyword. Set "None", "H", "V", "HV" or "VH".' - ) - else: - self._pingers2kick = val - self.trigpingh_nrpulses = 1 if "h" in val else 0 - self.trigpingv_nrpulses = 1 if "v" in val else 0 + self._pingers2kick = str(val).lower() class MeasureTbTData(_AcqBPMsSignals): @@ -123,12 +111,12 @@ def get_timing_state(self): """.""" state = super().get_timing_state() trigpingh = self.devices["trigpingh"] + state["trigpingh_state"] = trigpingh.state # correct? state["trigpingh_source"] = trigpingh.source - state["trigpingh_nrpulses"] = trigpingh.nr_pulses state["trigpingh_delay"] = trigpingh.delay trigpingv = self.devices["trigpingv"] + state["trigpingv_state"] = trigpingv.state # correct? state["trigpingv_source"] = trigpingv.source - state["trigpingv_nrpulses"] = trigpingv.nr_pulses state["trigpingv_delay"] = trigpingv.delay return state @@ -138,27 +126,48 @@ def prepare_timing(self, state=None): # magnets trigger timing below prms = self.params trigpingh = self.devices["trigpingh"] - trigpingh.source = state.get("trigpingh_source", prms.timing_event) - trigpingh.nr_pulses = state.get( - "trigpingh_nrpulses", prms.trigpingh_nrpulses - ) - dly = state.get("trigpingh_delay", prms.trigpingh_delay) - if dly is not None: - trigpingh.delay = dly + pingers2kick = prms.pingers2kick + if ("h" in pingers2kick) or (state.get("trigpingh_state", 0) == 1): + trigpingh.cmd_enable(timeout=prms.timeout) + trigpingh.source = state.get("trigpingh_source", prms.timing_event) + dly = state.get("trigpingh_delay", prms.trigpingh_delay) + if dly is not None: + trigpingh.delay = dly + else: + trigpingh.cmd_disable(timeout=prms.timeout) trigpingv = self.devices["trigpingv"] - trigpingv.source = state.get("trigpingv_source", prms.timing_event) - trigpingh.nr_pulses = state.get( - "trigpingv_nrpulses", prms.trigpingv_nrpulses - ) - dly = state.get("trigpingv_delay", prms.trigpingv_delay) - if dly is not None: - trigpingv.delay = dly + if ("v" in pingers2kick) or (state.get("trigpingv_state", 0) == 1): + trigpingv.cmd_enable(timeout=prms.timeout) + trigpingv.source = state.get("trigpingv_source", prms.timing_event) + dly = state.get("trigpingv_delay", prms.trigpingv_delay) + if dly is not None: + trigpingv.delay = dly + else: + trigpingv.cmd_disable(timeout=prms.timeout) def get_magnets_strength(self): """.""" return self.devices["pingh"].strength, self.devices["pingv"].strength + def get_magnets_state(self): + """.""" + return self.devices["pingh"].pwrstate, self.devices["pingv"].pwrstate + + def restore_magnets_state(self, state): + """.""" + pinghpulse, pingvpulse = state + pingh, pingv = self.devices['pingh'], self.devices['pingv'] + if pinghpulse: + pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) + else: + pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) + if pingvpulse: + pingh_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) + else: + pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) + return pingh_ok and pingv_ok + def set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None ): @@ -192,17 +201,45 @@ def set_magnets_strength( return True + def prepare_magnets(self): + """.""" + print('Preparing magnets...') + mags_timeout = self.params.magnets_timeout + pingers2kick = self.params.pingers2kick + pingh = self.devices['pingh'] + pingv = self.devices['pingv'] + + if 'h' in pingers2kick: + pingh_ok = pingh.cmd_turn_on_pulse(self, timeout=mags_timeout) + print('Failed to turn-on pingh.') if not pingh_ok else None + else: + pingh_ok = pingv.cmd_turn_off_pulse(self, timeout=mags_timeout) + print('Failed to turn-off pingh.') if not pingh_ok else None + print(f'pingh succesfully turned-{pingh.pwrstate}') + + if 'v' in pingers2kick: + pingv_ok = pingv.cmd_turn_on_pulse(self, timeout=mags_timeout) + print('Failed to turn-on pingv.') if not pingv_ok else None + else: + pingv_ok = pingv.cmd_turn_off_pulse(self, timeout=mags_timeout) + print('Failed to turn-off pingh.') if not pingv_ok else None + print(f'pingv succesfully turned-{pingv.pwrstate}') + print('Setting magnets strengths...') + if pingh_ok and pingv_ok: + return self.set_magnets_strength() + def do_measurement(self): """.""" currinfo = self.devices["currinfo"] init_timing_state = self.get_timing_state() + init_magnets_state = self.get_magnets_state() init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() self.prepare_timing() - mags_ok = self.set_magnets_strength() # gets strengths from params + # TODO: prepare_magnets actions when strnegth is None + mags_ok = self.prepare_magnets() # gets strengths from params if mags_ok: print("Magnets strengths were succesfully set.") - strenghts = self.get_magnets_strength() try: self.acquire_data() # BPMs signals + relevant info are acquired @@ -211,14 +248,15 @@ def do_measurement(self): self.data["current_before"] = current_before self.data["current_after"] = self.data.pop("stored_current") self.data["init_magnets_strengths"] = init_magnets_strength + strenghts = self.get_magnets_strength() self.data["manets_strengths"] = strenghts * 1e3 # [urad] print("Acquisition was succesful.") except Exception as e: print(f"An error occurred during acquisition: {e}") else: print("Did not measure. Restoring magnets & timing initial state.") - self.recover_timing_state(init_timing_state) + mags_ok = self.restore_magnets_state(init_magnets_state) mags_ok = self.set_magnets_strength(init_magnets_strength) # restore if not mags_ok: msg = "Magnets strengths were not restored to initial values." @@ -226,9 +264,10 @@ def do_measurement(self): print(msg) print("Initial strengths:") print(f"\t pingh:{init_magnets_strength[0]:.4f} [mrad]") - print(f"\t pingv:{init_magnets_strength[0]:.4f} [mrad]") + print(f"\t pingv:{init_magnets_strength[1]:.4f} [mrad]") else: print("Magnets strengths succesfully restored to initial values.") + print('Measurement finished.') def get_default_fname(self): """.""" From 8dec5e68977914cde20cbc12d613621f75476ae7 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 22 Apr 2024 16:27:46 -0300 Subject: [PATCH 042/144] COMMISS.TBT.ENH: magnets state as a dict with pwrstate and pulsestate --- apsuite/commisslib/measure_tbt_data.py | 57 +++++++++++++++++++------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ad2c558b..c92ac443 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -122,29 +122,36 @@ def get_timing_state(self): def prepare_timing(self, state=None): """.""" + print("Setting BPMs timing") super().prepare_timing(state) # BPM trigger timing + + print("Setting magnets timing") # magnets trigger timing below prms = self.params trigpingh = self.devices["trigpingh"] pingers2kick = prms.pingers2kick if ("h" in pingers2kick) or (state.get("trigpingh_state", 0) == 1): - trigpingh.cmd_enable(timeout=prms.timeout) + trigh_ok = trigpingh.cmd_enable(timeout=prms.timeout) trigpingh.source = state.get("trigpingh_source", prms.timing_event) dly = state.get("trigpingh_delay", prms.trigpingh_delay) if dly is not None: trigpingh.delay = dly else: - trigpingh.cmd_disable(timeout=prms.timeout) + trigh_ok = trigpingh.cmd_disable(timeout=prms.timeout) + print(f"PingerH trigger set: {trigh_ok}") trigpingv = self.devices["trigpingv"] if ("v" in pingers2kick) or (state.get("trigpingv_state", 0) == 1): - trigpingv.cmd_enable(timeout=prms.timeout) + trigv_ok = trigpingv.cmd_enable(timeout=prms.timeout) trigpingv.source = state.get("trigpingv_source", prms.timing_event) dly = state.get("trigpingv_delay", prms.trigpingv_delay) if dly is not None: trigpingv.delay = dly else: - trigpingv.cmd_disable(timeout=prms.timeout) + trigv_ok = trigpingv.cmd_disable(timeout=prms.timeout) + print(f"PingerV trigger set: {trigv_ok}") + + return trigh_ok and trigv_ok def get_magnets_strength(self): """.""" @@ -152,20 +159,39 @@ def get_magnets_strength(self): def get_magnets_state(self): """.""" - return self.devices["pingh"].pwrstate, self.devices["pingv"].pwrstate + state = dict() + pingh, pingv = self.devices["pingh"], self.devices["pingv"] + state["pingh_pwr"] = pingh.pwrstate + state["pingv_pwr"] = pingv.pwrstate + state["pingh_pulse"] = pingh.pulse + state["pingv_pulse"] = pingv.pulse + return state def restore_magnets_state(self, state): - """.""" - pinghpulse, pingvpulse = state + """Restore magnets pwr and pulse states.""" + timeout = self.params.magnets_timeout pingh, pingv = self.devices['pingh'], self.devices['pingv'] - if pinghpulse: - pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) + + if state['pingh_pwr']: + pingh_ok = pingh.cmd_turn_on(timeout) else: - pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) - if pingvpulse: - pingh_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) + pingh_ok = pingh.cmd_turn_off(timeout) + + if state["pingv_pwr"]: + pingv_ok = pingv.cmd_turn_on(timeout) + else: + pingv_ok = pingv.cmd_turn_off(timeout) + + if state['pingh_pulse']: + pingh_ok = pingh.cmd_turn_on_pulse(timeout) else: - pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) + pingh_ok = pingh.cmd_turn_off_pulse(timeout) + + if state["pingv_pulse"]: + pingv_ok = pingv.cmd_turn_on_pulse(timeout) + else: + pingv_ok = pingv.cmd_turn_off_pulse(timeout) + return pingh_ok and pingv_ok def set_magnets_strength( @@ -235,11 +261,14 @@ def do_measurement(self): init_magnets_state = self.get_magnets_state() init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current() - self.prepare_timing() + timing_ok = self.prepare_timing() + if timing_ok: + print("Timing configs. were succesfully set") # TODO: prepare_magnets actions when strnegth is None mags_ok = self.prepare_magnets() # gets strengths from params if mags_ok: print("Magnets strengths were succesfully set.") + if mags_ok and timing_ok: try: self.acquire_data() # BPMs signals + relevant info are acquired From f6a390ff924e7e13562ef28a9a907331215fe0e1 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 22 Apr 2024 17:05:20 -0300 Subject: [PATCH 043/144] COMMISS.TBT.ENH: do nothing if strengths are `None` & prepare magnets state compatible with an an optioonal `dict` state arg --- apsuite/commisslib/measure_tbt_data.py | 81 +++++++++++++++----------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c92ac443..8377f877 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -167,7 +167,7 @@ def get_magnets_state(self): state["pingv_pulse"] = pingv.pulse return state - def restore_magnets_state(self, state): + def set_magnets_state(self, state): """Restore magnets pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices['pingh'], self.devices['pingv'] @@ -199,60 +199,71 @@ def set_magnets_strength( ): """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] - if hkick is None: - hkick = self.params.hkick / 1e3 # [urad] -> [mrad] - pingh.set_strength(hkick, tol=0.1 * hkick, timeout=0, wait_mon=False) - if vkick is None: - vkick = self.params.vkick / 1e3 # [urad] -> [mrad] - pingv.set_strength(vkick, tol=0.1 * vkick, timeout=0, wait_mon=False) + if hkick is not None: + pingh.set_strength(hkick, timeout=0, wait_mon=False) + else: + print("pingh not changed (None-type strength)") + pingh_ok = True + + if vkick is not None: + pingv.set_strength(vkick, timeout=0, wait_mon=False) + else: + print("pingv not changed (None-type strength)") + pingv_ok = True # wait magnets ramp and check if correctly set if magnets_timeout is None: magnets_timeout = self.params.magnets_timeout t0 = _time.time() - pingh_ok = pingh.set_strength( - hkick, tol=0.05 * hkick, timeout=magnets_timeout, wait_mon=False - ) + if hkick is not None: + pingh_ok = pingh.set_strength( + hkick, tol=0.05 * hkick, timeout=magnets_timeout, + wait_mon=False + ) elapsed_time = _time.time() - t0 magnets_timeout -= elapsed_time - pingv_ok = pingv.set_strength( - vkick, tol=0.05 * vkick, timeout=magnets_timeout, wait_mon=False - ) + if vkick is not None: + pingv_ok = pingv.set_strength( + vkick, tol=0.05 * vkick, timeout=magnets_timeout, + wait_mon=False + ) if (not pingh_ok) or (not pingv_ok): bad_pingers = "pingh " if not pingh_ok else "" bad_pingers += "pingv" if not pingv_ok else "" print(f"Some magnets were not set.\n\tBad pingers: {bad_pingers}") return False - return True def prepare_magnets(self): """.""" print('Preparing magnets...') - mags_timeout = self.params.magnets_timeout pingers2kick = self.params.pingers2kick - pingh = self.devices['pingh'] - pingv = self.devices['pingv'] - + state = dict() if 'h' in pingers2kick: - pingh_ok = pingh.cmd_turn_on_pulse(self, timeout=mags_timeout) - print('Failed to turn-on pingh.') if not pingh_ok else None + state['pingh_pwr'] = 1 + state['pingh_pulse'] = 1 else: - pingh_ok = pingv.cmd_turn_off_pulse(self, timeout=mags_timeout) - print('Failed to turn-off pingh.') if not pingh_ok else None - print(f'pingh succesfully turned-{pingh.pwrstate}') + state['pingh_pwr'] = 0 + state['pingh_pulse'] = 0 if 'v' in pingers2kick: - pingv_ok = pingv.cmd_turn_on_pulse(self, timeout=mags_timeout) - print('Failed to turn-on pingv.') if not pingv_ok else None + state['pingh_pwr'] = 1 + state['pingh_pulse'] = 1 else: - pingv_ok = pingv.cmd_turn_off_pulse(self, timeout=mags_timeout) - print('Failed to turn-off pingh.') if not pingv_ok else None - print(f'pingv succesfully turned-{pingv.pwrstate}') - print('Setting magnets strengths...') - if pingh_ok and pingv_ok: - return self.set_magnets_strength() + state['pingh_pwr'] = 0 + state['pingh_pulse'] = 0 + + state_ok = self.set_magnets_state(state) + + if state_ok: + hkick = self.params.hkick / 1e3 + vkick = self.params.vkick / 1e3 + print('Setting magnets strengths...') + return self.set_magnets_strength(hkick, vkick) + else: + print('Magnets state not set.') + return False def do_measurement(self): """.""" @@ -267,7 +278,7 @@ def do_measurement(self): # TODO: prepare_magnets actions when strnegth is None mags_ok = self.prepare_magnets() # gets strengths from params if mags_ok: - print("Magnets strengths were succesfully set.") + print("Magnets ready.") if mags_ok and timing_ok: try: self.acquire_data() @@ -284,8 +295,10 @@ def do_measurement(self): print(f"An error occurred during acquisition: {e}") else: print("Did not measure. Restoring magnets & timing initial state.") - self.recover_timing_state(init_timing_state) - mags_ok = self.restore_magnets_state(init_magnets_state) + timing_ok = self.recover_timing_state(init_timing_state) + if not timing_ok: + print("Timing was not restored to initial state.") + mags_ok = self.set_magnets_state(init_magnets_state) mags_ok = self.set_magnets_strength(init_magnets_strength) # restore if not mags_ok: msg = "Magnets strengths were not restored to initial values." From 74a42a3c8c249e41f01418ad02232b34c9055d2b Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 22 Apr 2024 17:31:48 -0300 Subject: [PATCH 044/144] COMMISS.TBT.ENH: include magnets state & strengths to `get_data` function --- apsuite/commisslib/measure_tbt_data.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 8377f877..e73eb684 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -275,7 +275,6 @@ def do_measurement(self): timing_ok = self.prepare_timing() if timing_ok: print("Timing configs. were succesfully set") - # TODO: prepare_magnets actions when strnegth is None mags_ok = self.prepare_magnets() # gets strengths from params if mags_ok: print("Magnets ready.") @@ -288,8 +287,6 @@ def do_measurement(self): self.data["current_before"] = current_before self.data["current_after"] = self.data.pop("stored_current") self.data["init_magnets_strengths"] = init_magnets_strength - strenghts = self.get_magnets_strength() - self.data["manets_strengths"] = strenghts * 1e3 # [urad] print("Acquisition was succesful.") except Exception as e: print(f"An error occurred during acquisition: {e}") @@ -311,6 +308,13 @@ def do_measurement(self): print("Magnets strengths succesfully restored to initial values.") print('Measurement finished.') + def get_data(self): + """.""" + data = super().get_data() + data["magnets_state"] = self.get_magnets_state + data["magnets_strengths"] = self.get_magnets_strength() * 1e3 + return data + def get_default_fname(self): """.""" prms = self.params From 829d576ae59f3a31ede9f0bf35474cc9023bcc13 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Tue, 23 Apr 2024 15:13:08 -0300 Subject: [PATCH 045/144] COMMISS.TBT.BUG: fixes during online tests, change to mrad. --- apsuite/commisslib/measure_tbt_data.py | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index e73eb684..7df82f32 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -27,8 +27,8 @@ def __init__(self): self.event_mode = "Injection" self._pingers2kick = "none" # 'H', 'V' or 'HV' - self.hkick = None # [urad] - self.vkick = None # [urad] + self.hkick = None # [mrad] + self.vkick = None # [mrad] self.trigpingh_delay = None self.trigpingv_delay = None self.magnets_timeout = 5. @@ -49,7 +49,7 @@ def __str__(self): if self.hkick is None: stg += stmp("hkick", "same", "(current value will not be changed)") else: - stg += ftmp("hkick", self.hkick, "[urad]") + stg += ftmp("hkick", self.hkick, "[mrad]") dly = self.trigpingh_delay if dly is None: stg += stmp( @@ -62,7 +62,7 @@ def __str__(self): if self.vkick is None: stg += stmp("vkick", "same", "(current value will not be changed)") else: - stg += ftmp("vkick", self.vkick, "[urad]") + stg += ftmp("vkick", self.vkick, "[mrad]") dly = self.trigpingv_delay if dly is None: stg += stmp( @@ -103,7 +103,7 @@ def create_devices(self): ) self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) - self.devices["pinghv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) + self.devices["pingv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) return @@ -124,7 +124,7 @@ def prepare_timing(self, state=None): """.""" print("Setting BPMs timing") super().prepare_timing(state) # BPM trigger timing - + state = dict() if state is None else state print("Setting magnets timing") # magnets trigger timing below prms = self.params @@ -217,14 +217,14 @@ def set_magnets_strength( t0 = _time.time() if hkick is not None: pingh_ok = pingh.set_strength( - hkick, tol=0.05 * hkick, timeout=magnets_timeout, + hkick, tol=0.05 * abs(hkick), timeout=magnets_timeout, wait_mon=False ) elapsed_time = _time.time() - t0 magnets_timeout -= elapsed_time if vkick is not None: pingv_ok = pingv.set_strength( - vkick, tol=0.05 * vkick, timeout=magnets_timeout, + vkick, tol=0.05 * abs(vkick), timeout=magnets_timeout, wait_mon=False ) @@ -248,17 +248,17 @@ def prepare_magnets(self): state['pingh_pulse'] = 0 if 'v' in pingers2kick: - state['pingh_pwr'] = 1 - state['pingh_pulse'] = 1 + state['pingv_pwr'] = 1 + state['pingv_pulse'] = 1 else: - state['pingh_pwr'] = 0 - state['pingh_pulse'] = 0 + state['pingv_pwr'] = 0 + state['pingv_pulse'] = 0 state_ok = self.set_magnets_state(state) if state_ok: - hkick = self.params.hkick / 1e3 - vkick = self.params.vkick / 1e3 + hkick = self.params.hkick + vkick = self.params.vkick print('Setting magnets strengths...') return self.set_magnets_strength(hkick, vkick) else: @@ -271,7 +271,7 @@ def do_measurement(self): init_timing_state = self.get_timing_state() init_magnets_state = self.get_magnets_state() init_magnets_strength = self.get_magnets_strength() - current_before = currinfo.current() + current_before = currinfo.current timing_ok = self.prepare_timing() if timing_ok: print("Timing configs. were succesfully set") @@ -311,8 +311,8 @@ def do_measurement(self): def get_data(self): """.""" data = super().get_data() - data["magnets_state"] = self.get_magnets_state - data["magnets_strengths"] = self.get_magnets_strength() * 1e3 + data["magnets_state"] = self.get_magnets_state() + data["magnets_strengths"] = self.get_magnets_strength() # [mrad] return data def get_default_fname(self): @@ -326,9 +326,9 @@ def get_default_fname(self): stg += "hkick_inactive_vkick_inactive" else: hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) - stg += f"hkick_{hkick:3d}_urad" if "h" in pingers2kick else \ + stg += f"hkick_{hkick:3d}_mrad" if "h" in pingers2kick else \ "hkick_inactive" - stg += f"vkick_{vkick:3d}_urad" if "h" in pingers2kick else \ + stg += f"vkick_{vkick:3d}_mrad" if "h" in pingers2kick else \ "vkick_inactive" tm = self.data["timestamp"] From 4441d18afee05084edc32d6b59b98058cf36a83c Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 25 Apr 2024 16:05:25 -0300 Subject: [PATCH 046/144] COMMISS.TBT.ENH: add analysis class properties and load method --- apsuite/commisslib/measure_tbt_data.py | 132 ++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 7df82f32..2e787f5e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -344,7 +344,137 @@ class TbTDataAnalysis(MeasureTbTData): def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" super().__init__(isonline=isonline) - self.fname = filename + self._fname = filename + self.timestamp = None + self.trajx, self.trajy = None, None # zero-mean trajectories in [mm] + self.trajsum = None + self.tunex, self.tuney = None, None + self.acq_rate = None + self.sampling_freq = None + self.switching_freq = None + self.rf_freq = None + if self._fname: + self.load_and_apply() + + def __str__(self): + """.""" + stg = "" + data = self.data + if data: + stg += "\n" + stg += "Measurement data:\n" + + ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format + stmp = "{0:26s} = {1:9} {2:s}\n".format + gtmp = "{0:<15s} = {1:} {2:}\n".format + + stg += gtmp("timestamp", self.timestamp, "") + stg += "\n" + stg += "Storage Ring State\n" + + stg += ftmp("current_before", data["current_before"], "mA") + stg += ftmp("current_after", data["current_after"], "mA") + stg += ftmp("tunex", data["tunex"], "") + stg += ftmp("tuney", data["tuney"], "") + stg += stmp("tunex_enable", bool(data["tunex_enable"]), "") + stg += stmp("tuney_enable", bool(data["tuney_enable"]), "") + + stg += "\n" + stg += "EVT state\n" + + stg += stmp("evt_mode", data["timing_state"]["evt_mode"], "") + stg += stmp("evt_delay", data["timing_state"]["evt_delay"], "") + + stg += "\n" + stg += "BPMs state\n" + + stg += stmp("acq_rate", data["acq_rate"], "") + stg += stmp("nrsamples_pre", data["nrsamples_pre"], "") + stg += stmp("nrsamples_post", data["nrsamples_post"], "") + stg += stmp("switching_mode", data["switching_mode"], "") + stg += stmp("switching_frequency", data["switching_frequency"], "") + stg += stmp( + "trigbpm_source", data["timing_state"]["trigbpm_source"], "" + ) + stg += stmp( + "trigbpm_nrpulses", + data["timing_state"]["trigbpm_nrpulses"], + "", + ) + stg += stmp( + "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" + ) + + stg += "\n" + stg += "Pingers state\n" + + stg += stmp( + "trigpingh_state", data["timing_state"]["trigpingh_state"], "" + ) + stg += stmp( + "trigpingh_source", + data["timing_state"]["trigpingh_source"], + "", + ) + stg += stmp( + "trigpingh_delay", data["timing_state"]["trigpingh_delay"], "" + ) + # stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") + # stg += stmp( + # "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" + # ) + stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") + + stg += stmp( + "trigpingv_state", data["timing_state"]["trigpingv_state"], "" + ) + stg += stmp( + "trigpingv_source", + data["timing_state"]["trigpingv_source"], + "", + ) + stg += stmp( + "trigpingv_delay", data["timing_state"]["trigpingv_delay"], "" + ) + # stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") + # stg += stmp( + # "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" + # ) + stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") + return stg + + @property + def fname(self): + """.""" + return self._fname + + @fname.setter + def fname(self, val): + """.""" + self._fname = val + self.load_and_apply(val) + + def load_and_apply(self): + """Load data and copy often used data to class attributes.""" + keys = super().load_and_apply(self.fname) + if keys: + print("The following keys were not used:") + print(" ", str(keys)) + data = self.data + timestamp = _datetime.datetime.fromtimestamp(data["timestamp"]) + self.timestamp = timestamp + trajx, trajy = data["orbx"].copy() * 1e-3, data["orby"].copy() * 1e-3 + trajsum = data["sumdata"].copy() + # zero mean in samples dimension + trajx -= trajx.mean(axis=0)[None, :] + trajy -= trajy.mean(axis=0)[None, :] + self.trajx, self.trajy, self.trajsum = trajx, trajy, trajsum + self.tunex, self.tuney = data['tunex'], data['tuney'] + self.acq_rate = data["acq_rate"] + self.rf_freq = data["rf_frequency"] + self.sampling_freq = self.data["sampling_frequency"] + self.switching_freq = self.data["switching_frequency"] + return def linear_optics_analysis(self): """.""" From f62972d4591b344f1b5feb8a472055eabd6e9418 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 25 Apr 2024 16:07:36 -0300 Subject: [PATCH 047/144] COMMISS.TBT.ENH: add plotting method to analysis class --- apsuite/commisslib/measure_tbt_data.py | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2e787f5e..2870b386 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -501,6 +501,60 @@ def plot_traj_spectrum(): """.""" raise NotImplementedError + def plot_trajs(self, bpm_index=0, timescale=0): + """Plot trajectories and sum-signal at a given BPM and time-scale. + + Timescale 0 : Harmonic motion + Timescale 1 : Chromaticity decoherence modulation + Timescale 2 : Transverse decoherence modulations + """ + nr_pre = self.data['nrsamples_pre'] + nr_post = self.data['nrsamples_post'] + n = 5 + + if not timescale: + nmax_x, nmax_y = int(1 / self.tunex), int(1 / self.tuney) + slicex = (nr_pre - n, nr_pre + nmax_x + n + 1) + slicey = (nr_pre - n, nr_pre + nmax_y + n + 1) + slicesum = (nr_pre - n, nr_pre + max(nmax_x, nmax_y) + n + 1) + + nmax = int(1 / self.SYNCH_TUNE) + if timescale == 1: + slicex = (nr_pre - n, nr_pre + nmax + n + 1) + slicey = slicex + slicesum = slicex + + elif timescale == 2: + slicex = (nr_pre + nmax, nr_pre + nr_post + 1) + slicey = slicex + slicesum = slicex + + trajx = self.trajx[:, bpm_index] + trajy = self.trajy[:, bpm_index] + trajsum = self.trajsum[:, bpm_index] + + fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) + fig.suptitle( + f"{self.acq_rate.upper()} acquisition at BPM {bpm_index:3d}" + ) + ax[0].set_title("horizontal trajectory") + ax[0].plot(trajx, "-", mfc="none", color="blue") + ax[0].set_xlim(slicex) + ax[0].set_ylabel("position [mm]") + ax[1].set_title("vertical trajectory") + ax[1].plot(trajy, "-", mfc="none", color="red") + ax[1].set_xlim(slicey) + ax[1].sharey(ax[0]) + ax[2].set_title("BPM sum signal") + ax[2].plot(trajsum, "-", mfc="none", color="k") + ax[2].set_xlim(slicesum) + + ax[2].set_ylabel("sum [a.u.]") + + fig.supxlabel("turn index") + fig.tight_layout() + _mplt.show() + return fig, ax def _get_tune_guess(self, matrix): """.""" matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) From 59f2ec0e9e210593f7b8c8eb06f6962ca7c56a1b Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 25 Apr 2024 16:10:13 -0300 Subject: [PATCH 048/144] COMMISS.TBT.ENH: add plotting method for beta-beat and phase-errors & method to get the model betas and phases --- apsuite/commisslib/measure_tbt_data.py | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2870b386..37c62f77 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -8,6 +8,10 @@ from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU, Trigger +import pyaccel as _pa +from pymodels import si as _si + +from ..optics_analysis import ChromCorr as _ChromCorr, TuneCorr as _TuneCorr from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ AcqBPMsSignalsParams as _AcqBPMsSignalsParams @@ -555,6 +559,41 @@ def plot_trajs(self, bpm_index=0, timescale=0): fig.tight_layout() _mplt.show() return fig, ax + def plot_betabeat_and_phase_error( + self, beta_model, beta_meas, phase_model, phase_meas + ): + """.""" + fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) + fig.suptitle("beta and phase") + ax[0].plot(beta_model, "o-", label="Model", mfc="none") + ax[0].plot(beta_meas, "o--", label="Meas", mfc="none") + ax[0].set_ylabel("beta function") + ax[0].legend() + + beta_beat = (beta_model - beta_meas) / beta_meas + ax[1].plot( + beta_beat * 100, + "o-", + label=f"rms = {beta_beat.std()*100:.2f} %", + mfc="none", + ) + ax[1].set_ylabel("beta beating [%]") + ax[1].legend() + delta_mu = phase_model - phase_meas + ax[2].plot(phase_model, "o-", label="Model", mfc="none") + ax[2].plot(phase_meas, "o--", label="Meas", mfc="none") + ax[2].plot( + delta_mu, + "o-", + label=f"max abs err: {_np.abs(delta_mu.max()):.2f})", + mfc="none", + ) + ax[2].set_ylabel("phase advance [rad]") + ax[2].legend() + + fig.supxlabel("BPM") + fig.tight_layout() + _mplt.show() def _get_tune_guess(self, matrix): """.""" matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) @@ -619,3 +658,27 @@ def calculate_betafunc_and_action(self, amplitudes, nominal_beta): action /= _np.sum(amplitudes**2 * nominal_beta) beta = amplitudes**2 / action return beta, action + def _get_nominal_beta_and_phase(self, tunes=None, chroms=None): + """.""" + model = _si.create_accelerator() + model = _si.fitted_models.vertical_dispersion_and_coupling(model) + model.radiation_on = False + model.cavity_on = False + model.vchamber_on = False + + if tunes is not None: + tunecorr = _TuneCorr(model, acc="SI") + tunecorr.correct_parameters(goal_parameters=tunes) + + if chroms is not None: + chromcorr = _ChromCorr(model, acc="SI") + chromcorr.correct_parameters(goal_parameters=chroms) + + famdata = _si.get_family_data(model) + twiss, *_ = _pa.optics.calc_twiss(accelerator=model, indices="open") + bpms_idcs = _pa.lattice.flatten(famdata["BPM"]["index"]) + betax = twiss.betax[bpms_idcs] + phasex = twiss.mux[bpms_idcs] + betay = twiss.betay[bpms_idcs] + phasey = twiss.muy[bpms_idcs] + return betax, betay, phasex, phasey From f9f004326e9563acf0f658e75a99e2daaced9160 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 25 Apr 2024 16:13:21 -0300 Subject: [PATCH 049/144] COMMISS.TBT.ENH: fix formatting --- apsuite/commisslib/measure_tbt_data.py | 69 +++++++++++++++----------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 37c62f77..74a7c99a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -35,7 +35,7 @@ def __init__(self): self.vkick = None # [mrad] self.trigpingh_delay = None self.trigpingv_delay = None - self.magnets_timeout = 5. + self.magnets_timeout = 5.0 def __str__(self): """.""" @@ -174,9 +174,9 @@ def get_magnets_state(self): def set_magnets_state(self, state): """Restore magnets pwr and pulse states.""" timeout = self.params.magnets_timeout - pingh, pingv = self.devices['pingh'], self.devices['pingv'] + pingh, pingv = self.devices["pingh"], self.devices["pingv"] - if state['pingh_pwr']: + if state["pingh_pwr"]: pingh_ok = pingh.cmd_turn_on(timeout) else: pingh_ok = pingh.cmd_turn_off(timeout) @@ -186,7 +186,7 @@ def set_magnets_state(self, state): else: pingv_ok = pingv.cmd_turn_off(timeout) - if state['pingh_pulse']: + if state["pingh_pulse"]: pingh_ok = pingh.cmd_turn_on_pulse(timeout) else: pingh_ok = pingh.cmd_turn_off_pulse(timeout) @@ -221,15 +221,19 @@ def set_magnets_strength( t0 = _time.time() if hkick is not None: pingh_ok = pingh.set_strength( - hkick, tol=0.05 * abs(hkick), timeout=magnets_timeout, - wait_mon=False + hkick, + tol=0.05 * abs(hkick), + timeout=magnets_timeout, + wait_mon=False, ) elapsed_time = _time.time() - t0 magnets_timeout -= elapsed_time if vkick is not None: pingv_ok = pingv.set_strength( - vkick, tol=0.05 * abs(vkick), timeout=magnets_timeout, - wait_mon=False + vkick, + tol=0.05 * abs(vkick), + timeout=magnets_timeout, + wait_mon=False, ) if (not pingh_ok) or (not pingv_ok): @@ -241,32 +245,32 @@ def set_magnets_strength( def prepare_magnets(self): """.""" - print('Preparing magnets...') + print("Preparing magnets...") pingers2kick = self.params.pingers2kick state = dict() - if 'h' in pingers2kick: - state['pingh_pwr'] = 1 - state['pingh_pulse'] = 1 + if "h" in pingers2kick: + state["pingh_pwr"] = 1 + state["pingh_pulse"] = 1 else: - state['pingh_pwr'] = 0 - state['pingh_pulse'] = 0 + state["pingh_pwr"] = 0 + state["pingh_pulse"] = 0 - if 'v' in pingers2kick: - state['pingv_pwr'] = 1 - state['pingv_pulse'] = 1 + if "v" in pingers2kick: + state["pingv_pwr"] = 1 + state["pingv_pulse"] = 1 else: - state['pingv_pwr'] = 0 - state['pingv_pulse'] = 0 + state["pingv_pwr"] = 0 + state["pingv_pulse"] = 0 state_ok = self.set_magnets_state(state) if state_ok: hkick = self.params.hkick vkick = self.params.vkick - print('Setting magnets strengths...') + print("Setting magnets strengths...") return self.set_magnets_strength(hkick, vkick) else: - print('Magnets state not set.') + print("Magnets state not set.") return False def do_measurement(self): @@ -310,7 +314,7 @@ def do_measurement(self): print(f"\t pingv:{init_magnets_strength[1]:.4f} [mrad]") else: print("Magnets strengths succesfully restored to initial values.") - print('Measurement finished.') + print("Measurement finished.") def get_data(self): """.""" @@ -330,10 +334,16 @@ def get_default_fname(self): stg += "hkick_inactive_vkick_inactive" else: hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) - stg += f"hkick_{hkick:3d}_mrad" if "h" in pingers2kick else \ - "hkick_inactive" - stg += f"vkick_{vkick:3d}_mrad" if "h" in pingers2kick else \ - "vkick_inactive" + stg += ( + f"hkick_{hkick:3d}_mrad" + if "h" in pingers2kick + else "hkick_inactive" + ) + stg += ( + f"vkick_{vkick:3d}_mrad" + if "h" in pingers2kick + else "vkick_inactive" + ) tm = self.data["timestamp"] fmt = "%Y-%m-%d-%H-%M-%S" @@ -344,6 +354,7 @@ def get_default_fname(self): class TbTDataAnalysis(MeasureTbTData): """.""" + SYNCH_TUNE = 0.004713 def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" @@ -559,6 +570,7 @@ def plot_trajs(self, bpm_index=0, timescale=0): fig.tight_layout() _mplt.show() return fig, ax + def plot_betabeat_and_phase_error( self, beta_model, beta_meas, phase_model, phase_meas ): @@ -594,6 +606,7 @@ def plot_betabeat_and_phase_error( fig.supxlabel("BPM") fig.tight_layout() _mplt.show() + def _get_tune_guess(self, matrix): """.""" matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) @@ -622,9 +635,9 @@ def _get_amplitude_phase(self, fourier_components): def harmonic_tbt_model(self, ilist, *args, return_ravel=True): """Harmonic motion model for positions seen at a given BPM.""" - nbpms = (len(args)-1) // 2 + nbpms = (len(args) - 1) // 2 tune = args[0] - amps = _np.array(args[1:nbpms+1]) + amps = _np.array(args[1 : nbpms + 1]) phases = _np.array(args[-nbpms:]) x = ilist.reshape((-1, nbpms), order="F") wr = 2 * _np.pi * tune From 8b141b78da93043542b37bf32a3c163e4d0cb7f1 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 26 Apr 2024 20:03:14 -0300 Subject: [PATCH 050/144] COMMISS.TBT.ENH: add harmonic analysis (fitting) --- apsuite/commisslib/measure_tbt_data.py | 203 ++++++++++++++++++------- 1 file changed, 151 insertions(+), 52 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 74a7c99a..1b81146a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -368,8 +368,13 @@ def __init__(self, filename="", isonline=False): self.sampling_freq = None self.switching_freq = None self.rf_freq = None + self.trajx_turns_slice = None + self.trajy_turns_slice = None + self.model_optics = None + self.fitted_optics = None + self.pca_optics = None if self._fname: - self.load_and_apply() + self.load_and_apply(self._fname) def __str__(self): """.""" @@ -469,9 +474,9 @@ def fname(self, val): self._fname = val self.load_and_apply(val) - def load_and_apply(self): + def load_and_apply(self, fname): """Load data and copy often used data to class attributes.""" - keys = super().load_and_apply(self.fname) + keys = super().load_and_apply(fname) if keys: print("The following keys were not used:") print(" ", str(keys)) @@ -495,9 +500,83 @@ def linear_optics_analysis(self): """.""" raise NotImplementedError - def harmonic_analysis(self): + def harmonic_analysis(self, guess_tunes=False): """.""" - raise NotImplementedError + if guess_tunes: + tunex, tuney = self._guess_tune_from_dft() + else: + tunex, tuney = self.tunex, self.tuney + + fitted_optics = dict() + for pinger in self.params.pingers2kick: + if pinger == "h": + from_turn2turn = self.trajx_turns_slice + turns_slice = slice( + from_turn2turn[0], from_turn2turn[1] + 1, 1 + ) + traj = self.trajx[turns_slice, :].copy() + tune = tunex + label = "x" + else: + from_turn2turn = self.trajy_turns_slice + turns_slice = slice( + from_turn2turn[0], from_turn2turn[1] + 1, 1 + ) + traj = self.trajy[turns_slice, :].copy() + tune = tuney + label = "y" + + # TODO: adapt turns selection to grant integer nr of cycles + nbpms = traj.shape[-1] + + fourier = self._get_fourier_components(traj, tune) + amps, phases = self._get_amplitude_phase(fourier) + params_guess = _np.concatenate(([tune], amps, phases)).tolist() + n = self._get_independent_variables(from_turn2turn, nbpms) + + initial_fit = self.harmonic_tbt_model( + n, *params_guess, return_ravel=False + ) + + params_fit, params_error = self.fit_harmonic_model( + from_turn2turn, traj, *params_guess + ) + params_fit = params_fit.tolist() + final_fit = self.harmonic_tbt_model( + n, *params_fit, return_ravel=False + ) + + tune = params_fit[0] + amps = _np.array(params_fit[1 : nbpms + 1]) + phases_fit = _np.array(params_fit[-nbpms:]) + + self._get_nominal_optics(tunes=(tunex, tuney)) + beta_model = self.model_optics["beta"+label] + phases_model = self.model_optics["phase"+label] + beta_fit, action = self.calc_beta_and_action(amps, beta_model) + + # collect fitted data + fitted_optics["tune"+label] = tune + fitted_optics["tune_err"+label] = params_error[0] + fitted_optics["beta"+label] = beta_fit + # TODO: propagate amplitude errors to beta errors + fitted_optics["beta"+label+"_err"] = params_error[1 : nbpms + 1] + fitted_optics["phase"+label] = phases_fit + fitted_optics["phase"+label+"_err"] = params_error[-nbpms:] + fitted_optics["action"+label] = action + # TODO: propagate amplitude errors to action error + fitted_optics["traj"+label+"_init_fit"] = initial_fit + fitted_optics["traj"+label+"_final_fit"] = final_fit + + self.fitted_optics = fitted_optics + + self.plot_betabeat_and_phase_error( + beta_model, beta_fit, phases_model, phases_fit, + title=f"beta{label} & phase{label} - from harmonic model fit" + ) + + # TODO: compare fit with trajectory + self.fitted_optics = fitted_optics def principal_components_analysis(self): """.""" @@ -571,18 +650,25 @@ def plot_trajs(self, bpm_index=0, timescale=0): _mplt.show() return fig, ax + def plot_trajs_vs_fit(self): + """.""" + raise NotImplementedError + def plot_betabeat_and_phase_error( - self, beta_model, beta_meas, phase_model, phase_meas + self, beta_model, beta_meas, phase_model, phase_meas, title=None ): """.""" fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) - fig.suptitle("beta and phase") + if title is not None: + fig.suptitle(title) + else: + fig.suptitle("beta and phase") ax[0].plot(beta_model, "o-", label="Model", mfc="none") ax[0].plot(beta_meas, "o--", label="Meas", mfc="none") ax[0].set_ylabel("beta function") ax[0].legend() - beta_beat = (beta_model - beta_meas) / beta_meas + beta_beat = (beta_model - beta_meas) / beta_model ax[1].plot( beta_beat * 100, "o-", @@ -607,19 +693,25 @@ def plot_betabeat_and_phase_error( fig.tight_layout() _mplt.show() - def _get_tune_guess(self, matrix): + def _guess_tune_from_dft(self): """.""" - matrix_dft, tune = self.calc_spectrum(matrix, fs=1, axis=0) - tunes = tune[:, None] * _np.ones(matrix.shape[-1])[None, :] - peak_idcs = _np.abs(matrix_dft).argmax(axis=0) - tune_peaks = [tunes[idc, col] for col, idc in enumerate(peak_idcs)] - return _np.mean(tune_peaks) # tune guess + tune_guesses = list() + for plane in "xy": + traj = self.trajx if plane == "x" else self.trajy + traj_dft, tune = self.calc_spectrum(traj, fs=1, axis=0) + tunes = tune[:, None] * _np.ones(traj.shape[-1])[None, :] + peak_idcs = _np.abs(traj_dft).argmax(axis=0) + tune_peaks = [ + tunes[idc, col] for col, idc in enumerate(peak_idcs) + ] # peaking tune at each BPM's DFT. + tune_guesses.append(_np.mean(tune_peaks)) + return tune_guesses def _get_fourier_components(self, matrix, tune): """Performs linear fit for Fourier components amplitudes.""" - ilist = _np.arange(matrix.shape[0]) - cos = _np.cos(2 * _np.pi * tune * ilist) - sin = _np.sin(2 * _np.pi * tune * ilist) + n = _np.arange(matrix.shape[0]) + cos = _np.cos(2 * _np.pi * tune * n) + sin = _np.sin(2 * _np.pi * tune * n) coeff_mat = _np.concatenate((cos[:, None], sin[:, None]), axis=1) fourier_components = _np.linalg.pinv(coeff_mat) @ matrix @@ -631,38 +723,38 @@ def _get_amplitude_phase(self, fourier_components): phases = _np.arctan2( fourier_components[0, :], fourier_components[-1, :] ) - return amplitudes, phases + return amplitudes, _np.unwrap(phases) - def harmonic_tbt_model(self, ilist, *args, return_ravel=True): + def harmonic_tbt_model(self, n, *args, return_ravel=True): """Harmonic motion model for positions seen at a given BPM.""" nbpms = (len(args) - 1) // 2 tune = args[0] amps = _np.array(args[1 : nbpms + 1]) phases = _np.array(args[-nbpms:]) - x = ilist.reshape((-1, nbpms), order="F") + x = n.reshape((-1, nbpms), order="F") wr = 2 * _np.pi * tune mod = amps[None, :] * _np.sin(wr * x + phases[None, :]) if return_ravel: return mod.ravel() return mod - def fit_harmonic_model( - self, matrix, tune_guess, amplitudes_guess, phases_guess - ): - """Fits harmonic TbT model to data.""" - bpmdata = matrix.ravel() - nturns, nbpms = matrix.shape[0], matrix.shape[1] - xdata = _np.tile(_np.arange(nturns), nbpms).ravel() - p0 = _np.concatenate( - ([tune_guess], amplitudes_guess, phases_guess) - ).tolist() + def _get_independent_variables(self, from_turn2turn, nbpms): + arange = _np.arange(from_turn2turn[0], from_turn2turn[1] + 1, 1) + return _np.tile(arange, nbpms) + def fit_harmonic_model(self, from_turn2turn, traj, *params_guess): + """Fits harmonic TbT model to data.""" + bpmdata = traj.ravel() + nbpms = traj.shape[1] + xdata = self._get_independent_variables(from_turn2turn, nbpms) + # TODO: add exception in case fit fails popt, pcov = _curve_fit( - f=self.harmonic_tbt_model, xdata=xdata, ydata=bpmdata, p0=p0 + f=self.harmonic_tbt_model, xdata=xdata, ydata=bpmdata, + p0=params_guess ) return popt, _np.diagonal(pcov) - def calculate_betafunc_and_action(self, amplitudes, nominal_beta): + def calc_beta_and_action(self, amplitudes, nominal_beta): """Calculates beta function and betatron action. As in Eq. (9) of Ref. [1] @@ -671,27 +763,34 @@ def calculate_betafunc_and_action(self, amplitudes, nominal_beta): action /= _np.sum(amplitudes**2 * nominal_beta) beta = amplitudes**2 / action return beta, action - def _get_nominal_beta_and_phase(self, tunes=None, chroms=None): - """.""" - model = _si.create_accelerator() - model = _si.fitted_models.vertical_dispersion_and_coupling(model) - model.radiation_on = False - model.cavity_on = False - model.vchamber_on = False - - if tunes is not None: - tunecorr = _TuneCorr(model, acc="SI") - tunecorr.correct_parameters(goal_parameters=tunes) - if chroms is not None: + def _get_nominal_optics(self, tunes=None, chroms=None): + """.""" + if self.model_optics is None: + model_optics = dict() + model = _si.create_accelerator() + model = _si.fitted_models.vertical_dispersion_and_coupling(model) + model.radiation_on = False + model.cavity_on = False + model.vchamber_on = False + + if tunes is not None: + tunecorr = _TuneCorr(model, acc="SI") + tunex = tunes[0] + 49 + tuney = tunes[0] + 14 + tunecorr.correct_parameters(goal_parameters=(tunex, tuney)) + + chroms = (2.5, 2.5) chromcorr = _ChromCorr(model, acc="SI") chromcorr.correct_parameters(goal_parameters=chroms) - famdata = _si.get_family_data(model) - twiss, *_ = _pa.optics.calc_twiss(accelerator=model, indices="open") - bpms_idcs = _pa.lattice.flatten(famdata["BPM"]["index"]) - betax = twiss.betax[bpms_idcs] - phasex = twiss.mux[bpms_idcs] - betay = twiss.betay[bpms_idcs] - phasey = twiss.muy[bpms_idcs] - return betax, betay, phasex, phasey + famdata = _si.get_family_data(model) + twiss, *_ = _pa.optics.calc_twiss( + accelerator=model, indices="open" + ) + bpms_idcs = _pa.lattice.flatten(famdata["BPM"]["index"]) + model_optics["betax"] = twiss.betax[bpms_idcs] + model_optics["phasex"] = twiss.mux[bpms_idcs] + model_optics["betay"] = twiss.betay[bpms_idcs] + model_optics["phasey"] = twiss.muy[bpms_idcs] + self.model_optics = model_optics From cf74983a444c91b8aa7feb0ea068921b2063daba Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 26 Apr 2024 20:03:34 -0300 Subject: [PATCH 051/144] COMMISS.TBT.ENH: add PCA analysis --- apsuite/commisslib/measure_tbt_data.py | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1b81146a..6dc59a0b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -498,6 +498,8 @@ def load_and_apply(self, fname): def linear_optics_analysis(self): """.""" + self.harmonic_analysis() + self.principal_components_analysis() raise NotImplementedError def harmonic_analysis(self, guess_tunes=False): @@ -580,7 +582,21 @@ def harmonic_analysis(self, guess_tunes=False): def principal_components_analysis(self): """.""" - raise NotImplementedError + pca_optics = dict() + for pinger in self.params.pingers2kick: + tunes = self.tunex, self.tuney + if self.model_optics is None: + self._get_nominal_optics(tunes) + beta_model = self.model_optics["beta"+pinger] + phase_model = self.model_optics["phase"+pinger] + beta_pca, phase_pca = self.calc_beta_and_phase_with_pca(self.trajx) + self.plot_betabeat_and_phase_error( + beta_model, beta_pca, phase_model, phase_pca, + title=f'beta{pinger} & phase{pinger} - from PCA' + ) + pca_optics["beta"+pinger] = beta_pca + pca_optics["phase"+pinger] = phase_pca + self.pca_optics = pca_optics def independent_component_analysis(self): """.""" @@ -764,6 +780,19 @@ def calc_beta_and_action(self, amplitudes, nominal_beta): beta = amplitudes**2 / action return beta, action + def calc_beta_and_phase_with_pca(self, matrix, beta_model): + """.""" + _, svals, vtmat = self.calc_svd(matrix, full_matrices=False) + beta_meas = ( + svals[0] ** 2 * vtmat[0, :] ** 2 + svals[1] ** 2 * vtmat[1, :] ** 2 + ) + beta_meas /= _np.std(beta_meas) / _np.std(beta_model) + phase_meas = _np.arctan2( + svals[1] * vtmat[1, :], svals[0] * vtmat[0, :] + ) + phase_meas = _np.abs(_np.unwrap(phase_meas)) # why the abs? + return beta_meas, phase_meas + def _get_nominal_optics(self, tunes=None, chroms=None): """.""" if self.model_optics is None: From 89ac141aca43656dccd35bb7e8283812a300d61b Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Sun, 28 Apr 2024 20:47:41 -0300 Subject: [PATCH 052/144] COMMISS.TBT.ENH: do not change manges pwr_state --- apsuite/commisslib/measure_tbt_data.py | 26 +++++++++++++------------- apsuite/dynap/phase_space.py | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 6dc59a0b..b8849fe4 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -176,15 +176,15 @@ def set_magnets_state(self, state): timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] - if state["pingh_pwr"]: - pingh_ok = pingh.cmd_turn_on(timeout) - else: - pingh_ok = pingh.cmd_turn_off(timeout) + # if state["pingh_pwr"]: + # pingh_ok = pingh.cmd_turn_on(timeout) + # else: + # pingh_ok = pingh.cmd_turn_off(timeout) - if state["pingv_pwr"]: - pingv_ok = pingv.cmd_turn_on(timeout) - else: - pingv_ok = pingv.cmd_turn_off(timeout) + # if state["pingv_pwr"]: + # pingv_ok = pingv.cmd_turn_on(timeout) + # else: + # pingv_ok = pingv.cmd_turn_off(timeout) if state["pingh_pulse"]: pingh_ok = pingh.cmd_turn_on_pulse(timeout) @@ -249,17 +249,17 @@ def prepare_magnets(self): pingers2kick = self.params.pingers2kick state = dict() if "h" in pingers2kick: - state["pingh_pwr"] = 1 + # state["pingh_pwr"] = 1 disable pulsing only state["pingh_pulse"] = 1 else: - state["pingh_pwr"] = 0 + # state["pingh_pwr"] = 0 state["pingh_pulse"] = 0 if "v" in pingers2kick: - state["pingv_pwr"] = 1 + # state["pingv_pwr"] = 1 state["pingv_pulse"] = 1 else: - state["pingv_pwr"] = 0 + # state["pingv_pwr"] = 0 state["pingv_pulse"] = 0 state_ok = self.set_magnets_state(state) @@ -439,7 +439,7 @@ def __str__(self): stg += stmp( "trigpingh_delay", data["timing_state"]["trigpingh_delay"], "" ) - # stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") + # stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") # commented because last measurement had a problem here # stg += stmp( # "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" # ) diff --git a/apsuite/dynap/phase_space.py b/apsuite/dynap/phase_space.py index ab6e02ad..893f4ebd 100644 --- a/apsuite/dynap/phase_space.py +++ b/apsuite/dynap/phase_space.py @@ -206,10 +206,10 @@ def make_figure( routx[0].ravel()*1e3, routx[1].ravel()*1e3, c='b', s=2) - phx.scatter( - routy[0].ravel()*1e3, - routy[1].ravel()*1e3, - c='r', s=2) + #phx.scatter( + # routy[0].ravel()*1e3, + # routy[1].ravel()*1e3, + # c='r', s=2) phx.set_xlabel('X [mm]') phx.set_ylabel("X' [mrad]") @@ -217,10 +217,10 @@ def make_figure( routy[2].ravel()*1e3, routy[3].ravel()*1e3, c='r', s=2) - phy.scatter( - routx[2].ravel()*1e3, - routx[3].ravel()*1e3, - c='b', s=2) + #phy.scatter( + # routx[2].ravel()*1e3, + # routx[3].ravel()*1e3, + # c='b', s=2) phy.set_xlabel('Y [mm]') phy.set_ylabel("Y' [mrad]") From 197791bca97ad915be2647d547a522e09e16050c Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Sun, 28 Apr 2024 21:09:31 -0300 Subject: [PATCH 053/144] COMMISS.TBT.ENH: do not attempt setting mags and doing the meas if `prepare_timing` fails --- apsuite/commisslib/measure_tbt_data.py | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b8849fe4..7c7008b2 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -283,21 +283,20 @@ def do_measurement(self): timing_ok = self.prepare_timing() if timing_ok: print("Timing configs. were succesfully set") - mags_ok = self.prepare_magnets() # gets strengths from params - if mags_ok: - print("Magnets ready.") - if mags_ok and timing_ok: - try: - self.acquire_data() - # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - self.data["current_before"] = current_before - self.data["current_after"] = self.data.pop("stored_current") - self.data["init_magnets_strengths"] = init_magnets_strength - print("Acquisition was succesful.") - except Exception as e: - print(f"An error occurred during acquisition: {e}") + mags_ok = self.prepare_magnets() # gets strengths from params + if mags_ok: + print("Magnets ready.") + try: + self.acquire_data() + # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + self.data["current_before"] = current_before + self.data["current_after"] = self.data.pop("stored_current") + self.data["init_magnets_strengths"] = init_magnets_strength + print("Acquisition was succesful.") + except Exception as e: + print(f"An error occurred during acquisition: {e}") else: print("Did not measure. Restoring magnets & timing initial state.") timing_ok = self.recover_timing_state(init_timing_state) From f06ef34469e74d4111023a45d7c66a176eaa3f56 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Sun, 28 Apr 2024 21:13:06 -0300 Subject: [PATCH 054/144] TBT.COMMISS.ENH: include the possibility of failing at restoring magnets state --- apsuite/commisslib/measure_tbt_data.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 7c7008b2..2b148b2f 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -303,16 +303,19 @@ def do_measurement(self): if not timing_ok: print("Timing was not restored to initial state.") mags_ok = self.set_magnets_state(init_magnets_state) - mags_ok = self.set_magnets_strength(init_magnets_strength) # restore + mags_ok = mags_ok and self.set_magnets_strength( + init_magnets_strength + ) # restore if not mags_ok: - msg = "Magnets strengths were not restored to initial values." + msg = "Magnets state or strengths were not restored." msg += "Restore manually." print(msg) + print(init_magnets_state) print("Initial strengths:") print(f"\t pingh:{init_magnets_strength[0]:.4f} [mrad]") print(f"\t pingv:{init_magnets_strength[1]:.4f} [mrad]") else: - print("Magnets strengths succesfully restored to initial values.") + print("Magnets state & strengths succesfully restored.") print("Measurement finished.") def get_data(self): From 8ef517c26b42e0caea61ffa1b72a99f9e2c6f976 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 29 Apr 2024 09:20:21 -0300 Subject: [PATCH 055/144] COMMISS.TBT.BUG: fix default naming to deal with `None`-type strengths --- apsuite/commisslib/measure_tbt_data.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2b148b2f..2185e0b6 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -332,21 +332,16 @@ def get_default_fname(self): stg += f"_{prms.acq_rate}_rate" pingers2kick = prms.pingers2kick - if pingers2kick == "none": - stg += "hkick_inactive_vkick_inactive" - else: - hkick, vkick = int(round(prms.hkick)), int(round(prms.vkick)) - stg += ( - f"hkick_{hkick:3d}_mrad" - if "h" in pingers2kick - else "hkick_inactive" - ) - stg += ( - f"vkick_{vkick:3d}_mrad" - if "h" in pingers2kick - else "vkick_inactive" - ) - + stg += ( + f"hkick_{prms.hkick:.4f}_mrad".replace(".", "p") + if "h" in pingers2kick + else "hkick_inactive" + ) + stg += ( + f"vkick_{prms.vkick:.4f}_mrad".replace(".", "p") + if "v" in pingers2kick + else "vkick_inactive" + ) tm = self.data["timestamp"] fmt = "%Y-%m-%d-%H-%M-%S" tmstp = _datetime.datetime.fromtimestamp(tm).strftime(fmt) From d3c93ad073d33e01e4d9b93a5a6e00214a95e90c Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 29 Apr 2024 09:37:50 -0300 Subject: [PATCH 056/144] COMMISS.TBT.BUG: fix restore strengths --- apsuite/commisslib/measure_tbt_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2185e0b6..5f7eab06 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -304,7 +304,7 @@ def do_measurement(self): print("Timing was not restored to initial state.") mags_ok = self.set_magnets_state(init_magnets_state) mags_ok = mags_ok and self.set_magnets_strength( - init_magnets_strength + *init_magnets_strength ) # restore if not mags_ok: msg = "Magnets state or strengths were not restored." From 92eec5824e23b298de70e128d2e9e7e551b2f123 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 29 Apr 2024 09:39:05 -0300 Subject: [PATCH 057/144] COMMISS.TBT.ANLY: default chromaticity if `chroms == None` --- apsuite/commisslib/measure_tbt_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 5f7eab06..1f282435 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -806,7 +806,7 @@ def _get_nominal_optics(self, tunes=None, chroms=None): tuney = tunes[0] + 14 tunecorr.correct_parameters(goal_parameters=(tunex, tuney)) - chroms = (2.5, 2.5) + chroms = (2.5, 2.5) if chroms is None else chroms chromcorr = _ChromCorr(model, acc="SI") chromcorr.correct_parameters(goal_parameters=chroms) From b5e224603c735dac58b77913e9696de2f5ec9a49 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 29 Apr 2024 09:40:02 -0300 Subject: [PATCH 058/144] COMMISS.TBT.MEAS: minor formating fixes --- apsuite/commisslib/measure_tbt_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1f282435..c53fb5e6 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -292,7 +292,9 @@ def do_measurement(self): # such as timestamps tunes, stored current # rf frequency, acq rate, nr samples, etc. self.data["current_before"] = current_before - self.data["current_after"] = self.data.pop("stored_current") + self.data["current_after"] = self.data.pop( + "stored_current" + ) self.data["init_magnets_strengths"] = init_magnets_strength print("Acquisition was succesful.") except Exception as e: From 1302549ed4b6546d6a41da698f3e82189a3da903 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 29 Apr 2024 13:59:40 -0300 Subject: [PATCH 059/144] COMMISS.TBT.MEAS.ENH: wait magnets mon instead of rb --- apsuite/commisslib/measure_tbt_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c53fb5e6..794fcf88 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -204,13 +204,13 @@ def set_magnets_strength( """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is not None: - pingh.set_strength(hkick, timeout=0, wait_mon=False) + pingh.set_strength(hkick, timeout=0, wait_mon=True) else: print("pingh not changed (None-type strength)") pingh_ok = True if vkick is not None: - pingv.set_strength(vkick, timeout=0, wait_mon=False) + pingv.set_strength(vkick, timeout=0, wait_mon=True) else: print("pingv not changed (None-type strength)") pingv_ok = True @@ -224,7 +224,7 @@ def set_magnets_strength( hkick, tol=0.05 * abs(hkick), timeout=magnets_timeout, - wait_mon=False, + wait_mon=True, ) elapsed_time = _time.time() - t0 magnets_timeout -= elapsed_time @@ -233,7 +233,7 @@ def set_magnets_strength( vkick, tol=0.05 * abs(vkick), timeout=magnets_timeout, - wait_mon=False, + wait_mon=True, ) if (not pingh_ok) or (not pingv_ok): @@ -353,7 +353,7 @@ def get_default_fname(self): class TbTDataAnalysis(MeasureTbTData): """.""" - SYNCH_TUNE = 0.004713 + SYNCH_TUNE = 0.004713 # check this def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" From df8c325220e9b4b4d683065c7092d0b941d87901 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 6 May 2024 12:11:39 -0300 Subject: [PATCH 060/144] COMMISS.TBT.ENH: minor improvements in PCA analysis --- apsuite/commisslib/measure_tbt_data.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 794fcf88..fd072781 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -583,18 +583,22 @@ def principal_components_analysis(self): """.""" pca_optics = dict() for pinger in self.params.pingers2kick: + traj = self.trajx if pinger == "h" else self.trajy + label = "x" if pinger == "h" else "y" tunes = self.tunex, self.tuney if self.model_optics is None: self._get_nominal_optics(tunes) - beta_model = self.model_optics["beta"+pinger] - phase_model = self.model_optics["phase"+pinger] - beta_pca, phase_pca = self.calc_beta_and_phase_with_pca(self.trajx) + beta_model = self.model_optics["beta"+label] + phase_model = self.model_optics["phase"+label] + beta_pca, phase_pca = self.calc_beta_and_phase_with_pca( + traj, beta_model + ) self.plot_betabeat_and_phase_error( beta_model, beta_pca, phase_model, phase_pca, - title=f'beta{pinger} & phase{pinger} - from PCA' + title=f'beta{label} & phase{label} - from PCA' ) - pca_optics["beta"+pinger] = beta_pca - pca_optics["phase"+pinger] = phase_pca + pca_optics["beta"+label] = beta_pca + pca_optics["phase"+label] = phase_pca self.pca_optics = pca_optics def independent_component_analysis(self): From 7f68880e06afb30f55e594ce045a5be35988b1ab Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 6 May 2024 16:56:57 -0300 Subject: [PATCH 061/144] COMMISS.TBT.MEAS.ENH: include magnets strengths in magnets state; unify setting of strenghts and states --- apsuite/commisslib/measure_tbt_data.py | 94 +++++++++++++++----------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index fd072781..f79f5baf 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -165,37 +165,65 @@ def get_magnets_state(self): """.""" state = dict() pingh, pingv = self.devices["pingh"], self.devices["pingv"] + pingh_str, pingv_str = self.get_magnets_strength() state["pingh_pwr"] = pingh.pwrstate state["pingv_pwr"] = pingv.pwrstate state["pingh_pulse"] = pingh.pulse state["pingv_pulse"] = pingv.pulse + state["pingh_strength"] = pingh_str + state["pingv_strength"] = pingv_str + return state def set_magnets_state(self, state): - """Restore magnets pwr and pulse states.""" + """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] - # if state["pingh_pwr"]: - # pingh_ok = pingh.cmd_turn_on(timeout) - # else: - # pingh_ok = pingh.cmd_turn_off(timeout) + # Power and strengths + if state["pingh_pwr"]: + pingh_ok = pingh.turn_on(timeout=self.params.magnets_timeout) + if pingh_ok: + pingh_ok = self.set_magnets_strength( + hkick=state["pingh_strength"] + ) + else: + print("Failed at turning-on pingh.") + else: + pingh_ok = pingh.turn_off(timeout=self.params.magnets_timeout) - # if state["pingv_pwr"]: - # pingv_ok = pingv.cmd_turn_on(timeout) - # else: - # pingv_ok = pingv.cmd_turn_off(timeout) + if state["pingv_pwr"]: + pingv_ok = pingv.turn_on(timeout=self.params.magnets_timeout) + if pingv_ok: + pingv_ok = self.set_magnets_strength( + vkick=state["pingv_strength"] + ) + else: + print("Failed at turning-on pingv.") + else: + pingv_ok = pingv.turn_off(timeout=self.params.magnets_timeout) + if pingh_ok and pingv_ok: + print("Magnets power-state and strengths set.") + else: + print("Failed at setting magnets power-state and strengths") + + print("Changing pulse-state") + + # Pulse state if state["pingh_pulse"]: - pingh_ok = pingh.cmd_turn_on_pulse(timeout) + pingh_ok = pingh_ok and pingh.cmd_turn_on_pulse(timeout) else: - pingh_ok = pingh.cmd_turn_off_pulse(timeout) + pingh_ok = pingh_ok and pingh.cmd_turn_off_pulse(timeout) + msg = "pingh pulse set" if pingh_ok else "pingh pulse not set" + print("\t"+msg) if state["pingv_pulse"]: - pingv_ok = pingv.cmd_turn_on_pulse(timeout) + pingv_ok = pingv_ok and pingv.cmd_turn_on_pulse(timeout) else: - pingv_ok = pingv.cmd_turn_off_pulse(timeout) - + pingv_ok = pingv_ok and pingv.cmd_turn_off_pulse(timeout) + msg = "pingv pulse set" if pingv_ok else "pingv pulse not set" + print("\t"+msg) return pingh_ok and pingv_ok def set_magnets_strength( @@ -239,7 +267,8 @@ def set_magnets_strength( if (not pingh_ok) or (not pingv_ok): bad_pingers = "pingh " if not pingh_ok else "" bad_pingers += "pingv" if not pingv_ok else "" - print(f"Some magnets were not set.\n\tBad pingers: {bad_pingers}") + msg = "Some magnets strengths were not set.\n" + msg += f"\t Bad pingers: {bad_pingers}" return False return True @@ -249,40 +278,33 @@ def prepare_magnets(self): pingers2kick = self.params.pingers2kick state = dict() if "h" in pingers2kick: - # state["pingh_pwr"] = 1 disable pulsing only + state["pingh_pwr"] = 1 # always make sure its on state["pingh_pulse"] = 1 else: - # state["pingh_pwr"] = 0 - state["pingh_pulse"] = 0 + # state["pingh_pwr"] = 0 # but will not be turning-off. + state["pingh_pulse"] = 0 # only changing pulse-sts if "v" in pingers2kick: - # state["pingv_pwr"] = 1 + state["pingv_pwr"] = 1 state["pingv_pulse"] = 1 else: # state["pingv_pwr"] = 0 state["pingv_pulse"] = 0 - state_ok = self.set_magnets_state(state) + state["pingh_strength"] = self.params.hkick + state["pingv_strength"] = self.params.vkick - if state_ok: - hkick = self.params.hkick - vkick = self.params.vkick - print("Setting magnets strengths...") - return self.set_magnets_strength(hkick, vkick) - else: - print("Magnets state not set.") - return False + return self.set_magnets_state(state) def do_measurement(self): """.""" currinfo = self.devices["currinfo"] init_timing_state = self.get_timing_state() init_magnets_state = self.get_magnets_state() - init_magnets_strength = self.get_magnets_strength() current_before = currinfo.current timing_ok = self.prepare_timing() if timing_ok: - print("Timing configs. were succesfully set") + print("Timing configs. were successfully set") mags_ok = self.prepare_magnets() # gets strengths from params if mags_ok: print("Magnets ready.") @@ -295,8 +317,8 @@ def do_measurement(self): self.data["current_after"] = self.data.pop( "stored_current" ) - self.data["init_magnets_strengths"] = init_magnets_strength - print("Acquisition was succesful.") + self.data["init_magnets_state"] = init_magnets_state + print("Acquisition was successful.") except Exception as e: print(f"An error occurred during acquisition: {e}") else: @@ -305,19 +327,13 @@ def do_measurement(self): if not timing_ok: print("Timing was not restored to initial state.") mags_ok = self.set_magnets_state(init_magnets_state) - mags_ok = mags_ok and self.set_magnets_strength( - *init_magnets_strength - ) # restore if not mags_ok: msg = "Magnets state or strengths were not restored." msg += "Restore manually." print(msg) print(init_magnets_state) - print("Initial strengths:") - print(f"\t pingh:{init_magnets_strength[0]:.4f} [mrad]") - print(f"\t pingv:{init_magnets_strength[1]:.4f} [mrad]") else: - print("Magnets state & strengths succesfully restored.") + print("Magnets state & strengths successfully restored.") print("Measurement finished.") def get_data(self): From cd9ef79b491929305da2caa7159113794d603e6a Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Wed, 8 May 2024 16:54:41 -0300 Subject: [PATCH 062/144] COMMISS.TBT.MEAS.ENH: fixes during last online tests --- apsuite/commisslib/measure_tbt_data.py | 49 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f79f5baf..71d375c5 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -175,33 +175,45 @@ def get_magnets_state(self): return state - def set_magnets_state(self, state): + def set_magnets_state(self, state, wait_mon=True): """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] # Power and strengths - if state["pingh_pwr"]: - pingh_ok = pingh.turn_on(timeout=self.params.magnets_timeout) + if state.get("pingh_pwr", False): + pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) if pingh_ok: pingh_ok = self.set_magnets_strength( - hkick=state["pingh_strength"] + hkick=state["pingh_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingh.") else: - pingh_ok = pingh.turn_off(timeout=self.params.magnets_timeout) + pingh_ok = self.set_magnets_strength( + hkick=state["pingh_strength"], wait_mon=wait_mon + ) + if pingh_ok: + pingh_ok = pingh.cmd_turn_off( + timeout=self.params.magnets_timeout + ) - if state["pingv_pwr"]: - pingv_ok = pingv.turn_on(timeout=self.params.magnets_timeout) + if state.get("pingv_pwr", False): + pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) if pingv_ok: pingv_ok = self.set_magnets_strength( - vkick=state["pingv_strength"] + vkick=state["pingv_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingv.") else: - pingv_ok = pingv.turn_off(timeout=self.params.magnets_timeout) + pingv_ok = self.set_magnets_strength( + vkick=state["pingv_strength"], wait_mon=wait_mon + ) + if pingv_ok: + pingv_ok = pingv.cmd_turn_off( + timeout=self.params.magnets_timeout + ) if pingh_ok and pingv_ok: print("Magnets power-state and strengths set.") @@ -224,21 +236,22 @@ def set_magnets_state(self, state): pingv_ok = pingv_ok and pingv.cmd_turn_off_pulse(timeout) msg = "pingv pulse set" if pingv_ok else "pingv pulse not set" print("\t"+msg) + return pingh_ok and pingv_ok def set_magnets_strength( - self, hkick=None, vkick=None, magnets_timeout=None + self, hkick=None, vkick=None, magnets_timeout=None, wait_mon=True ): """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is not None: - pingh.set_strength(hkick, timeout=0, wait_mon=True) + pingh.set_strength(hkick, timeout=0, wait_mon=wait_mon) else: print("pingh not changed (None-type strength)") pingh_ok = True if vkick is not None: - pingv.set_strength(vkick, timeout=0, wait_mon=True) + pingv.set_strength(vkick, timeout=0, wait_mon=wait_mon) else: print("pingv not changed (None-type strength)") pingv_ok = True @@ -247,21 +260,24 @@ def set_magnets_strength( if magnets_timeout is None: magnets_timeout = self.params.magnets_timeout t0 = _time.time() + if hkick is not None: pingh_ok = pingh.set_strength( hkick, tol=0.05 * abs(hkick), timeout=magnets_timeout, - wait_mon=True, + wait_mon=wait_mon, ) + elapsed_time = _time.time() - t0 magnets_timeout -= elapsed_time + if vkick is not None: pingv_ok = pingv.set_strength( vkick, tol=0.05 * abs(vkick), timeout=magnets_timeout, - wait_mon=True, + wait_mon=wait_mon, ) if (not pingh_ok) or (not pingv_ok): @@ -270,6 +286,7 @@ def set_magnets_strength( msg = "Some magnets strengths were not set.\n" msg += f"\t Bad pingers: {bad_pingers}" return False + return True def prepare_magnets(self): @@ -323,10 +340,10 @@ def do_measurement(self): print(f"An error occurred during acquisition: {e}") else: print("Did not measure. Restoring magnets & timing initial state.") - timing_ok = self.recover_timing_state(init_timing_state) + timing_ok = self.prepare_timing(init_timing_state) if not timing_ok: print("Timing was not restored to initial state.") - mags_ok = self.set_magnets_state(init_magnets_state) + mags_ok = self.set_magnets_state(init_magnets_state, wait_mon=False) if not mags_ok: msg = "Magnets state or strengths were not restored." msg += "Restore manually." From 7e8c6b2c09b5a7fe73e154a0d7cec661996a8947 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 10 May 2024 14:27:21 -0300 Subject: [PATCH 063/144] COMMISS.TBT.ANLY.ENH: improve plot methods --- apsuite/commisslib/measure_tbt_data.py | 67 ++++++++++++++++++-------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 71d375c5..00afe06b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -7,6 +7,7 @@ import numpy as _np from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU, Trigger +from siriuspy.sofb.csdev import SOFBFactory import pyaccel as _pa from pymodels import si as _si @@ -16,8 +17,14 @@ AcqBPMsSignalsParams as _AcqBPMsSignalsParams +from matplotlib import rcParams +rcParams.update({ + 'font.size': 14, 'grid.alpha': 0.5, 'grid.linestyle': '--', + 'axes.grid': True, 'text.usetex': False}) + class TbTDataParams(_AcqBPMsSignalsParams): """.""" + BPMS_NAMES = SOFBFactory.create("SI").bpm_names def __init__(self): """.""" @@ -606,7 +613,7 @@ def harmonic_analysis(self, guess_tunes=False): self.plot_betabeat_and_phase_error( beta_model, beta_fit, phases_model, phases_fit, - title=f"beta{label} & phase{label} - from harmonic model fit" + title=f"Sinusoidal fit analysis - beta{label} & phase{label}" ) # TODO: compare fit with trajectory @@ -628,7 +635,7 @@ def principal_components_analysis(self): ) self.plot_betabeat_and_phase_error( beta_model, beta_pca, phase_model, phase_pca, - title=f'beta{label} & phase{label} - from PCA' + title=f"PCA Analysis: beta{label} & phase{label}" ) pca_optics["beta"+label] = beta_pca pca_optics["phase"+label] = phase_pca @@ -680,8 +687,9 @@ def plot_trajs(self, bpm_index=0, timescale=0): trajsum = self.trajsum[:, bpm_index] fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) + name = self.params.BPMS_NAMES[bpm_index] fig.suptitle( - f"{self.acq_rate.upper()} acquisition at BPM {bpm_index:3d}" + f"{self.acq_rate.upper()} acq. at BPM {bpm_index:3d} ({name})" ) ax[0].set_title("horizontal trajectory") ax[0].plot(trajx, "-", mfc="none", color="blue") @@ -707,41 +715,60 @@ def plot_trajs_vs_fit(self): raise NotImplementedError def plot_betabeat_and_phase_error( - self, beta_model, beta_meas, phase_model, phase_meas, title=None + self, beta_model, beta_meas, phase_model, phase_meas, title=None, + plot_comparison=False ): """.""" - fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) + if plot_comparison: + fig, axs = _mplt.subplots(2, 2, figsize=(15, 10)) + else: + fig, axs = _mplt.subplots(1, 2, figsize=(15, 5)) + if title is not None: fig.suptitle(title) else: fig.suptitle("beta and phase") - ax[0].plot(beta_model, "o-", label="Model", mfc="none") - ax[0].plot(beta_meas, "o--", label="Meas", mfc="none") - ax[0].set_ylabel("beta function") - ax[0].legend() + # Beta plots + if plot_comparison: + ax_beta = axs[0, 0] + ax_beta.plot(beta_model, "o-", label="Model", mfc="none") + ax_beta.plot(beta_meas, "o--", label="Meas", mfc="none") + ax_beta.set_ylabel("beta function") + ax_beta.legend() + + # Beta beating plot + ax_beat = axs[0, 1] if plot_comparison else axs[0] beta_beat = (beta_model - beta_meas) / beta_model - ax[1].plot( + ax_beat.plot( beta_beat * 100, "o-", label=f"rms = {beta_beat.std()*100:.2f} %", mfc="none", ) - ax[1].set_ylabel("beta beating [%]") - ax[1].legend() + ax_beat.set_ylabel("beta beating [%]") + ax_beat.legend() + + # Phase plots + if plot_comparison: + ax_phase = axs[1, 0] + ax_phase.plot(phase_model, "o-", label="Model", mfc="none") + ax_phase.plot(phase_meas, "o--", label="Meas", mfc="none") + ax_phase.set_ylabel("phase advance [rad]") + ax_phase.legend() + + ax_phase_err = axs[1, 1] if plot_comparison else axs[1] delta_mu = phase_model - phase_meas - ax[2].plot(phase_model, "o-", label="Model", mfc="none") - ax[2].plot(phase_meas, "o--", label="Meas", mfc="none") - ax[2].plot( + ax_phase_err.plot( delta_mu, "o-", - label=f"max abs err: {_np.abs(delta_mu.max()):.2f})", - mfc="none", + label=f"avg. error = {delta_mu.mean():.2f}", + mfc="none" ) - ax[2].set_ylabel("phase advance [rad]") - ax[2].legend() + ax_phase_err.set_ylabel("phase error [rad]") + ax_phase_err.legend() - fig.supxlabel("BPM") + fig.supxlabel("BPM index") fig.tight_layout() _mplt.show() From c7eeab472f3e91e1553711c2d7509dec8603dc83 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 10 May 2024 14:28:50 -0300 Subject: [PATCH 064/144] undo changes in `phase_space.py` committed & pushed by accident --- apsuite/dynap/phase_space.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apsuite/dynap/phase_space.py b/apsuite/dynap/phase_space.py index 893f4ebd..981b91d1 100644 --- a/apsuite/dynap/phase_space.py +++ b/apsuite/dynap/phase_space.py @@ -206,10 +206,10 @@ def make_figure( routx[0].ravel()*1e3, routx[1].ravel()*1e3, c='b', s=2) - #phx.scatter( - # routy[0].ravel()*1e3, - # routy[1].ravel()*1e3, - # c='r', s=2) + phx.scatter( + routy[0].ravel()*1e3, + routy[1].ravel()*1e3, + c='r', s=2) phx.set_xlabel('X [mm]') phx.set_ylabel("X' [mrad]") @@ -217,10 +217,10 @@ def make_figure( routy[2].ravel()*1e3, routy[3].ravel()*1e3, c='r', s=2) - #phy.scatter( - # routx[2].ravel()*1e3, - # routx[3].ravel()*1e3, - # c='b', s=2) + phy.scatter( + routx[2].ravel()*1e3, + routx[3].ravel()*1e3, + c='b', s=2) phy.set_xlabel('Y [mm]') phy.set_ylabel("Y' [mrad]") From 5499162e60527b51b25f475cd25b79fd92817c8f Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 10 May 2024 14:48:12 -0300 Subject: [PATCH 065/144] again to undo stuff done with `phase_space.py` --- apsuite/dynap/phase_space.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apsuite/dynap/phase_space.py b/apsuite/dynap/phase_space.py index 981b91d1..ab6e02ad 100644 --- a/apsuite/dynap/phase_space.py +++ b/apsuite/dynap/phase_space.py @@ -207,9 +207,9 @@ def make_figure( routx[1].ravel()*1e3, c='b', s=2) phx.scatter( - routy[0].ravel()*1e3, - routy[1].ravel()*1e3, - c='r', s=2) + routy[0].ravel()*1e3, + routy[1].ravel()*1e3, + c='r', s=2) phx.set_xlabel('X [mm]') phx.set_ylabel("X' [mrad]") @@ -218,9 +218,9 @@ def make_figure( routy[3].ravel()*1e3, c='r', s=2) phy.scatter( - routx[2].ravel()*1e3, - routx[3].ravel()*1e3, - c='b', s=2) + routx[2].ravel()*1e3, + routx[3].ravel()*1e3, + c='b', s=2) phy.set_xlabel('Y [mm]') phy.set_ylabel("Y' [mrad]") From 554db799b8801278aab9e2edca59ee17b9100e49 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 10 May 2024 14:56:10 -0300 Subject: [PATCH 066/144] COMMISS.TBT.ENH: remove RC params configs --- apsuite/commisslib/measure_tbt_data.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 00afe06b..1c11b7bd 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -17,11 +17,6 @@ AcqBPMsSignalsParams as _AcqBPMsSignalsParams -from matplotlib import rcParams -rcParams.update({ - 'font.size': 14, 'grid.alpha': 0.5, 'grid.linestyle': '--', - 'axes.grid': True, 'text.usetex': False}) - class TbTDataParams(_AcqBPMsSignalsParams): """.""" BPMS_NAMES = SOFBFactory.create("SI").bpm_names From a013d088c618f37d339d35aeb98e0e44da5f2d7f Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 14 May 2024 16:13:42 -0300 Subject: [PATCH 067/144] COMMISS.TBT.ANLY.ENH: rename prorperty `fitting_optics` to `fitting_data` --- apsuite/commisslib/measure_tbt_data.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1c11b7bd..73260f84 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -543,7 +543,7 @@ def harmonic_analysis(self, guess_tunes=False): else: tunex, tuney = self.tunex, self.tuney - fitted_optics = dict() + fitting_data = dict() for pinger in self.params.pingers2kick: if pinger == "h": from_turn2turn = self.trajx_turns_slice @@ -592,19 +592,19 @@ def harmonic_analysis(self, guess_tunes=False): beta_fit, action = self.calc_beta_and_action(amps, beta_model) # collect fitted data - fitted_optics["tune"+label] = tune - fitted_optics["tune_err"+label] = params_error[0] - fitted_optics["beta"+label] = beta_fit + fitting_data["tune"+label] = tune + fitting_data["tune_err"+label] = params_error[0] + fitting_data["beta"+label] = beta_fit # TODO: propagate amplitude errors to beta errors - fitted_optics["beta"+label+"_err"] = params_error[1 : nbpms + 1] - fitted_optics["phase"+label] = phases_fit - fitted_optics["phase"+label+"_err"] = params_error[-nbpms:] - fitted_optics["action"+label] = action + fitting_data["beta"+label+"_err"] = params_error[1 : nbpms + 1] + fitting_data["phase"+label] = phases_fit + fitting_data["phase"+label+"_err"] = params_error[-nbpms:] + fitting_data["action"+label] = action # TODO: propagate amplitude errors to action error - fitted_optics["traj"+label+"_init_fit"] = initial_fit - fitted_optics["traj"+label+"_final_fit"] = final_fit + fitting_data["traj"+label+"_init_fit"] = initial_fit + fitting_data["traj"+label+"_final_fit"] = final_fit - self.fitted_optics = fitted_optics + self.fitting_data = fitting_data self.plot_betabeat_and_phase_error( beta_model, beta_fit, phases_model, phases_fit, @@ -612,7 +612,7 @@ def harmonic_analysis(self, guess_tunes=False): ) # TODO: compare fit with trajectory - self.fitted_optics = fitted_optics + self.fitting_data = fitting_data def principal_components_analysis(self): """.""" From 2d19c1cdd5171c198f73532ac1433ecd2f15394a Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 14 May 2024 16:17:57 -0300 Subject: [PATCH 068/144] COMMISS.TBT.ANLY.ENH: plot phase advance and phase advance errors instead of absolute phase at BPMs --- apsuite/commisslib/measure_tbt_data.py | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 73260f84..95b35371 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -711,10 +711,10 @@ def plot_trajs_vs_fit(self): def plot_betabeat_and_phase_error( self, beta_model, beta_meas, phase_model, phase_meas, title=None, - plot_comparison=False + compare_meas2model=False ): """.""" - if plot_comparison: + if compare_meas2model: fig, axs = _mplt.subplots(2, 2, figsize=(15, 10)) else: fig, axs = _mplt.subplots(1, 2, figsize=(15, 5)) @@ -725,7 +725,7 @@ def plot_betabeat_and_phase_error( fig.suptitle("beta and phase") # Beta plots - if plot_comparison: + if compare_meas2model: ax_beta = axs[0, 0] ax_beta.plot(beta_model, "o-", label="Model", mfc="none") ax_beta.plot(beta_meas, "o--", label="Meas", mfc="none") @@ -733,7 +733,7 @@ def plot_betabeat_and_phase_error( ax_beta.legend() # Beta beating plot - ax_beat = axs[0, 1] if plot_comparison else axs[0] + ax_beat = axs[0, 1] if compare_meas2model else axs[0] beta_beat = (beta_model - beta_meas) / beta_model ax_beat.plot( beta_beat * 100, @@ -745,22 +745,24 @@ def plot_betabeat_and_phase_error( ax_beat.legend() # Phase plots - if plot_comparison: + if compare_meas2model: + model_phase_advance = _np.diff(phase_model) + meas_phase_advance = _np.abs(_np.diff(phase_meas)) ax_phase = axs[1, 0] - ax_phase.plot(phase_model, "o-", label="Model", mfc="none") - ax_phase.plot(phase_meas, "o--", label="Meas", mfc="none") - ax_phase.set_ylabel("phase advance [rad]") + ax_phase.plot(model_phase_advance, "o-", label="Model", mfc="none") + ax_phase.plot(meas_phase_advance, "o--", label="Meas", mfc="none") + ax_phase.set_ylabel("BPMs phase advance [rad]") ax_phase.legend() - ax_phase_err = axs[1, 1] if plot_comparison else axs[1] - delta_mu = phase_model - phase_meas + ax_phase_err = axs[1, 1] if compare_meas2model else axs[1] + phase_advance_err = model_phase_advance - meas_phase_advance ax_phase_err.plot( - delta_mu, + phase_advance_err, "o-", - label=f"avg. error = {delta_mu.mean():.2f}", + label=f"rms. error = {phase_advance_err.std():.2f}", mfc="none" ) - ax_phase_err.set_ylabel("phase error [rad]") + ax_phase_err.set_ylabel("BPMs phase advance error [rad]") ax_phase_err.legend() fig.supxlabel("BPM index") From a6b4136a125c15dff5b2ca2ca59944e5017e681d Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 14 May 2024 16:21:31 -0300 Subject: [PATCH 069/144] COMMISS.TBT.ANLY.ENH: refactor PCA analysis to share workflow with ICA - whiten source signals, variance contained in mixing matrix --- apsuite/commisslib/measure_tbt_data.py | 64 +++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 95b35371..c658a02b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -614,27 +614,57 @@ def harmonic_analysis(self, guess_tunes=False): # TODO: compare fit with trajectory self.fitting_data = fitting_data - def principal_components_analysis(self): + def principal_components_analysis(self, compare_meas2model=True): """.""" - pca_optics = dict() + pca_data = dict() for pinger in self.params.pingers2kick: - traj = self.trajx if pinger == "h" else self.trajy - label = "x" if pinger == "h" else "y" + if pinger == "h": + traj = self.trajx + label = "x" + else: + traj = self.trajy + label = "y" + tunes = self.tunex, self.tuney + if self.model_optics is None: self._get_nominal_optics(tunes) + + # get model optics beta_model = self.model_optics["beta"+label] phase_model = self.model_optics["phase"+label] - beta_pca, phase_pca = self.calc_beta_and_phase_with_pca( - traj, beta_model + + # perform PCA via SVD of history matrix + umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) + + # collect source signals and mixing matrix + signals = umat * _np.sqrt(traj.shape[0] - 1) # whiten signals + mixing_matrix = vtmat.T @ _np.diag(svals) + mixing_matrix /= _np.sqrt(traj.shape[0] - 1) + + # determine betatron sine and cosine modes + sin_mode = mixing_matrix[:, 1] # check this + cos_mode = mixing_matrix[:, 0] + + # calculate beta function & phase from betatron modes + beta, phase = self.get_beta_and_phase_from_betatron_modes( + sin_mode, cos_mode, beta_model ) + + # plot_results self.plot_betabeat_and_phase_error( - beta_model, beta_pca, phase_model, phase_pca, - title=f"PCA Analysis: beta{label} & phase{label}" + beta_model, beta, phase_model, phase, + title=f"PCA Analysis: beta{label} & phase{label}", + compare_meas2model=compare_meas2model ) - pca_optics["beta"+label] = beta_pca - pca_optics["phase"+label] = phase_pca - self.pca_optics = pca_optics + + # save analysis data + pca_data["singular_values_"+label] = svals + pca_data["source_signals_"+label] = signals + pca_data["mixing_matrix_"+label] = mixing_matrix + pca_data["beta"+label] = beta + pca_data["phase"+label] = phase + self.pca_data = pca_data def independent_component_analysis(self): """.""" @@ -853,6 +883,18 @@ def calc_beta_and_phase_with_pca(self, matrix, beta_model): phase_meas = _np.abs(_np.unwrap(phase_meas)) # why the abs? return beta_meas, phase_meas + def get_beta_and_phase_from_betatron_modes( + self, sin_mode, cos_mode, beta_model + ): + """Calulate beta & phase at BPMs from sine and cosine modes.""" + beta = (sin_mode**2 + cos_mode**2) + beta /= _np.std(beta) / _np.std(beta_model) + phase = _np.arctan2(sin_mode, cos_mode) + phase = _np.unwrap(phase, discont=2.6) + # 2.6 was set because it was the largest phase advance + # observed in the model for both x and y motion + return beta, phase + def _get_nominal_optics(self, tunes=None, chroms=None): """.""" if self.model_optics is None: From aaa0d7f8c5684599b2bcd5ed5be5830fd3530cb5 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 14 May 2024 16:23:20 -0300 Subject: [PATCH 070/144] COMMISS.TBT.ANLY.ENH: add ICA analysis with sklearn's FastICA --- apsuite/commisslib/measure_tbt_data.py | 66 +++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c658a02b..e6f8c5d2 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as _mplt import numpy as _np +from sklearn.decomposition import FastICA as _FastICA from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU, Trigger from siriuspy.sofb.csdev import SOFBFactory @@ -534,6 +535,7 @@ def linear_optics_analysis(self): """.""" self.harmonic_analysis() self.principal_components_analysis() + self.independent_component_analysis() raise NotImplementedError def harmonic_analysis(self, guess_tunes=False): @@ -666,9 +668,69 @@ def principal_components_analysis(self, compare_meas2model=True): pca_data["phase"+label] = phase self.pca_data = pca_data - def independent_component_analysis(self): + def independent_component_analysis( + self, n_components=8, compare_meas2model=True + ): """.""" - raise NotImplementedError + ica_data = dict() + for pinger in self.params.pingers2kick: + if pinger == "h": + traj = self.trajx + label = "x" + else: + traj = self.trajy + label = "y" + tunes = self.tunex, self.tuney + + if self.model_optics is None: + self._get_nominal_optics(tunes) + + # get model optics + beta_model = self.model_optics["beta"+label] + phase_model = self.model_optics["phase"+label] + + # perform Independent Component Analysis (ICA) + ica = _FastICA( + n_components=n_components, + whiten="unit-variance", + algorithm="deflation", + tol=1e-12 + ) + + # collect source signals & mixing matrix + signals = ica.fit_transform(traj) # whiten signals + mixing_matrix = ica.mixing_ + + # determine betatron modes from mixing matrix + # largest variance should be contained in the betatron modes + idcs = _np.argsort(_np.std(mixing_matrix, axis=0))[-2:] + sin_mode = mixing_matrix[:, idcs[0]] + cos_mode = mixing_matrix[:, idcs[-1]] + + # determine which betatron mode is the sine & which is cosine + # sine mode starts off close to zero + if _np.abs(sin_mode[0]) > _np.abs(cos_mode[0]): + cos_mode, sin_mode = sin_mode, cos_mode + idcs[0], idcs[1] = idcs[1], idcs[0] + + # calculate beta function & phase from betatron modes + beta, phase = self.get_beta_and_phase_from_betatron_modes( + sin_mode, cos_mode, beta_model + ) + + # plot results + self.plot_betabeat_and_phase_error( + beta_model, beta, phase_model, phase, + title=f"ICA Analysis: beta{label} & phase{label}", + compare_meas2model=compare_meas2model + ) + + # save results + ica_data["source_signals_"+label] = signals + ica_data["mixing_matrix_"+label] = mixing_matrix + ica_data["beta"+label] = beta + ica_data["phase"+label] = phase + self.ica_data = ica_data def equilibrium_params_analysis(self): """.""" From 05518621d7104e9cde9c8d844fc78e8d5c64c42b Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 13:41:14 -0300 Subject: [PATCH 071/144] COMMISS.TBT.MEAS.ENH: update default params --- apsuite/commisslib/measure_tbt_data.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index e6f8c5d2..9061ec7e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -7,6 +7,7 @@ import numpy as _np from sklearn.decomposition import FastICA as _FastICA from scipy.optimize import curve_fit as _curve_fit + from siriuspy.devices import PowerSupplyPU, Trigger from siriuspy.sofb.csdev import SOFBFactory @@ -30,15 +31,12 @@ def __init__(self): self.nrpoints_before = 100 self.nrpoints_after = 2000 - self.timing_event = "Linac" - self.event_mode = "Injection" - self._pingers2kick = "none" # 'H', 'V' or 'HV' self.hkick = None # [mrad] self.vkick = None # [mrad] self.trigpingh_delay = None self.trigpingv_delay = None - self.magnets_timeout = 5.0 + self.magnets_timeout = 120. def __str__(self): """.""" @@ -49,6 +47,7 @@ def __str__(self): dtmp = "{0:26s} = {1:9d} {2:s}\n".format stmp = "{0:26s} = {1:9} {2:s}\n".format stg += "\n" + stg += "Pingers params\n" stg += "\n" stg += stmp("pingers2kick", self.pingers2kick, "") From b4c4ed9ac841e452d7a19550a4ebfaca15e18502 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 14:32:36 -0300 Subject: [PATCH 072/144] COMMISS.AcqBPMS.ENH: get BPM trigger event & mode as strings --- apsuite/commisslib/meas_bpms_signals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 8f464d5f..5cd72816 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -137,14 +137,14 @@ def get_timing_state(self): trigbpm = self.devices['trigbpm'] state = dict() - state['trigbpm_source'] = trigbpm.source + state['trigbpm_source'] = trigbpm.source_str state['trigbpm_nrpulses'] = trigbpm.nr_pulses state['trigbpm_delay'] = trigbpm.delay evt = self._get_event(self.params.timing_event) if evt is not None: state['evt_delay'] = evt.delay - state['evt_mode'] = evt.mode + state['evt_mode'] = evt.mode_str return state def recover_timing_state(self, state): From a2167796ce6f048a7e7c125f5ad8545f53dbc347 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 14:33:22 -0300 Subject: [PATCH 073/144] COMMIS.TBT.MEAS.ENH: get pingers trigger event source as strings --- apsuite/commisslib/measure_tbt_data.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9061ec7e..9f2d4736 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -116,14 +116,17 @@ def create_devices(self): def get_timing_state(self): """.""" state = super().get_timing_state() + trigpingh = self.devices["trigpingh"] - state["trigpingh_state"] = trigpingh.state # correct? - state["trigpingh_source"] = trigpingh.source + state["trigpingh_state"] = trigpingh.state + state["trigpingh_source"] = trigpingh.source_str state["trigpingh_delay"] = trigpingh.delay + trigpingv = self.devices["trigpingv"] - state["trigpingv_state"] = trigpingv.state # correct? - state["trigpingv_source"] = trigpingv.source + state["trigpingv_state"] = trigpingv.state + state["trigpingv_source"] = trigpingv.source_str state["trigpingv_delay"] = trigpingv.delay + return state def prepare_timing(self, state=None): From 5756579a117b0baf9d41eb444cd9479d2f69ea0c Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 14:54:27 -0300 Subject: [PATCH 074/144] COMMISS.TBT.MEAS.ENH: fixes in default filname --- apsuite/commisslib/measure_tbt_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9f2d4736..d8ab4fce 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -369,18 +369,18 @@ def get_default_fname(self): """.""" prms = self.params stg = "kickedbeam_data" - stg += f"_{prms.acq_rate}_rate" + stg += f"_{prms.acq_rate}_rate_" pingers2kick = prms.pingers2kick stg += ( - f"hkick_{prms.hkick:.4f}_mrad".replace(".", "p") + f"hkick_{prms.hkick:.4f}_mrad_".replace(".", "p").replace("-", "m") if "h" in pingers2kick - else "hkick_inactive" + else "hkick_inactive_" ) stg += ( - f"vkick_{prms.vkick:.4f}_mrad".replace(".", "p") + f"vkick_{prms.vkick:.4f}_mrad_".replace(".", "p").replace("-", "m") if "v" in pingers2kick - else "vkick_inactive" + else "vkick_inactive_" ) tm = self.data["timestamp"] fmt = "%Y-%m-%d-%H-%M-%S" From 7f13ff7d3d10c0f909bf79748382e290d697dc0e Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 15:42:40 -0300 Subject: [PATCH 075/144] COMMISS.TBT.ANLY.ENH: fixes in analysis str method --- apsuite/commisslib/measure_tbt_data.py | 57 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d8ab4fce..4de5ba2e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -428,6 +428,7 @@ def __str__(self): stg += gtmp("timestamp", self.timestamp, "") stg += "\n" stg += "Storage Ring State\n" + stg += "\n" stg += ftmp("current_before", data["current_before"], "mA") stg += ftmp("current_after", data["current_after"], "mA") @@ -444,6 +445,7 @@ def __str__(self): stg += "\n" stg += "BPMs state\n" + stg += "\n" stg += stmp("acq_rate", data["acq_rate"], "") stg += stmp("nrsamples_pre", data["nrsamples_pre"], "") @@ -451,53 +453,74 @@ def __str__(self): stg += stmp("switching_mode", data["switching_mode"], "") stg += stmp("switching_frequency", data["switching_frequency"], "") stg += stmp( - "trigbpm_source", data["timing_state"]["trigbpm_source"], "" + "trigbpm_source", + data["timing_state"]["trigbpm_source"], + "" ) stg += stmp( "trigbpm_nrpulses", data["timing_state"]["trigbpm_nrpulses"], - "", + "" ) stg += stmp( - "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" + "trigbpm_delay", + data["timing_state"]["trigbpm_delay"], + "" ) stg += "\n" stg += "Pingers state\n" + stg += "\n" stg += stmp( - "trigpingh_state", data["timing_state"]["trigpingh_state"], "" + "trigpingh_state", + data["timing_state"]["trigpingh_state"], + "" ) stg += stmp( "trigpingh_source", data["timing_state"]["trigpingh_source"], - "", + "" ) stg += stmp( - "trigpingh_delay", data["timing_state"]["trigpingh_delay"], "" + "trigpingh_delay", + data["timing_state"]["trigpingh_delay"], + "" + ) + stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") + + stg += stmp( + "pingh_pulse", + data["magnets_state"]["pingh_pulse"], + "" ) - # stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") # commented because last measurement had a problem here - # stg += stmp( - # "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" - # ) stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") + stg += "\n" stg += stmp( - "trigpingv_state", data["timing_state"]["trigpingv_state"], "" + "trigpingv_state", + data["timing_state"]["trigpingv_state"], + "" ) stg += stmp( "trigpingv_source", data["timing_state"]["trigpingv_source"], - "", + "" + ) + stg += stmp( + "trigpingv_delay", + data["timing_state"]["trigpingv_delay"], + "" ) + stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") + stg += stmp( - "trigpingv_delay", data["timing_state"]["trigpingv_delay"], "" + "pingv_pulse", + data["magnets_state"]["pingv_pulse"], + "" ) - # stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") - # stg += stmp( - # "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" - # ) stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") + return stg @property From edf8b7a4f564c2d2102dfc5e45bc08bc6b4bd358 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 May 2024 17:04:23 -0300 Subject: [PATCH 076/144] COMMISS.TBT.ANLY.ENH: add default traj turns selection for linear optics fitting --- apsuite/commisslib/measure_tbt_data.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 4de5ba2e..868cbf01 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -396,20 +396,28 @@ class TbTDataAnalysis(MeasureTbTData): def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" super().__init__(isonline=isonline) + self._fname = filename self.timestamp = None self.trajx, self.trajy = None, None # zero-mean trajectories in [mm] self.trajsum = None + self.tunex, self.tuney = None, None self.acq_rate = None + self.nrsamples_pre = None + self.nrsamples_post = None self.sampling_freq = None self.switching_freq = None self.rf_freq = None + self.trajx_turns_slice = None self.trajy_turns_slice = None + self.model_optics = None - self.fitted_optics = None - self.pca_optics = None + self.fitting_data = None + self.pca_data = None + self.ica_data = None + if self._fname: self.load_and_apply(self._fname) @@ -540,20 +548,30 @@ def load_and_apply(self, fname): if keys: print("The following keys were not used:") print(" ", str(keys)) + data = self.data timestamp = _datetime.datetime.fromtimestamp(data["timestamp"]) self.timestamp = timestamp trajx, trajy = data["orbx"].copy() * 1e-3, data["orby"].copy() * 1e-3 trajsum = data["sumdata"].copy() + # zero mean in samples dimension trajx -= trajx.mean(axis=0)[None, :] trajy -= trajy.mean(axis=0)[None, :] self.trajx, self.trajy, self.trajsum = trajx, trajy, trajsum + self.tunex, self.tuney = data['tunex'], data['tuney'] self.acq_rate = data["acq_rate"] self.rf_freq = data["rf_frequency"] self.sampling_freq = self.data["sampling_frequency"] self.switching_freq = self.data["switching_frequency"] + self.nrsamples_pre = self.data["nrsamples_pre"] + self.nrsamples_post = self.data["nrsamples_post"] + + nrpre = self.nrsamples_pre + self.trajx_turns_slice = (nrpre, nrpre + int(1 / self.tunex)) + self.trajy_turns_slice = (nrpre, nrpre + int(1 / self.tuney)) + return def linear_optics_analysis(self): From c7d06922d07b19f32372114aff2436b4468f753b Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 10:07:22 -0300 Subject: [PATCH 077/144] COMMISS.TbT.ANLY.ENH: fix saving of fitting_data only after loops finish & add some comments --- apsuite/commisslib/measure_tbt_data.py | 62 +++++++++++++++++++++----- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 868cbf01..f41ac747 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -575,14 +575,45 @@ def load_and_apply(self, fname): return def linear_optics_analysis(self): - """.""" + """Linear optics (beta-beating & phase adv. errors) analysis. + + Determines beta-functions and phase-advances on BPMs via sinusoidal + fitting of TbT data in the harmonic motion timescale, as well as via + spatio-temporal modal analysis using Independent Components Analysis + (PCA) and Independent Components Analysis (ICA). + """ self.harmonic_analysis() self.principal_components_analysis() self.independent_component_analysis() - raise NotImplementedError - def harmonic_analysis(self, guess_tunes=False): - """.""" + def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): + r"""Linear optics analysis using sinusoidal model for TbT data. + + TbT motion at the i-th turn and j-th BPM in the timescale of less + than $\nu^{-1}$ turns reads + + $$ x_{ij} = A_j \sin (2 \pi \nu i + \phi_j) $$. + + The betatron tune $\nu$ and the amplitudes $A_j$ and phases $\phi_j$ + are fitted at each BPM. + + Phase-advance between adjacent BPMs is calulated from the fitted BPM + phase $\phi_j$. The betatron action $J$ is determined from the fitted + amplitudes $A_j$ and the nominal beta-function as in eq. (9) of ref. + [1]. Fitting of beta-functions from the calculated action and + fitted amplitudes. + + + Args: + guess_tunes (bool, optional): whether to use the initial guess for + the tunes from the data DFT or use the measured tunes. Defaults to + False. + + compare_meas2model (bool, optional): whether to plot measured and + nominal beta-functions and BPMs phase-advance, as well as + beta-beting and phase-advance errors or plot only beta-beating and + phase-advance-errors. Defaults to True + """ if guess_tunes: tunex, tuney = self._guess_tune_from_dft() else: @@ -610,33 +641,42 @@ def harmonic_analysis(self, guess_tunes=False): # TODO: adapt turns selection to grant integer nr of cycles nbpms = traj.shape[-1] + # get initial guess of amplitudes and phases with linear fit fourier = self._get_fourier_components(traj, tune) amps, phases = self._get_amplitude_phase(fourier) params_guess = _np.concatenate(([tune], amps, phases)).tolist() - n = self._get_independent_variables(from_turn2turn, nbpms) + # evaluate harmonic motion model with initial guess for params + n = self._get_independent_variables(from_turn2turn, nbpms) initial_fit = self.harmonic_tbt_model( n, *params_guess, return_ravel=False ) + # perform nonlinear fit to refine search for params params_fit, params_error = self.fit_harmonic_model( from_turn2turn, traj, *params_guess ) params_fit = params_fit.tolist() + + # evaluate harmonic motion model with nonlinear fit params final_fit = self.harmonic_tbt_model( n, *params_fit, return_ravel=False ) + # collect fitted params (tune, amplitudes and phases at BPMs) tune = params_fit[0] amps = _np.array(params_fit[1 : nbpms + 1]) phases_fit = _np.array(params_fit[-nbpms:]) - self._get_nominal_optics(tunes=(tunex, tuney)) + if self.model_optics is None: + self._get_nominal_optics(tunes=(tunex, tuney)) beta_model = self.model_optics["beta"+label] phases_model = self.model_optics["phase"+label] + + # fit beta-function & action from fitted amplitudes & nominal beta beta_fit, action = self.calc_beta_and_action(amps, beta_model) - # collect fitted data + # store fitting data fitting_data["tune"+label] = tune fitting_data["tune_err"+label] = params_error[0] fitting_data["beta"+label] = beta_fit @@ -649,14 +689,14 @@ def harmonic_analysis(self, guess_tunes=False): fitting_data["traj"+label+"_init_fit"] = initial_fit fitting_data["traj"+label+"_final_fit"] = final_fit - self.fitting_data = fitting_data - + # Plot results (beta_beating and phase adv. error) self.plot_betabeat_and_phase_error( beta_model, beta_fit, phases_model, phases_fit, - title=f"Sinusoidal fit analysis - beta{label} & phase{label}" + title=f"Sinusoidal fit analysis - beta{label} & phase{label}", + compare_meas2model=compare_meas2model ) - # TODO: compare fit with trajectory + self.fitting_data = fitting_data def principal_components_analysis(self, compare_meas2model=True): From b8279e07a4d6aec3235f0fb5606b5b9306ed11eb Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 10:27:50 -0300 Subject: [PATCH 078/144] COMMISS.TBT.ANLY.ENH: add reference to docstring --- apsuite/commisslib/measure_tbt_data.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f41ac747..76fc2092 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -613,6 +613,12 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): nominal beta-functions and BPMs phase-advance, as well as beta-beting and phase-advance errors or plot only beta-beating and phase-advance-errors. Defaults to True + + References: + [1] X.R. Resende, M.B. Alves, L. Liu, and F.H. de Sá, “Equilibrium and + Nonlinear Beam Dynamics Parameters From Sirius Turn-by-Turn BPM + Data”, in Proc. IPAC'21, Campinas, SP, Brazil, May 2021, pp. + 1935-1938. doi:10.18429/JACoW-IPAC2021-TUPAB219 """ if guess_tunes: tunex, tuney = self._guess_tune_from_dft() From fc9792c73bdb935357dd9676a5d2708f597f4405 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 14:15:08 -0300 Subject: [PATCH 079/144] COMMISS.TBT.ANLY.ENH: fix typo in ICA --- apsuite/commisslib/measure_tbt_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 76fc2092..97ee05eb 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -584,7 +584,7 @@ def linear_optics_analysis(self): """ self.harmonic_analysis() self.principal_components_analysis() - self.independent_component_analysis() + self.independent_components_analysis() def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): r"""Linear optics analysis using sinusoidal model for TbT data. @@ -757,7 +757,7 @@ def principal_components_analysis(self, compare_meas2model=True): pca_data["phase"+label] = phase self.pca_data = pca_data - def independent_component_analysis( + def independent_components_analysis( self, n_components=8, compare_meas2model=True ): """.""" From 982b5460dab863752ce3f87edfae7520e1e95b34 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 14:58:13 -0300 Subject: [PATCH 080/144] COMMISS.TBT.ANLY.ENH: add PCA & ICA docstrings --- apsuite/commisslib/measure_tbt_data.py | 101 ++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 97ee05eb..07cfd798 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -619,6 +619,9 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): Nonlinear Beam Dynamics Parameters From Sirius Turn-by-Turn BPM Data”, in Proc. IPAC'21, Campinas, SP, Brazil, May 2021, pp. 1935-1938. doi:10.18429/JACoW-IPAC2021-TUPAB219 + + [2] Huang, X. Beam-based correction and optimization for accelerators. + Section 5.1. CRC Press, 2020. """ if guess_tunes: tunex, tuney = self._guess_tune_from_dft() @@ -706,7 +709,52 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): self.fitting_data = fitting_data def principal_components_analysis(self, compare_meas2model=True): - """.""" + r"""Peforms Principal Components Analysis (PCA). + + Calculates beta-functions and betatron phase-advance at the BPMs using + PCA. PCA aims to identify principal axes along which the covariance + matrix of the data is diagonal. For betatron-dominated motion, there + are two pricipal components (cosine and sine modes) that can be + related to betatron functions and phase advance, as described in refs + [1,2]. + + For a beam history matrix with BPMs arranged along the columns and + turn-by turn samples along the rows (nturns x nbpms), it can be shown + (ref. [1]) that the principal components diagonalizing the covariance + matrix are the columns of the V matrix, where V refers to the spatial + patterns of the data, as accessed by its singular-value decomposition: + data = U S Vt. The first two columns of V are the beatron modes. + + In the language of Blind Source Separation, if the data matrix X can + be expressed as a linear mixture of uncorrelated source signals + arranged as the columns of matrix S, i.e. X = S A^T, then the + mixing matrix A can be identified with A = V S^T / sqrt{nturns - 1}. + Acting on X with A's pseudo-inverse, the unmixing matrix, gives the + whitened uncorrelated source signals S = U \sqrt{nturns - 1}, + with S^T S / (nsamples - 1 ) = identity. This choice for the mixing + matrix and the whitened sources follows scikit-learn's [2] convention + and is compatible with the convention adopted in the independent + components analysis (ICA), where the betatron modes and phase advances + are calculated from the columns of the mixing matrix. + + Args: + compare_meas2model (bool, optional): whether to plot measured and + nominal beta-functions and BPMs phase-advance, as well as + beta-beting and phase-advance errors or plot only beta-beating and + phase-advance-errors. Defaults to True + + References: + [1] Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance + and ${\beta}$ function measurements using model-independent + analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. DOI 10.1103/ + PhysRevSTAB.6.104001 + + [2] Huang, X. Beam-Based Correction and Optimization, Ch 5.2. + CRC Press. 2020. + + [3] Scikit-learn examples. "Blind Source Separation using FastICA". + https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py + """ pca_data = dict() for pinger in self.params.pingers2kick: if pinger == "h": @@ -760,7 +808,56 @@ def principal_components_analysis(self, compare_meas2model=True): def independent_components_analysis( self, n_components=8, compare_meas2model=True ): - """.""" + r"""Peforms Independent Components Analysis (ICA). + + Calculates beta-functions and betatron phase-advance at the BPMs using + ICA. ICA aims to identify the linear transformation (unmixing matrix) + revealing statistically independent source signals. Just as in PCA, + the beatron motion sine and cosine modes can be used to calculate + beta-functions and BPMs phase advances. + + While PCA aims to identify the linear transformation revealing + uncorrelated source signals, ICA seeks the transformation + revealing statistically independent signals, a stronger + requirement than uncorrelatedness. ICA often performs better at blind + source separation for linear mixtures of sinals with non-gaussian + distributions, and can be more robust at betatron motion + identification when there are contaminating signals or bad + acquisitions. + + ICA can be implemented with second-oderd blind source identification + (SOBI) [1], based on simultaneous diagonalization of the time-shifted + data covariance matrix(not implemented), or with information-theoretic + approaches for maximizing statistical indpendence of source signals + [2]. We use the latter, as implemented in scikit-learn's FastICA [3, + 4]. + + The variance convention is the same as in PCA analysis: whiten source + signals, with the mixing matrix containing the modes energy/variance + [4]. + + Args: + n_components (int, optional): number of independent components to + decompose the data + compare_meas2model (bool, optional): whether to plot measured and + nominal beta-functions and BPMs phase-advance, as well as + beta-beting and phase-advance errors or plot only beta-beating and + phase-advance-errors. Defaults to True + + References: + [1] Huang, X. Beam-Based Correction and Optimization, Section 5.2.3 + CRC Press. 2020. + + [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms + and applications. Neural Networks. Volume 13, Issues 4-5, 2000, + Pages 411-430, https://doi.org/10.1016/S0893-6080(00)00026-5. + + [3] scikit-learn.decomposition.FastICA documentation. + https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html + + [4] Scikit-learn examples. "Blind Source Separation using FastICA" + https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py + """ ica_data = dict() for pinger in self.params.pingers2kick: if pinger == "h": From ac5ca9d16daba96dc77f6d8de111950c9afe9edc Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 15:40:10 -0300 Subject: [PATCH 081/144] COMMISS.TBT.MEAS.ENH: save also pingers voltage --- apsuite/commisslib/measure_tbt_data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 07cfd798..aa982429 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -133,6 +133,7 @@ def prepare_timing(self, state=None): """.""" print("Setting BPMs timing") super().prepare_timing(state) # BPM trigger timing + state = dict() if state is None else state print("Setting magnets timing") # magnets trigger timing below @@ -177,6 +178,8 @@ def get_magnets_state(self): state["pingv_pulse"] = pingv.pulse state["pingh_strength"] = pingh_str state["pingv_strength"] = pingv_str + state["pingh_voltage"] = pingh.voltage + state["pingv_voltage"] = pingv.voltage return state From 3ca39f6aaf3fa5e60da706876646277e88a342be Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 16:40:30 -0300 Subject: [PATCH 082/144] COMMISS.TBT.ANLY.ENH: add option to compare fit & acquision in `plot_trajs` --- apsuite/commisslib/measure_tbt_data.py | 39 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index aa982429..ac1786c5 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -930,26 +930,31 @@ def plot_traj_spectrum(): """.""" raise NotImplementedError - def plot_trajs(self, bpm_index=0, timescale=0): + def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): """Plot trajectories and sum-signal at a given BPM and time-scale. Timescale 0 : Harmonic motion Timescale 1 : Chromaticity decoherence modulation Timescale 2 : Transverse decoherence modulations """ + if self.fitting_data is None and compare_fit: + msg = "No fitting was performed yet." + msg += "Plotting measured data only." + print(msg) + compare_fit = False + nr_pre = self.data['nrsamples_pre'] nr_post = self.data['nrsamples_post'] - n = 5 if not timescale: nmax_x, nmax_y = int(1 / self.tunex), int(1 / self.tuney) - slicex = (nr_pre - n, nr_pre + nmax_x + n + 1) - slicey = (nr_pre - n, nr_pre + nmax_y + n + 1) - slicesum = (nr_pre - n, nr_pre + max(nmax_x, nmax_y) + n + 1) + slicex = (nr_pre, nr_pre + nmax_x + 1) + slicey = (nr_pre, nr_pre + nmax_y + 1) + slicesum = (nr_pre, nr_pre + max(nmax_x, nmax_y) + 1) nmax = int(1 / self.SYNCH_TUNE) if timescale == 1: - slicex = (nr_pre - n, nr_pre + nmax + n + 1) + slicex = (nr_pre, nr_pre + nmax + 1) slicey = slicex slicesum = slicex @@ -965,16 +970,32 @@ def plot_trajs(self, bpm_index=0, timescale=0): fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) name = self.params.BPMS_NAMES[bpm_index] fig.suptitle( - f"{self.acq_rate.upper()} acq. at BPM {bpm_index:3d} ({name})" + f"{self.acq_rate.upper()} acq. at BPM {bpm_index:03d} ({name})" ) ax[0].set_title("horizontal trajectory") - ax[0].plot(trajx, "-", mfc="none", color="blue") + ax[0].plot(trajx, "-", mfc="none", color="blue", label="acq.") + if compare_fit and "h" in self.params.pingers2kick: + fit = self.fitting_data["trajx_final_fit"] + ax[0].plot( + fit[slice(*slicex, 1), bpm_index], + "x-", mfc="none", color="blue", label="fit" + ) + ax[0].legend() ax[0].set_xlim(slicex) ax[0].set_ylabel("position [mm]") + ax[1].set_title("vertical trajectory") - ax[1].plot(trajy, "-", mfc="none", color="red") + ax[1].plot(trajy, "-", mfc="none", color="red", label="acq.") + if compare_fit and "v" in self.params.pingers2kick: + fit = self.fitting_data["trajy_final_fit"] + ax[1].plot( + fit[slice(*slicey, 1), bpm_index], + "x-", mfc="none", color="red", label="fit" + ) + ax[1].legend() ax[1].set_xlim(slicey) ax[1].sharey(ax[0]) + ax[2].set_title("BPM sum signal") ax[2].plot(trajsum, "-", mfc="none", color="k") ax[2].set_xlim(slicesum) From e935f06917f7089dac2a5964d2f04338df053e6b Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 May 2024 17:00:03 -0300 Subject: [PATCH 083/144] COMMISS.TBT.ANLY.ENH: save also fitting residue in `fitting_data` --- apsuite/commisslib/measure_tbt_data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ac1786c5..d49a4159 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -688,6 +688,9 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): # fit beta-function & action from fitted amplitudes & nominal beta beta_fit, action = self.calc_beta_and_action(amps, beta_model) + # evaluate fitting residues + residue = traj - final_fit + # store fitting data fitting_data["tune"+label] = tune fitting_data["tune_err"+label] = params_error[0] @@ -700,7 +703,7 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): # TODO: propagate amplitude errors to action error fitting_data["traj"+label+"_init_fit"] = initial_fit fitting_data["traj"+label+"_final_fit"] = final_fit - + fitting_data["fitting"+label+"_residue"] = residue # Plot results (beta_beating and phase adv. error) self.plot_betabeat_and_phase_error( beta_model, beta_fit, phases_model, phases_fit, From 70e41aa91f63167ab4c843c4e89027ff1244ef7e Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 20 May 2024 12:56:56 -0300 Subject: [PATCH 084/144] COMMISS.TBT.ANLY.ENH: plot also residue in `plot_trajs` --- apsuite/commisslib/measure_tbt_data.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d49a4159..afd4744e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -979,10 +979,15 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): ax[0].plot(trajx, "-", mfc="none", color="blue", label="acq.") if compare_fit and "h" in self.params.pingers2kick: fit = self.fitting_data["trajx_final_fit"] + res = self.fitting_data["fittingx_residue"] ax[0].plot( fit[slice(*slicex, 1), bpm_index], "x-", mfc="none", color="blue", label="fit" ) + ax[0].plot( + res[slice(*slicex), bpm_index], + "x-", mfc="none", color="green", label="residue" + ) ax[0].legend() ax[0].set_xlim(slicex) ax[0].set_ylabel("position [mm]") @@ -991,10 +996,15 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): ax[1].plot(trajy, "-", mfc="none", color="red", label="acq.") if compare_fit and "v" in self.params.pingers2kick: fit = self.fitting_data["trajy_final_fit"] + res = self.fitting_data["fittingy_residue"] ax[1].plot( fit[slice(*slicey, 1), bpm_index], "x-", mfc="none", color="red", label="fit" ) + ax[1].plot( + res[slice(*slicey), bpm_index], + "x-", mfc="none", color="green", label="residue" + ) ax[1].legend() ax[1].set_xlim(slicey) ax[1].sharey(ax[0]) From 32a0aed71d6eedbb1e19eaef59ceb431206c9377 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 20 May 2024 12:57:56 -0300 Subject: [PATCH 085/144] COMMISS.TBT.ANLY.ENH: add `plot_fit_comparison` & `plot_rms_residue` --- apsuite/commisslib/measure_tbt_data.py | 38 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index afd4744e..c6edd21a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1020,9 +1020,41 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): _mplt.show() return fig, ax - def plot_trajs_vs_fit(self): - """.""" - raise NotImplementedError + def plot_fit_comparison(self, bpm_index=0, timescale=0): + """Plot comparison of fit vs. acqusitions. + + Args: + bpm_index (int, optional): BPM at which to compare. Defaults to 0. + timescale (int, optional): TbT timescale to plot. + 0 = harmonic TbT motion, + 1 = chromatic decoherence modulated motion, + 2 = amplitude decoherence modulated motion. Defaults to 0. + + Returns: + fig, ax: figure env and axes containing the plots + """ + return self.plot_trajs( + bpm_index=bpm_index, timescale=timescale, compare_fit=True + ) + + def plot_rms_residue(self, axis=0): + """Plot RMS fitting residue along BPMs or along Turns. + + Args: + axis (int, optional): If 0, plot along BPMs. If 1, along the + turns. Defaults to 0. + """ + for pinger in self.params.pingers2kick: + label = "x" if "h" in pinger else "y" + _mplt.figure() + along = "BPMs" if not axis else "Turns" + _mplt.title(f"RMS fitting residue along {along}") + _mplt.plot( + self.fitting_data["fitting"+label+"_residue"].std(axis=axis), + "o-", mfc='none' + ) + _mplt.xlabel(f"{along} index") + _mplt.ylabel("residue [mm]") def plot_betabeat_and_phase_error( self, beta_model, beta_meas, phase_model, phase_meas, title=None, From b0bdcecb881cecceac7fe588c28e1ae99eaee8b4 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 20 May 2024 13:24:50 -0300 Subject: [PATCH 086/144] COMMISS.TBT.ANLY.ENH: update and add some docstrings --- apsuite/commisslib/measure_tbt_data.py | 65 +++++++++++++++++++++----- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c6edd21a..1b375c45 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -582,7 +582,7 @@ def linear_optics_analysis(self): Determines beta-functions and phase-advances on BPMs via sinusoidal fitting of TbT data in the harmonic motion timescale, as well as via - spatio-temporal modal analysis using Independent Components Analysis + spatio-temporal modal analysis using Principal Components Analysis (PCA) and Independent Components Analysis (ICA). """ self.harmonic_analysis() @@ -750,13 +750,14 @@ def principal_components_analysis(self, compare_meas2model=True): phase-advance-errors. Defaults to True References: + [1] Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance and ${\beta}$ function measurements using model-independent analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. DOI 10.1103/ PhysRevSTAB.6.104001 - [2] Huang, X. Beam-Based Correction and Optimization, Ch 5.2. - CRC Press. 2020. + [2] Huang, X. Beam-Based Correction and Optimization for Accelerators, + Ch 5.2. CRC Press. 2020. [3] Scikit-learn examples. "Blind Source Separation using FastICA". https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py @@ -851,8 +852,9 @@ def independent_components_analysis( phase-advance-errors. Defaults to True References: - [1] Huang, X. Beam-Based Correction and Optimization, Section 5.2.3 - CRC Press. 2020. + + [1] Huang, X. Beam-Based Correction and Optimization for Accelerators, + Section 5.2.3. CRC Press. 2020. [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms and applications. Neural Networks. Volume 13, Issues 4-5, 2000, @@ -934,11 +936,23 @@ def plot_traj_spectrum(): raise NotImplementedError def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): - """Plot trajectories and sum-signal at a given BPM and time-scale. + """Plot trajectories and sum-signal at a given BPM and timescale. - Timescale 0 : Harmonic motion - Timescale 1 : Chromaticity decoherence modulation - Timescale 2 : Transverse decoherence modulations + Args: + bpm_index (int, optional): Which BPM's reading to show. + Defaults to 0 (first BPM). + timescale (int, optional): Turn-by-turn timescale, where: + timescale = 0 : ~ 20 turns; harmonic motion + timescale = 1 : ~ 200 turns; chromaticity decoherence + modulation + timescale = 2 : ~ 2000 turns; transverse decoherence + modulations + Defaults to 0. + compare_fit (bool, optional): whether to plot acquisitions and the + fitting and the fit residue. Defaults to False. + + Returns: + fig, ax: matplotlib figure and axes """ if self.fitting_data is None and compare_fit: msg = "No fitting was performed yet." @@ -1180,7 +1194,12 @@ def fit_harmonic_model(self, from_turn2turn, traj, *params_guess): def calc_beta_and_action(self, amplitudes, nominal_beta): """Calculates beta function and betatron action. - As in Eq. (9) of Ref. [1] + As in Eq. (9) of Ref: + + X.R. Resende, M.B. Alves, L. Liu, and F.H. de Sá, “Equilibrium and + Nonlinear Beam Dynamics Parameters From Sirius Turn-by-Turn BPM Data”, + in Proc. IPAC'21, Campinas, SP, Brazil, May 2021, pp. 1935-1938. + doi:10.18429/JACoW-IPAC2021-TUPAB219 """ action = _np.sum(amplitudes**4) action /= _np.sum(amplitudes**2 * nominal_beta) @@ -1203,7 +1222,22 @@ def calc_beta_and_phase_with_pca(self, matrix, beta_model): def get_beta_and_phase_from_betatron_modes( self, sin_mode, cos_mode, beta_model ): - """Calulate beta & phase at BPMs from sine and cosine modes.""" + """Calulate beta & phase at BPMs from sine and cosine modes. + + Reference: X. Huang. Beam-based correction and optimization for + accelerators. Eq. 5.44, pg. 139. + + Args: + sin_mode (160-array): sine mode of mixing matrix + cos_mode (160-array): cosine mode of mixing matrix + beta_model (160-array): nominal beta function, used for + determining the measured beta function scaling factor + + Returns: + beta: 160-array of measured beta function at BPMs + phase: 160-array of betatron phase at BPMs + + """ beta = (sin_mode**2 + cos_mode**2) beta /= _np.std(beta) / _np.std(beta_model) phase = _np.arctan2(sin_mode, cos_mode) @@ -1213,7 +1247,14 @@ def get_beta_and_phase_from_betatron_modes( return beta, phase def _get_nominal_optics(self, tunes=None, chroms=None): - """.""" + r"""Gets nominal lattice optics functions and phase-advances. + + Args: + tunes (tuple, optional): (nux, nuy) - betatron tunes fractional + part. Defaults to None, in which case (0.16, 0.22) is used. + chroms (tuple, optional): (\chix, \chiy) - machine chromaticity. + Defaults to None, in which case (2.5, 2.5) is used. + """ if self.model_optics is None: model_optics = dict() model = _si.create_accelerator() From b7d451d929a98a9c9df1c992596161642b1dc2e4 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 21 May 2024 10:22:52 -0300 Subject: [PATCH 087/144] COMMISS.TBT.ANLY.ENH: fixes fit comparison in `plot_trajs` --- apsuite/commisslib/measure_tbt_data.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1b375c45..a04ff01a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -989,17 +989,22 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): fig.suptitle( f"{self.acq_rate.upper()} acq. at BPM {bpm_index:03d} ({name})" ) + ax[0].set_title("horizontal trajectory") ax[0].plot(trajx, "-", mfc="none", color="blue", label="acq.") + if compare_fit and "h" in self.params.pingers2kick: fit = self.fitting_data["trajx_final_fit"] res = self.fitting_data["fittingx_residue"] + init, end = self.trajx_turns_slice ax[0].plot( - fit[slice(*slicex, 1), bpm_index], + _np.arange(init, end + 1, 1), + fit[:, bpm_index], "x-", mfc="none", color="blue", label="fit" ) ax[0].plot( - res[slice(*slicex), bpm_index], + _np.arange(init, end + 1, 1), + res[:, bpm_index], "x-", mfc="none", color="green", label="residue" ) ax[0].legend() @@ -1008,15 +1013,19 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): ax[1].set_title("vertical trajectory") ax[1].plot(trajy, "-", mfc="none", color="red", label="acq.") + if compare_fit and "v" in self.params.pingers2kick: fit = self.fitting_data["trajy_final_fit"] res = self.fitting_data["fittingy_residue"] + init, end = self.trajy_turns_slice ax[1].plot( - fit[slice(*slicey, 1), bpm_index], + _np.arange(init, end + 1, 1), + fit[:, bpm_index], "x-", mfc="none", color="red", label="fit" ) ax[1].plot( - res[slice(*slicey), bpm_index], + _np.arange(init, end + 1, 1), + res[:, bpm_index], "x-", mfc="none", color="green", label="residue" ) ax[1].legend() From edaa2f7497f3dabde205ab03947b2a62e5544203 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 21 May 2024 10:26:09 -0300 Subject: [PATCH 088/144] COMMISS.TBT.ANLY.ENH: fix tune setting in `_get_nominal_optics` --- apsuite/commisslib/measure_tbt_data.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index a04ff01a..b9329ff1 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -639,7 +639,7 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): from_turn2turn[0], from_turn2turn[1] + 1, 1 ) traj = self.trajx[turns_slice, :].copy() - tune = tunex + tune = tunex + 49. label = "x" else: from_turn2turn = self.trajy_turns_slice @@ -647,7 +647,7 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): from_turn2turn[0], from_turn2turn[1] + 1, 1 ) traj = self.trajy[turns_slice, :].copy() - tune = tuney + tune = tuney + 14. label = "y" # TODO: adapt turns selection to grant integer nr of cycles @@ -681,7 +681,7 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): phases_fit = _np.array(params_fit[-nbpms:]) if self.model_optics is None: - self._get_nominal_optics(tunes=(tunex, tuney)) + self._get_nominal_optics(tunes=(tunex+49., tuney+14.)) beta_model = self.model_optics["beta"+label] phases_model = self.model_optics["phase"+label] @@ -1274,9 +1274,7 @@ def _get_nominal_optics(self, tunes=None, chroms=None): if tunes is not None: tunecorr = _TuneCorr(model, acc="SI") - tunex = tunes[0] + 49 - tuney = tunes[0] + 14 - tunecorr.correct_parameters(goal_parameters=(tunex, tuney)) + tunecorr.correct_parameters(goal_parameters=tunes) chroms = (2.5, 2.5) if chroms is None else chroms chromcorr = _ChromCorr(model, acc="SI") From 6114a8b565725085ab4814d263d085c73b013aa9 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 23 May 2024 16:54:52 -0300 Subject: [PATCH 089/144] COMMISS.TBT.ANLY.ENH: save also amplitudes in `harmonic_analysis` --- apsuite/commisslib/measure_tbt_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b9329ff1..1b08235b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -694,6 +694,7 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): # store fitting data fitting_data["tune"+label] = tune fitting_data["tune_err"+label] = params_error[0] + fitting_data["amplitudes"+label] = amps fitting_data["beta"+label] = beta_fit # TODO: propagate amplitude errors to beta errors fitting_data["beta"+label+"_err"] = params_error[1 : nbpms + 1] From 8825e6a5bed4f27c1ffd8ab709c25cff30904a28 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 23 May 2024 16:55:44 -0300 Subject: [PATCH 090/144] COMMISS.TBT.ENH: add `plot` argument to plot analysis results only when required --- apsuite/commisslib/measure_tbt_data.py | 57 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1b08235b..b32c7ec0 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -589,7 +589,9 @@ def linear_optics_analysis(self): self.principal_components_analysis() self.independent_components_analysis() - def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): + def harmonic_analysis( + self, guess_tunes=True, plot=True, compare_meas2model=True + ): r"""Linear optics analysis using sinusoidal model for TbT data. TbT motion at the i-th turn and j-th BPM in the timescale of less @@ -610,7 +612,10 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): Args: guess_tunes (bool, optional): whether to use the initial guess for the tunes from the data DFT or use the measured tunes. Defaults to - False. + True. + + plot (bool, optional): whether to plot analysis results (beta & + phase advance). Defaults to True. compare_meas2model (bool, optional): whether to plot measured and nominal beta-functions and BPMs phase-advance, as well as @@ -705,17 +710,18 @@ def harmonic_analysis(self, guess_tunes=False, compare_meas2model=True): fitting_data["traj"+label+"_init_fit"] = initial_fit fitting_data["traj"+label+"_final_fit"] = final_fit fitting_data["fitting"+label+"_residue"] = residue - # Plot results (beta_beating and phase adv. error) - self.plot_betabeat_and_phase_error( - beta_model, beta_fit, phases_model, phases_fit, - title=f"Sinusoidal fit analysis - beta{label} & phase{label}", - compare_meas2model=compare_meas2model - ) - # TODO: compare fit with trajectory + # Plot results (beta_beating and phase adv. error) + if plot: + title = f"Sinusoidal fit analysis - beta{label} & phase{label}" + self.plot_betabeat_and_phase_error( + beta_model, beta_fit, phases_model, phases_fit, + title=title, + compare_meas2model=compare_meas2model + ) self.fitting_data = fitting_data - def principal_components_analysis(self, compare_meas2model=True): + def principal_components_analysis(self, plot, compare_meas2model=True): r"""Peforms Principal Components Analysis (PCA). Calculates beta-functions and betatron phase-advance at the BPMs using @@ -745,6 +751,9 @@ def principal_components_analysis(self, compare_meas2model=True): are calculated from the columns of the mixing matrix. Args: + plot (bool, optiional): whether to plot the analysis results. + Defaults to True. + compare_meas2model (bool, optional): whether to plot measured and nominal beta-functions and BPMs phase-advance, as well as beta-beting and phase-advance errors or plot only beta-beating and @@ -799,11 +808,12 @@ def principal_components_analysis(self, compare_meas2model=True): ) # plot_results - self.plot_betabeat_and_phase_error( - beta_model, beta, phase_model, phase, - title=f"PCA Analysis: beta{label} & phase{label}", - compare_meas2model=compare_meas2model - ) + if plot: + self.plot_betabeat_and_phase_error( + beta_model, beta, phase_model, phase, + title=f"PCA Analysis: beta{label} & phase{label}", + compare_meas2model=compare_meas2model + ) # save analysis data pca_data["singular_values_"+label] = svals @@ -814,7 +824,7 @@ def principal_components_analysis(self, compare_meas2model=True): self.pca_data = pca_data def independent_components_analysis( - self, n_components=8, compare_meas2model=True + self, n_components=8, plot=True, compare_meas2model=True ): r"""Peforms Independent Components Analysis (ICA). @@ -847,6 +857,10 @@ def independent_components_analysis( Args: n_components (int, optional): number of independent components to decompose the data + + plot (bool, optiional): whether to plot the analysis results. + Defaults to True + compare_meas2model (bool, optional): whether to plot measured and nominal beta-functions and BPMs phase-advance, as well as beta-beting and phase-advance errors or plot only beta-beating and @@ -914,11 +928,12 @@ def independent_components_analysis( ) # plot results - self.plot_betabeat_and_phase_error( - beta_model, beta, phase_model, phase, - title=f"ICA Analysis: beta{label} & phase{label}", - compare_meas2model=compare_meas2model - ) + if plot: + self.plot_betabeat_and_phase_error( + beta_model, beta, phase_model, phase, + title=f"ICA Analysis: beta{label} & phase{label}", + compare_meas2model=compare_meas2model + ) # save results ica_data["source_signals_"+label] = signals From e8ee1e673c4dcddff4836fb7c4be4d3718bb6e9f Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 23 May 2024 17:01:45 -0300 Subject: [PATCH 091/144] COMMISS.TBT.ENH.MEAS: turn-off pulse before changing magnets strengths --- apsuite/commisslib/measure_tbt_data.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b32c7ec0..6c17f30c 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -188,6 +188,10 @@ def set_magnets_state(self, state, wait_mon=True): timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] + # turn-off pulse before changing strengths + pingh_ok = pingh.cmd_turn_off_pulse(timeout) + pingv_ok = pingv.cmd_turn_off_pulse(timeout) + # Power and strengths if state.get("pingh_pwr", False): pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) From 45b896b30943e972de93dad84e6cf7c8c62cf9f8 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 7 Jun 2024 10:16:39 -0300 Subject: [PATCH 092/144] COMMISS.TBT.ENH: use int part of tunes only to get nominal optics --- apsuite/commisslib/measure_tbt_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 6c17f30c..723e092a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -648,7 +648,7 @@ def harmonic_analysis( from_turn2turn[0], from_turn2turn[1] + 1, 1 ) traj = self.trajx[turns_slice, :].copy() - tune = tunex + 49. + tune = tunex label = "x" else: from_turn2turn = self.trajy_turns_slice @@ -656,7 +656,7 @@ def harmonic_analysis( from_turn2turn[0], from_turn2turn[1] + 1, 1 ) traj = self.trajy[turns_slice, :].copy() - tune = tuney + 14. + tune = tuney label = "y" # TODO: adapt turns selection to grant integer nr of cycles @@ -690,7 +690,7 @@ def harmonic_analysis( phases_fit = _np.array(params_fit[-nbpms:]) if self.model_optics is None: - self._get_nominal_optics(tunes=(tunex+49., tuney+14.)) + self._get_nominal_optics(tunes=(tunex + 49., tuney + 14.)) beta_model = self.model_optics["beta"+label] phases_model = self.model_optics["phase"+label] @@ -785,7 +785,7 @@ def principal_components_analysis(self, plot, compare_meas2model=True): traj = self.trajy label = "y" - tunes = self.tunex, self.tuney + tunes = self.tunex + 49., self.tuney + 14. if self.model_optics is None: self._get_nominal_optics(tunes) @@ -893,7 +893,7 @@ def independent_components_analysis( else: traj = self.trajy label = "y" - tunes = self.tunex, self.tuney + tunes = self.tunex + 49., self.tuney + 14. if self.model_optics is None: self._get_nominal_optics(tunes) From c2241b7329f38182a9a96e63435fd261dfded650 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 7 Jun 2024 11:45:09 -0300 Subject: [PATCH 093/144] COMMISS.TBT.ANLY.ENH: add selection of BPMs & plot relative phase adv. errs instead of abs errs. --- apsuite/commisslib/measure_tbt_data.py | 62 +++++++++++++++++++------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 723e092a..a183363f 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -419,6 +419,7 @@ def __init__(self, filename="", isonline=False): self.trajx_turns_slice = None self.trajy_turns_slice = None + self.bpms2use = None self.model_optics = None self.fitting_data = None @@ -721,7 +722,8 @@ def harmonic_analysis( self.plot_betabeat_and_phase_error( beta_model, beta_fit, phases_model, phases_fit, title=title, - compare_meas2model=compare_meas2model + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use ) self.fitting_data = fitting_data @@ -814,9 +816,11 @@ def principal_components_analysis(self, plot, compare_meas2model=True): # plot_results if plot: self.plot_betabeat_and_phase_error( - beta_model, beta, phase_model, phase, + beta_model, beta, + phase_model, phase, title=f"PCA Analysis: beta{label} & phase{label}", - compare_meas2model=compare_meas2model + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use ) # save analysis data @@ -934,9 +938,11 @@ def independent_components_analysis( # plot results if plot: self.plot_betabeat_and_phase_error( - beta_model, beta, phase_model, phase, + beta_model, beta, + phase_model, phase, title=f"ICA Analysis: beta{label} & phase{label}", - compare_meas2model=compare_meas2model + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use ) # save results @@ -1101,9 +1107,18 @@ def plot_rms_residue(self, axis=0): def plot_betabeat_and_phase_error( self, beta_model, beta_meas, phase_model, phase_meas, title=None, - compare_meas2model=False + compare_meas2model=False, bpms2use=None ): """.""" + beta_model, beta_meas = beta_model.copy(), beta_meas.copy() + phase_model, phase_meas = phase_model.copy(), phase_meas.copy() + + if bpms2use is not None: + beta_model[~bpms2use] = _np.nan + beta_meas[~bpms2use] = _np.nan + phase_model[~bpms2use] = _np.nan + phase_meas[~bpms2use] = _np.nan + if compare_meas2model: fig, axs = _mplt.subplots(2, 2, figsize=(15, 10)) else: @@ -1117,42 +1132,55 @@ def plot_betabeat_and_phase_error( # Beta plots if compare_meas2model: ax_beta = axs[0, 0] - ax_beta.plot(beta_model, "o-", label="Model", mfc="none") - ax_beta.plot(beta_meas, "o--", label="Meas", mfc="none") + ax_beta.plot( + beta_model, "o-", label="Model", mfc="none" + ) + ax_beta.plot( + beta_meas, "o--", label="Meas", mfc="none" + ) ax_beta.set_ylabel("beta function") ax_beta.legend() # Beta beating plot ax_beat = axs[0, 1] if compare_meas2model else axs[0] beta_beat = (beta_model - beta_meas) / beta_model + beta_beat *= 100 # [%] ax_beat.plot( - beta_beat * 100, + beta_beat, "o-", - label=f"rms = {beta_beat.std()*100:.2f} %", + label=f"rms = {beta_beat[~_np.isnan(beta_beat)].std():.2f} %", mfc="none", ) ax_beat.set_ylabel("beta beating [%]") ax_beat.legend() - # Phase plots + # Phase advance plot if compare_meas2model: model_phase_advance = _np.diff(phase_model) meas_phase_advance = _np.abs(_np.diff(phase_meas)) + ax_phase = axs[1, 0] - ax_phase.plot(model_phase_advance, "o-", label="Model", mfc="none") - ax_phase.plot(meas_phase_advance, "o--", label="Meas", mfc="none") + ax_phase.plot( + model_phase_advance, "o-", label="Model", mfc="none" + ) + ax_phase.plot( + meas_phase_advance, "o--", label="Meas", mfc="none" + ) ax_phase.set_ylabel("BPMs phase advance [rad]") ax_phase.legend() + # Phase advance error plot ax_phase_err = axs[1, 1] if compare_meas2model else axs[1] - phase_advance_err = model_phase_advance - meas_phase_advance + rph_err = model_phase_advance - meas_phase_advance + rph_err /= model_phase_advance + rph_err *= 100 # [%] ax_phase_err.plot( - phase_advance_err, + rph_err, "o-", - label=f"rms. error = {phase_advance_err.std():.2f}", + label=f"rms.err={rph_err[~_np.isnan(rph_err)].std():.2f}", mfc="none" ) - ax_phase_err.set_ylabel("BPMs phase advance error [rad]") + ax_phase_err.set_ylabel("BPMs fractional phase advance error [rad]") ax_phase_err.legend() fig.supxlabel("BPM index") From 0ec8981074b9c50ef1de32d06c342adcddddac56 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 10 Jun 2024 13:34:23 -0300 Subject: [PATCH 094/144] COMMISS.TBT.ANLY.ENH: add `plot_trajs_spectrum` --- apsuite/commisslib/measure_tbt_data.py | 30 ++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index a183363f..e5c8a829 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -957,9 +957,35 @@ def equilibrium_params_analysis(self): raise NotImplementedError # plotting methods - def plot_traj_spectrum(): + def plot_trajs_spectrum( + self, bpm_index=0, trajx=None, trajy=None, title="" + ): """.""" - raise NotImplementedError + trajx = self.trajx if trajx is None else trajx + trajy = self.trajy if trajy is None else trajy + + trajx_spec, tunesx = self.calc_spectrum(trajx, fs=1) + trajy_spec, tunesy = self.calc_spectrum(trajy, fs=1) + + fig, axs = _mplt.subplots(2, 1, figsize=(12, 8)) + + axs[0].plot( + tunesx, _np.abs(trajx_spec)[:, bpm_index]) + axs[1].plot( + tunesy, _np.abs(trajy_spec)[:, bpm_index]) + + if not title: + title = "kicked beam trajectories spectrum \n" + title += f"kicks = ({self.params.hkick},{self.params.vkick}) mrad" + + axs[0].set_title(title) + + axs[0].set_ylabel(r'$x$ [mm]') + axs[1].set_ylabel(r'$y$ [mm]') + axs[1].set_xlabel('tune') + fig.tight_layout() + + return fig, axs def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): """Plot trajectories and sum-signal at a given BPM and timescale. From 66909c24b6c32bf2771235341eaf1051885541cb Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 10 Jun 2024 15:05:02 -0300 Subject: [PATCH 095/144] COMMISS.TBT.ANLY.ENH: add spatio-temporal modes analysis --- apsuite/commisslib/measure_tbt_data.py | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index e5c8a829..2fa06765 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -4,6 +4,7 @@ import time as _time import matplotlib.pyplot as _mplt +import matplotlib.gridspec as _gridspec import numpy as _np from sklearn.decomposition import FastICA as _FastICA from scipy.optimize import curve_fit as _curve_fit @@ -1095,6 +1096,88 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): _mplt.show() return fig, ax + def plot_modal_analysis(self): + """.""" + trajs = _np.concatenate((self.trajx, self.trajy), axis=1) + + u, s, vt = self.calc_svd(trajs, full_matrices=False) + + fig = _mplt.figure(figsize=(14, 12)) + + gs = _gridspec.GridSpec(4, 2) + + svals = fig.add_subplot(gs[0, 0]) + var = fig.add_subplot(gs[1, 0]) + + source1 = fig.add_subplot(gs[0, 1]) + source2 = fig.add_subplot(gs[1, 1], sharex=source1) + + spatial1 = fig.add_subplot(gs[2, 0]) + spatial2 = fig.add_subplot(gs[3, 0], sharex=spatial1) + + spec1 = fig.add_subplot(gs[2, 1]) + spec2 = fig.add_subplot(gs[3, 1], sharex=spec1, sharey=spec1) + + svals.plot(s, 'o', color='k', mfc='none') + svals.set_title('singular values spectrum') + svals.set_yscale("log") + + var.plot(_np.cumsum(s) / _np.sum(s), 'o', color="k", mfc='none') + var.set_title("explained variance") + var.set_xlabel("rank") + + source1.plot(u[:, 0], label='mode 0') + source1.plot(u[:, 1], label='mode 1') + source1.set_title("temporal modes - axis 1") + source1.set_xlabel("turns index") + source1.legend() + + source2.plot(u[:, 2], label='mode 2') + source2.plot(u[:, 3], label='mode 3') + source2.set_title("temporal modes - axis 2") + source2.set_xlabel("turns index") + source2.legend() + + spatial1.plot(vt.T[:, 0], label='mode 0') + spatial1.plot(vt.T[:, 1], label='mode 1') + spatial1.set_title("spatial modes - axis 1") + spatial1.set_xlabel("BPMs index (H/V)") + spatial1.legend() + + spatial2.plot(vt.T[:, 2], label='mode 2') + spatial2.plot(vt.T[:, 3], label='mode 3') + spatial2.set_title("spatial modes - axis 2") + spatial2.set_xlabel("BPMs index (H/V)") + spatial2.legend() + + freq, fourier = _np.fft.rfftfreq(n=u.shape[0]), _np.fft.rfft(u, axis=0) + + spec1.plot( + freq, _np.abs(fourier)[:, 0], 'o-', + color="C0", mfc="none", label='mode 0' + ) + spec1.plot( + freq, _np.abs(fourier)[:, 1], 'x-', color="C0", label='mode 1' + ) + spec1.set_title("temporal modes spectrum - axis 1") + spec1.set_xlabel("fractional tune") + spec1.legend() + + spec2.plot( + freq, _np.abs(fourier)[:, 2], 'o-', + color="C1", mfc="none", label='mode 2' + ) + spec2.plot( + freq, _np.abs(fourier)[:, 3], 'x-', color="C1", label='mode 3' + ) + spec2.set_title("temporal modes spectrum - axis 2") + spec2.set_xlabel("fractional tune") + spec2.legend() + + _mplt.tight_layout() + + _mplt.show() + def plot_fit_comparison(self, bpm_index=0, timescale=0): """Plot comparison of fit vs. acqusitions. From 446654493f5927c452ceb7eb02aab8ee69b208ac Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 15 Jul 2024 18:18:07 -0300 Subject: [PATCH 096/144] COMMISS.TBT.MEAS.ENH: add `restore_init_state` option & pingers kicks scaling factors --- apsuite/commisslib/measure_tbt_data.py | 43 +++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2fa06765..30e75891 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -35,9 +35,12 @@ def __init__(self): self._pingers2kick = "none" # 'H', 'V' or 'HV' self.hkick = None # [mrad] self.vkick = None # [mrad] + self.pingh_calibration = 1.5416651659146232 + self.pingv_calibration = 1.02267573 self.trigpingh_delay = None self.trigpingv_delay = None self.magnets_timeout = 120. + self.restore_init_state = True def __str__(self): """.""" @@ -57,6 +60,9 @@ def __str__(self): stg += stmp("hkick", "same", "(current value will not be changed)") else: stg += ftmp("hkick", self.hkick, "[mrad]") + stg += ftmp("pingh_calibration", self.pingh_calibration, "") + stg += ftmp("pingv_calibration", self.pingv_calibration, "") + dly = self.trigpingh_delay if dly is None: stg += stmp( @@ -79,6 +85,7 @@ def __str__(self): ) else: stg += ftmp("trigpingv_delay", dly, "[us]") + stg += stmp("restore_init_state", self.restore_init_state, "") return stg @property @@ -258,15 +265,17 @@ def set_magnets_strength( """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] if hkick is not None: + hkick *= self.params.pingh_calibration + print("changing pingh") pingh.set_strength(hkick, timeout=0, wait_mon=wait_mon) else: - print("pingh not changed (None-type strength)") pingh_ok = True if vkick is not None: + vkick *= self.params.pingv_calibration + print("changing pingv") pingv.set_strength(vkick, timeout=0, wait_mon=wait_mon) else: - print("pingv not changed (None-type strength)") pingv_ok = True # wait magnets ramp and check if correctly set @@ -275,6 +284,7 @@ def set_magnets_strength( t0 = _time.time() if hkick is not None: + print("waiting pingh") pingh_ok = pingh.set_strength( hkick, tol=0.05 * abs(hkick), @@ -286,6 +296,7 @@ def set_magnets_strength( magnets_timeout -= elapsed_time if vkick is not None: + print("waiting pingv") pingv_ok = pingv.set_strength( vkick, tol=0.05 * abs(vkick), @@ -352,18 +363,22 @@ def do_measurement(self): except Exception as e: print(f"An error occurred during acquisition: {e}") else: - print("Did not measure. Restoring magnets & timing initial state.") - timing_ok = self.prepare_timing(init_timing_state) - if not timing_ok: - print("Timing was not restored to initial state.") - mags_ok = self.set_magnets_state(init_magnets_state, wait_mon=False) - if not mags_ok: - msg = "Magnets state or strengths were not restored." - msg += "Restore manually." - print(msg) - print(init_magnets_state) - else: - print("Magnets state & strengths successfully restored.") + print("Did not measure") + if self.params.restore_init_state: + print("Restoring magnets & timing initial state.") + timing_ok = self.prepare_timing(init_timing_state) + if not timing_ok: + print("Timing was not restored to initial state.") + mags_ok = self.set_magnets_state( + init_magnets_state, wait_mon=False + ) + if not mags_ok: + msg = "Magnets state or strengths were not restored." + msg += "Restore manually." + print(msg) + print(init_magnets_state) + else: + print("Magnets state & strengths successfully restored.") print("Measurement finished.") def get_data(self): From 7b32ffdba589affe69e8e876f9a5234e87609b38 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 30 Aug 2024 10:13:21 -0300 Subject: [PATCH 097/144] COMMISS.TBT.ANLY.ENH: automatically update `trajs_turn_slice` --- apsuite/commisslib/measure_tbt_data.py | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 30e75891..f177ba82 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -425,7 +425,7 @@ def __init__(self, filename="", isonline=False): self.trajx, self.trajy = None, None # zero-mean trajectories in [mm] self.trajsum = None - self.tunex, self.tuney = None, None + self._tunex, self._tuney = None, None self.acq_rate = None self.nrsamples_pre = None self.nrsamples_post = None @@ -566,6 +566,32 @@ def fname(self, val): self._fname = val self.load_and_apply(val) + @property + def tunex(self): + """.""" + return self._tunex + + @tunex.setter + def tunex(self, val): + """.""" + if val is not None: + self._tunex = val + nrpre = self.nrsamples_pre + self.trajx_turns_slice = (nrpre, nrpre + int(1 / val)) + + @property + def tuney(self): + """.""" + return self._tuney + + @tuney.setter + def tuney(self, val): + """.""" + if val is not None: + self._tuney = val + nrpre = self.nrsamples_pre + self.trajy_turns_slice = (nrpre, nrpre + int(1 / val)) + def load_and_apply(self, fname): """Load data and copy often used data to class attributes.""" keys = super().load_and_apply(fname) From 704dcefe70d36d5e9fa3c7684f0ba5534268de16 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 30 Aug 2024 10:14:41 -0300 Subject: [PATCH 098/144] COMMISS.TBT.ANLY.ENH: handle missing data in `load_and_apply` --- apsuite/commisslib/measure_tbt_data.py | 31 +++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f177ba82..f696c369 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -599,29 +599,30 @@ def load_and_apply(self, fname): print("The following keys were not used:") print(" ", str(keys)) + # make often used data attributes data = self.data - timestamp = _datetime.datetime.fromtimestamp(data["timestamp"]) + epoch_tmstp = data.get("timestamp", None) + timestamp = _datetime.datetime.fromtimestamp( + epoch_tmstp + ) if epoch_tmstp is not None else None self.timestamp = timestamp - trajx, trajy = data["orbx"].copy() * 1e-3, data["orby"].copy() * 1e-3 - trajsum = data["sumdata"].copy() + trajx = data.get("orbx", None).copy() * 1e-3 + trajy = data.get("orby", None).copy() * 1e-3 + trajsum = data.get("sumdata", None).copy() # zero mean in samples dimension trajx -= trajx.mean(axis=0)[None, :] trajy -= trajy.mean(axis=0)[None, :] self.trajx, self.trajy, self.trajsum = trajx, trajy, trajsum - self.tunex, self.tuney = data['tunex'], data['tuney'] - self.acq_rate = data["acq_rate"] - self.rf_freq = data["rf_frequency"] - self.sampling_freq = self.data["sampling_frequency"] - self.switching_freq = self.data["switching_frequency"] - self.nrsamples_pre = self.data["nrsamples_pre"] - self.nrsamples_post = self.data["nrsamples_post"] - - nrpre = self.nrsamples_pre - self.trajx_turns_slice = (nrpre, nrpre + int(1 / self.tunex)) - self.trajy_turns_slice = (nrpre, nrpre + int(1 / self.tuney)) - + self.nrsamples_pre = data.get("nrsamples_pre", None) + self.nrsamples_post = data.get("nrsamples_post", None) + self.tunex = data.get('tunex', None) + self.tuney = data.get('tuney', None) + self.acq_rate = data.get("acq_rate", None) + self.rf_freq = data.get("rf_frequency", None) + self.sampling_freq = data.get("sampling_frequency", None) + self.switching_freq = data.get("switching_frequency", None) return def linear_optics_analysis(self): From fa8909c0b2ab4230c545b91881e98f6129194898 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 30 Aug 2024 10:34:57 -0300 Subject: [PATCH 099/144] COMMISS.TBT.ANLY.ENH: add error bars for PCA analysis --- apsuite/commisslib/measure_tbt_data.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f696c369..d60c0860 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -843,7 +843,7 @@ def principal_components_analysis(self, plot, compare_meas2model=True): umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) # collect source signals and mixing matrix - signals = umat * _np.sqrt(traj.shape[0] - 1) # whiten signals + signals = umat * _np.sqrt(traj.shape[0] - 1) # data signals mixing_matrix = vtmat.T @ _np.diag(svals) mixing_matrix /= _np.sqrt(traj.shape[0] - 1) @@ -856,6 +856,20 @@ def principal_components_analysis(self, plot, compare_meas2model=True): sin_mode, cos_mode, beta_model ) + # calculate error bars as in Appendix A of + # Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance + # and ${\beta}$ function measurements using model-independent + # analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. + # DOI 10.1103/PhysRevSTAB.6.104001 + + signal = _np.sqrt(_np.sum(svals[:2]**2)) + noise = _np.sqrt(_np.sum(svals[2:]**2)) + snr = signal / noise + nrsamples = self.nrsamples_pre + self.nrsamples_post + phase_error = 1 / snr / _np.sqrt(nrsamples) + phase_error *= _np.sqrt(beta_model.mean() / 2 / beta_model) + beta_error = 2 * beta_model * phase_error + # plot_results if plot: self.plot_betabeat_and_phase_error( @@ -872,6 +886,9 @@ def principal_components_analysis(self, plot, compare_meas2model=True): pca_data["mixing_matrix_"+label] = mixing_matrix pca_data["beta"+label] = beta pca_data["phase"+label] = phase + pca_data["snr"+label] = snr + pca_data["beta"+label+"_err"] = beta_error + pca_data["phase"+label+"_err"] = phase_error self.pca_data = pca_data def independent_components_analysis( From 9f9d907d6def6167aff9dd48da9f8d2d734ef119 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 30 Aug 2024 10:53:07 -0300 Subject: [PATCH 100/144] COMMISS.TBT.ANLY.ENH: set unwrap discont to pi --- apsuite/commisslib/measure_tbt_data.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d60c0860..2b2d09ac 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1466,9 +1466,7 @@ def get_beta_and_phase_from_betatron_modes( beta = (sin_mode**2 + cos_mode**2) beta /= _np.std(beta) / _np.std(beta_model) phase = _np.arctan2(sin_mode, cos_mode) - phase = _np.unwrap(phase, discont=2.6) - # 2.6 was set because it was the largest phase advance - # observed in the model for both x and y motion + phase = _np.unwrap(phase, discont=_np.pi) return beta, phase def _get_nominal_optics(self, tunes=None, chroms=None): From 1d48a38fd3dde9bb0132444ef96de66eed75a09c Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 4 Sep 2024 13:16:12 -0300 Subject: [PATCH 101/144] COMMISS.TBT.ANLY.ENH: add PCA analysis for stacked xy data --- apsuite/commisslib/measure_tbt_data.py | 257 +++++++++++++++++++------ 1 file changed, 200 insertions(+), 57 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2b2d09ac..2c5b630e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -770,46 +770,65 @@ def harmonic_analysis( ) self.fitting_data = fitting_data - def principal_components_analysis(self, plot, compare_meas2model=True): - r"""Peforms Principal Components Analysis (PCA). + def principal_components_analysis( + self, + stackxy=True, + planes="xy", + plot=True, + compare_meas2model=True + ): + r"""Peform linear optics analysis Principal Components Analysis (PCA). Calculates beta-functions and betatron phase-advance at the BPMs using - PCA. PCA aims to identify principal axes along which the covariance + PCA. + + PCA aims to identify principal axes along which the covariance matrix of the data is diagonal. For betatron-dominated motion, there - are two pricipal components (cosine and sine modes) that can be - related to betatron functions and phase advance, as described in refs - [1,2]. + are two pricipal components (cosine and sine modes) for each plane + (horizontal and vertical) that can be related to the betatron + functions and phase advance at the corresponding planes, as described + in refs [1,2]. - For a beam history matrix with BPMs arranged along the columns and - turn-by turn samples along the rows (nturns x nbpms), it can be shown - (ref. [1]) that the principal components diagonalizing the covariance - matrix are the columns of the V matrix, where V refers to the spatial - patterns of the data, as accessed by its singular-value decomposition: - data = U S Vt. The first two columns of V are the beatron modes. + For a beam history matrix X with with turn-by-turn samples along the + rows (nturns x nbpms), it can be shown (ref. [1]) that the principal + components diagonalizing the covariance matrix are the columns of the + spatial patterns matrix, V, where V is such that H = U S V.T. The + leading modes of the V matrix are the beatron modes [2]. In the language of Blind Source Separation, if the data matrix X can be expressed as a linear mixture of uncorrelated source signals - arranged as the columns of matrix S, i.e. X = S A^T, then the - mixing matrix A can be identified with A = V S^T / sqrt{nturns - 1}. - Acting on X with A's pseudo-inverse, the unmixing matrix, gives the - whitened uncorrelated source signals S = U \sqrt{nturns - 1}, - with S^T S / (nsamples - 1 ) = identity. This choice for the mixing - matrix and the whitened sources follows scikit-learn's [2] convention - and is compatible with the convention adopted in the independent - components analysis (ICA), where the betatron modes and phase advances - are calculated from the columns of the mixing matrix. + arranged as the columns of matrix S, i.e. X = S A^T, then, using PCA, + the mixing matrix A can be identified with + A = V @ S.T / sqrt{nturns - 1}. + Acting on X with A's pseudo-inverse (the unmixing matrix) gives the + whitened, uncorrelated source signals S = U \sqrt{nturns - 1}, with + S.T @ S / (nturns - 1 ) = identity. This choice for the normalization + of mixing matrix and whitened sources follows scikit-learn's [2] + convention and is compatible with the convention adopted in the + independent components analysis (ICA). Args: - plot (bool, optiional): whether to plot the analysis results. - Defaults to True. - - compare_meas2model (bool, optional): whether to plot measured and - nominal beta-functions and BPMs phase-advance, as well as - beta-beting and phase-advance errors or plot only beta-beating and - phase-advance-errors. Defaults to True + stackxy (bool, optional): stack hrizontal (x) and vertical (y) + trajectories and carry out analysis with nsamples x 320 history + matrix. Defaults to True. In this case, the leading 2 pairs of + SVD modes are associated with 4 dominant singular values, + which are identified with the horizontal and vertical betatron + modes. + + planes (str, optional): "x", "y" or "xy". Analyze the horizontal, + the vertical or both planes one at a time. Defaults to "xy", + in which case the x and y planes will be analyzed seprately. + Not used if stackxy is True. + + plot (bool, optional): whether to plot the analysis results + (beta-function and phase-advances). Defaults to True. + + compare_meas2model (bool, optional): whether to plot a comparison + between the measured and nominal beta-functions and BPMs + phase-advance, as well as the beta-beating and phase-advance + errors. Defaults to True. References: - [1] Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance and ${\beta}$ function measurements using model-independent analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. DOI 10.1103/ @@ -821,23 +840,118 @@ def principal_components_analysis(self, plot, compare_meas2model=True): [3] Scikit-learn examples. "Blind Source Separation using FastICA". https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py """ + tunes = self.tunex + 49., self.tuney + 14. + if self.model_optics is None: + self._get_nominal_optics(tunes) + + # get model optics + betax_model = self.model_optics["betax"] + betay_model = self.model_optics["betay"] + phasex_model = self.model_optics["phasex"] + phasey_model = self.model_optics["phasey"] + pca_data = dict() - for pinger in self.params.pingers2kick: - if pinger == "h": + if stackxy: + traj = _np.concatenate((self.trajx, self.trajy), axis=1) + # perform PCA via SVD of history matrix + umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) + + # collect source signals and mixing matrix + signals = umat * _np.sqrt(traj.shape[0] - 1) # data signals + mixing_matrix = vtmat.T @ _np.diag(svals) + mixing_matrix /= _np.sqrt(traj.shape[0] - 1) + + # calulate tunes of the source signals + tunes1, spec1 = self.calc_spectrum(signals[:, 0], axis=0) + tunes2, spec2 = self.calc_spectrum(signals[:, 2], axis=0) + # modes 0 & 1 and modes 2 & 3 have have equal spectra + # therefore, it suffices to analyze mode 0 and mode 2 only + + tune1 = tunes1[_np.argmax(_np.abs(spec1))] + tune2 = tunes2[_np.argmax(_np.abs(spec2))] + + # identify which signal is which (x or y) + xidcs, yidcs = self.identify_modes( + tune1, tune2, self.tunex, self.tuney + ) + + # extract betatron sine & cosine modes fom mixing matrix + sin_modex = mixing_matrix[:160, xidcs[-1]] + cos_modex = mixing_matrix[:160, xidcs[0]] + sin_modey = mixing_matrix[160:, yidcs[-1]] + cos_modey = mixing_matrix[160:, yidcs[0]] + + # calculate beta function & phase from betatron modes + betax, phasex = self.get_beta_and_phase_from_betatron_modes( + sin_modex, cos_modex, betax_model + ) + betay, phasey = self.get_beta_and_phase_from_betatron_modes( + sin_modey, cos_modey, betay_model + ) + + # concatenate results + beta = _np.concatenate((betax, betay)) + phase = _np.concatenate((phasex, phasey)) + + # calculate signal variance + signalx = _np.sqrt(_np.sum(svals[xidcs[0]:xidcs[1] + 1]**2)) + signaly = _np.sqrt(_np.sum(svals[yidcs[0]:yidcs[1] + 1]**2)) + # and signal noise + noise = _np.sqrt(_np.sum(svals[3:]**2)) + snrx, snry = signalx / noise, signaly / noise + + # calculate error bars as in Appendix A of + # Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance + # and ${\beta}$ function measurements using model-independent + # analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. + # DOI 10.1103/PhysRevSTAB.6.104001 + + nrsamples = self.nrsamples_pre + self.nrsamples_post + phasex_error = 1 / snrx / _np.sqrt(nrsamples) + phasex_error *= _np.sqrt(betax_model.mean() / 2 / betax_model) + betax_error = 2 * betax_model * phasex_error + + phasey_error = 1 / snry / _np.sqrt(nrsamples) + phasey_error *= _np.sqrt(betay_model.mean() / 2 / betay_model) + betay_error = 2 * betay_model * phasey_error + + # TODO: plot stacked data + # plot_results + if plot: + self.plot_betabeat_and_phase_error( + _np.concatenate((betax_model, betay_model)), beta, + _np.concatenate((phasex_model, phasey_model)), phase, + title="PCA Analysis: beta & phase (stacked x/y)", + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use + ) + + # save analysis data + pca_data["singular_values"] = svals + pca_data["source_signals"] = signals + pca_data["mixing_matrix"] = mixing_matrix + pca_data["xidcs"] = xidcs + pca_data["yidcs"] = yidcs + pca_data["beta"] = beta + pca_data["phase"] = phase + pca_data["snrx"] = snrx + pca_data["snry"] = snry + pca_data["beta_err"] = _np.concatenate((betax_error, betay_error)) + pca_data["phase_err"] = _np.concatenate( + (phasex_error, phasey_error) + ) + self.pca_data = pca_data + return + + for plane in planes: + if plane == "x": traj = self.trajx - label = "x" else: traj = self.trajy - label = "y" - - tunes = self.tunex + 49., self.tuney + 14. - - if self.model_optics is None: - self._get_nominal_optics(tunes) # get model optics - beta_model = self.model_optics["beta"+label] - phase_model = self.model_optics["phase"+label] + beta_model = self.model_optics["beta"+plane] + phase_model = self.model_optics["phase"+plane] # perform PCA via SVD of history matrix umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) @@ -856,14 +970,8 @@ def principal_components_analysis(self, plot, compare_meas2model=True): sin_mode, cos_mode, beta_model ) - # calculate error bars as in Appendix A of - # Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance - # and ${\beta}$ function measurements using model-independent - # analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. - # DOI 10.1103/PhysRevSTAB.6.104001 - - signal = _np.sqrt(_np.sum(svals[:2]**2)) - noise = _np.sqrt(_np.sum(svals[2:]**2)) + signal = _np.sqrt(_np.sum(svals[:2]**2)) # signal variance + noise = _np.sqrt(_np.sum(svals[2:]**2)) # white noise snr = signal / noise nrsamples = self.nrsamples_pre + self.nrsamples_post phase_error = 1 / snr / _np.sqrt(nrsamples) @@ -875,22 +983,57 @@ def principal_components_analysis(self, plot, compare_meas2model=True): self.plot_betabeat_and_phase_error( beta_model, beta, phase_model, phase, - title=f"PCA Analysis: beta{label} & phase{label}", + title=f"PCA Analysis: beta{plane} & phase{plane}", compare_meas2model=compare_meas2model, bpms2use=self.bpms2use ) # save analysis data - pca_data["singular_values_"+label] = svals - pca_data["source_signals_"+label] = signals - pca_data["mixing_matrix_"+label] = mixing_matrix - pca_data["beta"+label] = beta - pca_data["phase"+label] = phase - pca_data["snr"+label] = snr - pca_data["beta"+label+"_err"] = beta_error - pca_data["phase"+label+"_err"] = phase_error + pca_data["singular_values_"+plane] = svals + pca_data["source_signals_"+plane] = signals + pca_data["mixing_matrix_"+plane] = mixing_matrix + pca_data["beta"+plane] = beta + pca_data["phase"+plane] = phase + pca_data["snr"+plane] = snr + pca_data["beta"+plane+"_err"] = beta_error + pca_data["phase"+plane+"_err"] = phase_error self.pca_data = pca_data + def identify_modes(self, tune1, tune2, tunex, tuney): + """Identify the x and y betatron modes in mixing matrix. + + When calculating the mixing matrix via PCA or ICA, the horizontal and + betatron modes need the be determined. PCA sorts the modes with + increasing variance (singular values), while ICA sorts the modes + arbitrairly. By calculating the tune of the source signals + corresponding to a given betatron mode and comparing it to the + reference horizontal and vertical tunes, the hortizontal and vertical + beatron modes can be identified. + + Args: + tune1 (float): tune of the 1st & 2nd mode + tune2 (float): tune of the 3rd and 4th mode + tunex (float): _description_ + tuney (float): _description_ + + Returns: + _type_: _description_ + """ + diff1x = _np.abs(tune1 - tunex) + diff1y = _np.abs(tune1 - tuney) + diff2x = _np.abs(tune2 - tunex) + diff2y = _np.abs(tune2 - tuney) + + # Assign based on minimum differences + if diff1x < diff2x and diff2y < diff1y: + xidcs = 0, 1 + yidcs = 2, 3 + else: + xidcs = 2, 3 + yidcs = 0, 1 + + return xidcs, yidcs + def independent_components_analysis( self, n_components=8, plot=True, compare_meas2model=True ): From 91fa11ea4fe55c30d7fe21a986ef47daa10668ef Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 4 Sep 2024 13:18:40 -0300 Subject: [PATCH 102/144] COMMISS.TBT.ANLY.ENH: update default chromaticity & add TODOs --- apsuite/commisslib/measure_tbt_data.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 2c5b630e..c829f73b 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1195,13 +1195,13 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): Args: bpm_index (int, optional): Which BPM's reading to show. - Defaults to 0 (first BPM). + Defaults to 0 (first BPM). timescale (int, optional): Turn-by-turn timescale, where: timescale = 0 : ~ 20 turns; harmonic motion timescale = 1 : ~ 200 turns; chromaticity decoherence - modulation + modulation timescale = 2 : ~ 2000 turns; transverse decoherence - modulations + modulations Defaults to 0. compare_fit (bool, optional): whether to plot acquisitions and the fitting and the fit residue. Defaults to False. @@ -1300,6 +1300,7 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): def plot_modal_analysis(self): """.""" + # TODO: reuse code from `principal_components_analysis` trajs = _np.concatenate((self.trajx, self.trajy), axis=1) u, s, vt = self.calc_svd(trajs, full_matrices=False) @@ -1421,6 +1422,7 @@ def plot_betabeat_and_phase_error( compare_meas2model=False, bpms2use=None ): """.""" + # TODO: plot error bars if they are available beta_model, beta_meas = beta_model.copy(), beta_meas.copy() phase_model, phase_meas = phase_model.copy(), phase_meas.copy() @@ -1633,11 +1635,12 @@ def _get_nominal_optics(self, tunes=None, chroms=None): tunecorr = _TuneCorr(model, acc="SI") tunecorr.correct_parameters(goal_parameters=tunes) - chroms = (2.5, 2.5) if chroms is None else chroms + chroms = (3.8, 3.1) if chroms is None else chroms chromcorr = _ChromCorr(model, acc="SI") chromcorr.correct_parameters(goal_parameters=chroms) famdata = _si.get_family_data(model) + # TODO: add also EdTeng optics calculations twiss, *_ = _pa.optics.calc_twiss( accelerator=model, indices="open" ) From 957ddde0311da4762e81a0c9dbdabaa827a29d24 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 13 Sep 2024 09:20:00 -0300 Subject: [PATCH 103/144] COMMISS.TBT.BUG: spec & tunes calculation in stackedxy PCA --- apsuite/commisslib/measure_tbt_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c829f73b..d4f3f3a1 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -862,8 +862,8 @@ def principal_components_analysis( mixing_matrix /= _np.sqrt(traj.shape[0] - 1) # calulate tunes of the source signals - tunes1, spec1 = self.calc_spectrum(signals[:, 0], axis=0) - tunes2, spec2 = self.calc_spectrum(signals[:, 2], axis=0) + spec1, tunes1 = self.calc_spectrum(signals[:, 0], axis=0) + spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) # modes 0 & 1 and modes 2 & 3 have have equal spectra # therefore, it suffices to analyze mode 0 and mode 2 only From 79105c1fa65c9e3b3a01a782946edb75ba4fc905 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 17 Sep 2024 14:38:15 -0300 Subject: [PATCH 104/144] COMMISS.TBT.ANLY.WIP: add SOBI for ICA --- apsuite/commisslib/measure_tbt_data.py | 76 ++++++++++++++++---------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d4f3f3a1..be6524d3 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1,5 +1,4 @@ """.""" - import datetime as _datetime import time as _time @@ -9,6 +8,7 @@ from sklearn.decomposition import FastICA as _FastICA from scipy.optimize import curve_fit as _curve_fit +from mathphys.sobi import SOBI as _SOBI from siriuspy.devices import PowerSupplyPU, Trigger from siriuspy.sofb.csdev import SOFBFactory @@ -1035,31 +1035,38 @@ def identify_modes(self, tune1, tune2, tunex, tuney): return xidcs, yidcs def independent_components_analysis( - self, n_components=8, plot=True, compare_meas2model=True + self, n_components=8, method="FastICA", plot=True, + compare_meas2model=True ): r"""Peforms Independent Components Analysis (ICA). Calculates beta-functions and betatron phase-advance at the BPMs using - ICA. ICA aims to identify the linear transformation (unmixing matrix) + ICA. + + ICA aims to identify the linear transformation (unmixing matrix) revealing statistically independent source signals. Just as in PCA, the beatron motion sine and cosine modes can be used to calculate beta-functions and BPMs phase advances. While PCA aims to identify the linear transformation revealing uncorrelated source signals, ICA seeks the transformation - revealing statistically independent signals, a stronger - requirement than uncorrelatedness. ICA often performs better at blind - source separation for linear mixtures of sinals with non-gaussian - distributions, and can be more robust at betatron motion - identification when there are contaminating signals or bad - acquisitions. - - ICA can be implemented with second-oderd blind source identification + revealing statistically independent signals, a requirement much + stronger than uncorrelatedness. + + ICA often performs better at blind source separation for linear + mixtures of sinals with non-gaussian distributions, which is relevant + for when several source signals have similar variance. This method + thus is generally more robust at betatron motion identification when + there are contaminating signals, bad acquisitions or similar variance + between horizontal and vertical modes (partticularly relevant when + betatron coupling is significant). + + ICA can be implemented with second-order blind source identification (SOBI) [1], based on simultaneous diagonalization of the time-shifted - data covariance matrix(not implemented), or with information-theoretic - approaches for maximizing statistical indpendence of source signals - [2]. We use the latter, as implemented in scikit-learn's FastICA [3, - 4]. + data covariance matrices or with information-theoretic + approaches seeking the maximization of the statistical indpendence of + the estimated source signals [2]. We use the latter, as implemented in + scikit-learn's FastICA [3,4]. The variance convention is the same as in PCA analysis: whiten source signals, with the mixing matrix containing the modes energy/variance @@ -1110,15 +1117,23 @@ def independent_components_analysis( phase_model = self.model_optics["phase"+label] # perform Independent Component Analysis (ICA) - ica = _FastICA( - n_components=n_components, - whiten="unit-variance", - algorithm="deflation", - tol=1e-12 - ) - + if method == "FastICA": + ica = _FastICA( + n_components=n_components, + whiten="unit-variance", + algorithm="parallel", + tol=1e-12 + ) + if method == "SOBI": + ica = _SOBI( + n_components=n_components, + n_lags=5, + whiten="unit-variance", + isreal=True, + verbose=False, + ) # collect source signals & mixing matrix - signals = ica.fit_transform(traj) # whiten signals + signals = ica.fit_transform(traj) mixing_matrix = ica.mixing_ # determine betatron modes from mixing matrix @@ -1576,17 +1591,22 @@ def calc_beta_and_action(self, amplitudes, nominal_beta): beta = amplitudes**2 / action return beta, action - def calc_beta_and_phase_with_pca(self, matrix, beta_model): + def calc_beta_and_phase_with_pca(self, matrix, beta_model=None): """.""" _, svals, vtmat = self.calc_svd(matrix, full_matrices=False) + + if beta_model is None: + phase_meas = _np.arctan2( + svals[1] * vtmat[1, :], svals[0] * vtmat[0, :] + ) + phase_meas = (_np.unwrap(phase_meas)) + return phase_meas + beta_meas = ( svals[0] ** 2 * vtmat[0, :] ** 2 + svals[1] ** 2 * vtmat[1, :] ** 2 ) beta_meas /= _np.std(beta_meas) / _np.std(beta_model) - phase_meas = _np.arctan2( - svals[1] * vtmat[1, :], svals[0] * vtmat[0, :] - ) - phase_meas = _np.abs(_np.unwrap(phase_meas)) # why the abs? + return beta_meas, phase_meas def get_beta_and_phase_from_betatron_modes( From fba6f15f16615b4a5ac464a7c67825e8d8d3b810 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 14 Oct 2024 09:11:38 -0300 Subject: [PATCH 105/144] COMMISS.TBT.MEAS.BUG: normalize by scaling factor when getting pingers' strengths --- apsuite/commisslib/measure_tbt_data.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index be6524d3..b67c56b2 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -173,7 +173,11 @@ def prepare_timing(self, state=None): def get_magnets_strength(self): """.""" - return self.devices["pingh"].strength, self.devices["pingv"].strength + pingh_cal = self.params.pingh_calibration + pingv_cal = self.params.pingv_calibration + pingh_str = self.devices["pingh"].strength / pingh_cal + pingv_str = self.devices["pingv"].strength / pingv_cal + return pingh_str, pingv_str def get_magnets_state(self): """.""" From f41a970d667f1b6cfc9b35b29759fb0bb2106e27 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 14 Oct 2024 09:58:18 -0300 Subject: [PATCH 106/144] COMMISS.TBT,BPMS_SIGNAlS.MNT: general formating --- apsuite/commisslib/meas_bpms_signals.py | 222 +++++++++-------- apsuite/commisslib/measure_tbt_data.py | 318 +++++++++++++----------- 2 files changed, 284 insertions(+), 256 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 5cd72816..88b59888 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -1,4 +1,5 @@ """.""" + import time as _time import numpy as _np @@ -19,43 +20,45 @@ def __init__(self): """.""" self.trigbpm_delay = None self.trigbpm_nrpulses = 1 - self._timing_event = 'Study' + self._timing_event = "Study" self.event_delay = None - self.event_mode = 'External' + self.event_mode = "External" self.timeout = 40 self.nrpoints_before = 0 self.nrpoints_after = 20000 - self.acq_rate = 'FAcq' + self.acq_rate = "FAcq" self.acq_repeat = False - self.signals2acq = 'XY' + self.signals2acq = "XY" def __str__(self): """.""" - ftmp = '{0:26s} = {1:9.6f} {2:s}\n'.format - dtmp = '{0:26s} = {1:9d} {2:s}\n'.format - stmp = '{0:26s} = {1:9} {2:s}\n'.format - stg = '' + ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format + dtmp = "{0:26s} = {1:9d} {2:s}\n".format + stmp = "{0:26s} = {1:9} {2:s}\n".format + stg = "" dly = self.trigbpm_delay if dly is None: stg += stmp( - 'trigbpm_delay', 'same', '(current value will not be changed)') + "trigbpm_delay", "same", "(current value will not be changed)" + ) else: - stg += ftmp('trigbpm_delay', dly, '[us]') - stg += dtmp('trigbpm_nrpulses', self.trigbpm_nrpulses, '') - stg += stmp('timing_event', self.timing_event, '') + stg += ftmp("trigbpm_delay", dly, "[us]") + stg += dtmp("trigbpm_nrpulses", self.trigbpm_nrpulses, "") + stg += stmp("timing_event", self.timing_event, "") dly = self.event_delay if dly is None: stg += stmp( - 'event_delay', 'same', '(current value will not be changed)') + "event_delay", "same", "(current value will not be changed)" + ) else: - stg += ftmp('event_delay', dly, '[us]') - stg += stmp('event_mode', self.event_mode, '') - stg += ftmp('timeout', self.timeout, '[s]') - stg += dtmp('nrpoints_before', self.nrpoints_before, '') - stg += dtmp('nrpoints_after', self.nrpoints_after, '') - stg += stmp('acq_rate', self.acq_rate, '') - stg += dtmp('acq_repeat', self.acq_repeat, '') - stg += stmp('signals2acq', str(self.signals2acq), '') + stg += ftmp("event_delay", dly, "[us]") + stg += stmp("event_mode", self.event_mode, "") + stg += ftmp("timeout", self.timeout, "[s]") + stg += dtmp("nrpoints_before", self.nrpoints_before, "") + stg += dtmp("nrpoints_after", self.nrpoints_after, "") + stg += stmp("acq_rate", self.acq_rate, "") + stg += dtmp("acq_repeat", self.acq_repeat, "") + stg += stmp("signals2acq", str(self.signals2acq), "") return stg @property @@ -73,8 +76,8 @@ def from_dict(self, params_dict): """.""" dic = dict() for key, val in params_dict.items(): - if key.startswith('orbit_'): # compatibility with old data - key = key.replace('orbit_', '') + if key.startswith("orbit_"): # compatibility with old data + key = key.replace("orbit_", "") dic[key] = val return super().from_dict(dic) @@ -82,8 +85,8 @@ def from_dict(self, params_dict): class AcqBPMsSignals(_BaseClass): """.""" - BPM_TRIGGER = 'SI-Fam:TI-BPM' - PSM_TRIGGER = 'SI-Fam:TI-BPM-PsMtm' + BPM_TRIGGER = "SI-Fam:TI-BPM" + PSM_TRIGGER = "SI-Fam:TI-BPM-PsMtm" def __init__(self, isonline=True, ispost_mortem=False): """.""" @@ -94,9 +97,11 @@ def __init__(self, isonline=True, ispost_mortem=False): self.create_devices() calc_positions_from_amplitudes = staticmethod( - FamBPMs.calc_positions_from_amplitudes) - calc_positions_from_amplitudes.__doc__ = \ + FamBPMs.calc_positions_from_amplitudes + ) + calc_positions_from_amplitudes.__doc__ = ( FamBPMs.calc_positions_from_amplitudes.__doc__ + ) def load_and_apply(self, fname: str): """Load and apply `data` and `params` from pickle or HDF5 file. @@ -111,40 +116,42 @@ def load_and_apply(self, fname: str): ret = super().load_and_apply(fname) data = dict() for key, val in self.data.items(): - if key.startswith('bpms_'): # compatibility with old data - key = key.replace('bpms_', '') + if key.startswith("bpms_"): # compatibility with old data + key = key.replace("bpms_", "") data[key] = val self.data = data return ret def create_devices(self): """.""" - self.devices['currinfo'] = CurrInfoSI() - self.devices['fambpms'] = FamBPMs( - devname=FamBPMs.DEVICES.SI, ispost_mortem=self._ispost_mortem, - props2init='acq') - self.devices['tune'] = Tune(Tune.DEVICES.SI) + self.devices["currinfo"] = CurrInfoSI() + self.devices["fambpms"] = FamBPMs( + devname=FamBPMs.DEVICES.SI, + ispost_mortem=self._ispost_mortem, + props2init="acq", + ) + self.devices["tune"] = Tune(Tune.DEVICES.SI) trigname = self.BPM_TRIGGER if self._ispost_mortem: trigname = self.PSM_TRIGGER - self.devices['trigbpm'] = Trigger(trigname) - self.devices['evt_study'] = Event('Study') - self.devices['evg'] = EVG() - self.devices['rfgen'] = RFGen() + self.devices["trigbpm"] = Trigger(trigname) + self.devices["evt_study"] = Event("Study") + self.devices["evg"] = EVG() + self.devices["rfgen"] = RFGen() def get_timing_state(self): """.""" - trigbpm = self.devices['trigbpm'] + trigbpm = self.devices["trigbpm"] state = dict() - state['trigbpm_source'] = trigbpm.source_str - state['trigbpm_nrpulses'] = trigbpm.nr_pulses - state['trigbpm_delay'] = trigbpm.delay + state["trigbpm_source"] = trigbpm.source_str + state["trigbpm_nrpulses"] = trigbpm.nr_pulses + state["trigbpm_delay"] = trigbpm.delay evt = self._get_event(self.params.timing_event) if evt is not None: - state['evt_delay'] = evt.delay - state['evt_mode'] = evt.mode_str + state["evt_delay"] = evt.delay + state["evt_mode"] = evt.mode_str return state def recover_timing_state(self, state): @@ -155,111 +162,116 @@ def prepare_timing(self, state=None): """.""" state = dict() if state is None else state - trigbpm = self.devices['trigbpm'] - dly = state.get('trigbpm_delay', self.params.trigbpm_delay) + trigbpm = self.devices["trigbpm"] + dly = state.get("trigbpm_delay", self.params.trigbpm_delay) if dly is not None: trigbpm.delay = dly trigbpm.nr_pulses = state.get( - 'trigbpm_nrpulses', self.params.trigbpm_nrpulses) - src = state.get('trigbpm_source', self.params.timing_event) + "trigbpm_nrpulses", self.params.trigbpm_nrpulses + ) + src = state.get("trigbpm_source", self.params.timing_event) trigbpm.source = src evt = self._get_event(self.params.timing_event) if evt is not None: - dly = state.get('evt_delay', self.params.event_delay) + dly = state.get("evt_delay", self.params.event_delay) if dly is not None: evt.delay = dly - evt.mode = state.get('evt_mode', self.params.event_mode) - self.devices['evg'].cmd_update_events() + evt.mode = state.get("evt_mode", self.params.event_mode) + self.devices["evg"].cmd_update_events() def prepare_bpms_acquisition(self): """.""" - fambpms = self.devices['fambpms'] + fambpms = self.devices["fambpms"] prms = self.params fambpms.mturn_signals2acq = self.params.signals2acq return fambpms.config_mturn_acquisition( nr_points_after=prms.nrpoints_after, nr_points_before=prms.nrpoints_before, - acq_rate=prms.acq_rate, repeat=prms.acq_repeat) + acq_rate=prms.acq_rate, + repeat=prms.acq_repeat, + ) def acquire_data(self): """.""" - fambpms = self.devices['fambpms'] + fambpms = self.devices["fambpms"] ret = self.prepare_bpms_acquisition() - tag = self._bpm_tag(idx=abs(int(ret))-1) + tag = self._bpm_tag(idx=abs(int(ret)) - 1) if ret < 0: - print(tag + ' did not finish last acquisition.') + print(tag + " did not finish last acquisition.") elif ret > 0: - print(tag + ' is not ready for acquisition.') + print(tag + " is not ready for acquisition.") fambpms.reset_mturn_initial_state() # NOTE: user must trigger timing event - print('Ready for acquisition. Waiting for trigger event.') + print("Ready for acquisition. Waiting for trigger event.") time0 = _time.time() ret = fambpms.wait_update_mturn(timeout=self.params.timeout) - print(f'it took {_time.time()-time0:02f}s to update bpms') + print(f"it took {_time.time()-time0:02f}s to update bpms") if ret != 0: - print('There was a problem with acquisition') + print("There was a problem with acquisition") if ret > 0: - tag = self._bpm_tag(idx=int(ret)-1) + tag = self._bpm_tag(idx=int(ret) - 1) pos = fambpms.mturn_signals2acq[int((ret % 1) * 10) - 1] - print('This BPM did not update: ' + tag + ', signal ' + pos) + print("This BPM did not update: " + tag + ", signal " + pos) elif ret == -1: - print('Initial timestamps were not defined') + print("Initial timestamps were not defined") elif ret == -2: - print('Signals size changed.') + print("Signals size changed.") return self.data = self.get_data() def get_data(self): """Get Orbit and auxiliary data.""" - fbpms = self.devices['fambpms'] + fbpms = self.devices["fambpms"] mturn_orbit = fbpms.get_mturn_signals() data = dict() - data['ispost_mortem'] = self._ispost_mortem - data['timestamp'] = _time.time() - rf_freq = self.devices['rfgen'].frequency - data['rf_frequency'] = rf_freq - data['stored_current'] = self.devices['currinfo'].current + data["ispost_mortem"] = self._ispost_mortem + data["timestamp"] = _time.time() + rf_freq = self.devices["rfgen"].frequency + data["rf_frequency"] = rf_freq + data["stored_current"] = self.devices["currinfo"].current if list(self.params.signals2acq) != list(fbpms.mturn_signals2acq): - raise ValueError('signals2acq was not configured properly.') + raise ValueError("signals2acq was not configured properly.") elif len(mturn_orbit) != len(fbpms.mturn_signals2acq): raise ValueError( - 'Lenght of signals2acq does not match signals acquired.') + "Lenght of signals2acq does not match signals acquired." + ) for i, sig in enumerate(self.params.signals2acq): sig = sig.lower() - name = 'sumdata' - if sig in 'xy': - name = 'orb' + sig - elif sig in 'abcd': - name = 'ampl' + sig - elif sig == 'q': - name = 'posq' + name = "sumdata" + if sig in "xy": + name = "orb" + sig + elif sig in "abcd": + name = "ampl" + sig + elif sig == "q": + name = "posq" data[name] = mturn_orbit[i] - tune = self.devices['tune'] - data['tunex'], data['tuney'] = tune.tunex, tune.tuney + tune = self.devices["tune"] + data["tunex"], data["tuney"] = tune.tunex, tune.tuney bpm0 = fbpms.devices[0] - data['acq_rate'] = bpm0.acq_channel_str - data['sampling_frequency'] = fbpms.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['trigbpm'].delay_raw - data['switching_mode'] = bpm0.switching_mode_str - data['switching_frequency'] = fbpms.get_switching_frequency(rf_freq) - data['tunex_enable'] = tune.enablex - data['tuney_enable'] = tune.enabley - data['timing_state'] = self.get_timing_state() + data["acq_rate"] = bpm0.acq_channel_str + data["sampling_frequency"] = fbpms.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["trigbpm"].delay_raw + data["switching_mode"] = bpm0.switching_mode_str + data["switching_frequency"] = fbpms.get_switching_frequency(rf_freq) + data["tunex_enable"] = tune.enablex + data["tuney_enable"] = tune.enabley + data["timing_state"] = self.get_timing_state() return data @staticmethod def filter_data_frequencies( - orb, fmin, fmax, fsampling, keep_within_range=True): + orb, fmin, fmax, fsampling, keep_within_range=True + ): """Filter acquisition matrix considering a frequency range. Args: @@ -275,7 +287,7 @@ def filter_data_frequencies( """ dft = _sp_fft.rfft(orb, axis=0) - freq = _sp_fft.rfftfreq(orb.shape[0], d=1/fsampling) + freq = _sp_fft.rfftfreq(orb.shape[0], d=1 / fsampling) if keep_within_range: idcs = (freq < fmin) | (freq > fmax) dft[idcs] = 0 @@ -299,7 +311,7 @@ def filter_switching_cycles(orb, freq_sampling, freq_switching): """ # Calculate the number of samples per switching cycle - sw_sample_size = round(freq_sampling/freq_switching) + sw_sample_size = round(freq_sampling / freq_switching) osiz = orb.shape[0] nr_sws = osiz // sw_sample_size siz = nr_sws * sw_sample_size @@ -313,12 +325,12 @@ def filter_switching_cycles(orb, freq_sampling, freq_switching): # Replicate the switching signature to match the size of original data sw_pert = _np.tile(sw_sig, (1, nr_sws)) if osiz > siz: - sw_pert = _np.hstack([sw_pert, sw_sig[:, :osiz-siz]]) + sw_pert = _np.hstack([sw_pert, sw_sig[:, : osiz - siz]]) # Subtract the replicated switching signature from the original data return orb - sw_pert.T @staticmethod - def simulate_data_decimation(orb, downsampling=12*8): + def simulate_data_decimation(orb, downsampling=12 * 8): """Simulate data decimation by application of moving average filter. Args: @@ -332,8 +344,8 @@ def simulate_data_decimation(orb, downsampling=12*8): """ ds = downsampling - fil = _np.ones(ds)/ds - return _sp_sig.convolve(orb, fil[:, None], mode='same') + fil = _np.ones(ds) / ds + return _sp_sig.convolve(orb, fil[:, None], mode="same") @staticmethod def calc_spectrum(data, fs=1.0, axis=0): @@ -350,8 +362,8 @@ def calc_spectrum(data, fs=1.0, axis=0): freq (numpy.ndarray): Frequency for which the DFT was calculated. """ - spec = _sp_fft.rfft(data, axis=axis)/data.shape[axis] - freq = _sp_fft.rfftfreq(data.shape[axis], d=1/fs) + spec = _sp_fft.rfft(data, axis=axis) / data.shape[axis] + freq = _sp_fft.rfftfreq(data.shape[axis], d=1 / fs) return spec, freq @staticmethod @@ -387,18 +399,18 @@ def calc_hilbert_transform(data, axis=0): return _sp_sig.hilbert(data, axis=axis) def _bpm_tag(self, idx): - names = self.devices['fambpms'].bpm_names - return f'{names[idx]:s} (idx={idx:d})' + names = self.devices["fambpms"].bpm_names + return f"{names[idx]:s} (idx={idx:d})" def _get_event(self, evtname): if evtname not in _HLTimeSearch.get_configurable_hl_events(): - print('WARN:Event is not configurable.') + print("WARN:Event is not configurable.") return None - stg = f'evt_{evtname.lower():s}' + stg = f"evt_{evtname.lower():s}" evt = self.devices.get(stg, Event(evtname)) if evt.wait_for_connection(timeout=10): self.devices[stg] = evt else: - print('ERR:Event not connected.') + print("ERR:Event not connected.") return None return evt diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index b67c56b2..ffedb881 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1,19 +1,18 @@ """.""" + import datetime as _datetime import time as _time -import matplotlib.pyplot as _mplt import matplotlib.gridspec as _gridspec +import matplotlib.pyplot as _mplt import numpy as _np -from sklearn.decomposition import FastICA as _FastICA -from scipy.optimize import curve_fit as _curve_fit - +import pyaccel as _pa from mathphys.sobi import SOBI as _SOBI +from pymodels import si as _si +from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU, Trigger from siriuspy.sofb.csdev import SOFBFactory - -import pyaccel as _pa -from pymodels import si as _si +from sklearn.decomposition import FastICA as _FastICA from ..optics_analysis import ChromCorr as _ChromCorr, TuneCorr as _TuneCorr from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ @@ -22,6 +21,7 @@ class TbTDataParams(_AcqBPMsSignalsParams): """.""" + BPMS_NAMES = SOFBFactory.create("SI").bpm_names def __init__(self): @@ -39,7 +39,7 @@ def __init__(self): self.pingv_calibration = 1.02267573 self.trigpingh_delay = None self.trigpingv_delay = None - self.magnets_timeout = 120. + self.magnets_timeout = 120.0 self.restore_init_state = True def __str__(self): @@ -252,14 +252,14 @@ def set_magnets_state(self, state, wait_mon=True): else: pingh_ok = pingh_ok and pingh.cmd_turn_off_pulse(timeout) msg = "pingh pulse set" if pingh_ok else "pingh pulse not set" - print("\t"+msg) + print("\t" + msg) if state["pingv_pulse"]: pingv_ok = pingv_ok and pingv.cmd_turn_on_pulse(timeout) else: pingv_ok = pingv_ok and pingv.cmd_turn_off_pulse(timeout) msg = "pingv pulse set" if pingv_ok else "pingv pulse not set" - print("\t"+msg) + print("\t" + msg) return pingh_ok and pingv_ok @@ -418,6 +418,7 @@ def get_default_fname(self): class TbTDataAnalysis(MeasureTbTData): """.""" + SYNCH_TUNE = 0.004713 # check this def __init__(self, filename="", isonline=False): @@ -489,19 +490,15 @@ def __str__(self): stg += stmp("switching_mode", data["switching_mode"], "") stg += stmp("switching_frequency", data["switching_frequency"], "") stg += stmp( - "trigbpm_source", - data["timing_state"]["trigbpm_source"], - "" + "trigbpm_source", data["timing_state"]["trigbpm_source"], "" ) stg += stmp( "trigbpm_nrpulses", data["timing_state"]["trigbpm_nrpulses"], - "" + "", ) stg += stmp( - "trigbpm_delay", - data["timing_state"]["trigbpm_delay"], - "" + "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" ) stg += "\n" @@ -509,51 +506,39 @@ def __str__(self): stg += "\n" stg += stmp( - "trigpingh_state", - data["timing_state"]["trigpingh_state"], - "" + "trigpingh_state", data["timing_state"]["trigpingh_state"], "" ) stg += stmp( "trigpingh_source", data["timing_state"]["trigpingh_source"], - "" + "", ) stg += stmp( - "trigpingh_delay", - data["timing_state"]["trigpingh_delay"], - "" + "trigpingh_delay", data["timing_state"]["trigpingh_delay"], "" ) stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") stg += stmp( - "pingh_pulse", - data["magnets_state"]["pingh_pulse"], - "" + "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" ) stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") stg += "\n" stg += stmp( - "trigpingv_state", - data["timing_state"]["trigpingv_state"], - "" + "trigpingv_state", data["timing_state"]["trigpingv_state"], "" ) stg += stmp( "trigpingv_source", data["timing_state"]["trigpingv_source"], - "" + "", ) stg += stmp( - "trigpingv_delay", - data["timing_state"]["trigpingv_delay"], - "" + "trigpingv_delay", data["timing_state"]["trigpingv_delay"], "" ) stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") stg += stmp( - "pingv_pulse", - data["magnets_state"]["pingv_pulse"], - "" + "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" ) stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") @@ -606,9 +591,11 @@ def load_and_apply(self, fname): # make often used data attributes data = self.data epoch_tmstp = data.get("timestamp", None) - timestamp = _datetime.datetime.fromtimestamp( - epoch_tmstp - ) if epoch_tmstp is not None else None + timestamp = ( + _datetime.datetime.fromtimestamp(epoch_tmstp) + if epoch_tmstp is not None + else None + ) self.timestamp = timestamp trajx = data.get("orbx", None).copy() * 1e-3 trajy = data.get("orby", None).copy() * 1e-3 @@ -621,8 +608,8 @@ def load_and_apply(self, fname): self.nrsamples_pre = data.get("nrsamples_pre", None) self.nrsamples_post = data.get("nrsamples_post", None) - self.tunex = data.get('tunex', None) - self.tuney = data.get('tuney', None) + self.tunex = data.get("tunex", None) + self.tuney = data.get("tuney", None) self.acq_rate = data.get("acq_rate", None) self.rf_freq = data.get("rf_frequency", None) self.sampling_freq = data.get("sampling_frequency", None) @@ -738,9 +725,9 @@ def harmonic_analysis( phases_fit = _np.array(params_fit[-nbpms:]) if self.model_optics is None: - self._get_nominal_optics(tunes=(tunex + 49., tuney + 14.)) - beta_model = self.model_optics["beta"+label] - phases_model = self.model_optics["phase"+label] + self._get_nominal_optics(tunes=(tunex + 49.0, tuney + 14.0)) + beta_model = self.model_optics["beta" + label] + phases_model = self.model_optics["phase" + label] # fit beta-function & action from fitted amplitudes & nominal beta beta_fit, action = self.calc_beta_and_action(amps, beta_model) @@ -749,38 +736,37 @@ def harmonic_analysis( residue = traj - final_fit # store fitting data - fitting_data["tune"+label] = tune - fitting_data["tune_err"+label] = params_error[0] - fitting_data["amplitudes"+label] = amps - fitting_data["beta"+label] = beta_fit + fitting_data["tune" + label] = tune + fitting_data["tune_err" + label] = params_error[0] + fitting_data["amplitudes" + label] = amps + fitting_data["beta" + label] = beta_fit # TODO: propagate amplitude errors to beta errors - fitting_data["beta"+label+"_err"] = params_error[1 : nbpms + 1] - fitting_data["phase"+label] = phases_fit - fitting_data["phase"+label+"_err"] = params_error[-nbpms:] - fitting_data["action"+label] = action + fitting_data["beta" + label + "_err"] = params_error[1 : nbpms + 1] + fitting_data["phase" + label] = phases_fit + fitting_data["phase" + label + "_err"] = params_error[-nbpms:] + fitting_data["action" + label] = action # TODO: propagate amplitude errors to action error - fitting_data["traj"+label+"_init_fit"] = initial_fit - fitting_data["traj"+label+"_final_fit"] = final_fit - fitting_data["fitting"+label+"_residue"] = residue + fitting_data["traj" + label + "_init_fit"] = initial_fit + fitting_data["traj" + label + "_final_fit"] = final_fit + fitting_data["fitting" + label + "_residue"] = residue # Plot results (beta_beating and phase adv. error) if plot: title = f"Sinusoidal fit analysis - beta{label} & phase{label}" self.plot_betabeat_and_phase_error( - beta_model, beta_fit, phases_model, phases_fit, + beta_model, + beta_fit, + phases_model, + phases_fit, title=title, compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use + bpms2use=self.bpms2use, ) self.fitting_data = fitting_data def principal_components_analysis( - self, - stackxy=True, - planes="xy", - plot=True, - compare_meas2model=True - ): + self, stackxy=True, planes="xy", plot=True, compare_meas2model=True + ): r"""Peform linear optics analysis Principal Components Analysis (PCA). Calculates beta-functions and betatron phase-advance at the BPMs using @@ -844,7 +830,7 @@ def principal_components_analysis( [3] Scikit-learn examples. "Blind Source Separation using FastICA". https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py """ - tunes = self.tunex + 49., self.tuney + 14. + tunes = self.tunex + 49.0, self.tuney + 14.0 if self.model_optics is None: self._get_nominal_optics(tunes) @@ -898,10 +884,10 @@ def principal_components_analysis( phase = _np.concatenate((phasex, phasey)) # calculate signal variance - signalx = _np.sqrt(_np.sum(svals[xidcs[0]:xidcs[1] + 1]**2)) - signaly = _np.sqrt(_np.sum(svals[yidcs[0]:yidcs[1] + 1]**2)) + signalx = _np.sqrt(_np.sum(svals[xidcs[0] : xidcs[1] + 1] ** 2)) + signaly = _np.sqrt(_np.sum(svals[yidcs[0] : yidcs[1] + 1] ** 2)) # and signal noise - noise = _np.sqrt(_np.sum(svals[3:]**2)) + noise = _np.sqrt(_np.sum(svals[3:] ** 2)) snrx, snry = signalx / noise, signaly / noise # calculate error bars as in Appendix A of @@ -923,11 +909,13 @@ def principal_components_analysis( # plot_results if plot: self.plot_betabeat_and_phase_error( - _np.concatenate((betax_model, betay_model)), beta, - _np.concatenate((phasex_model, phasey_model)), phase, + _np.concatenate((betax_model, betay_model)), + beta, + _np.concatenate((phasex_model, phasey_model)), + phase, title="PCA Analysis: beta & phase (stacked x/y)", compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use + bpms2use=self.bpms2use, ) # save analysis data @@ -954,8 +942,8 @@ def principal_components_analysis( traj = self.trajy # get model optics - beta_model = self.model_optics["beta"+plane] - phase_model = self.model_optics["phase"+plane] + beta_model = self.model_optics["beta" + plane] + phase_model = self.model_optics["phase" + plane] # perform PCA via SVD of history matrix umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) @@ -974,8 +962,8 @@ def principal_components_analysis( sin_mode, cos_mode, beta_model ) - signal = _np.sqrt(_np.sum(svals[:2]**2)) # signal variance - noise = _np.sqrt(_np.sum(svals[2:]**2)) # white noise + signal = _np.sqrt(_np.sum(svals[:2] ** 2)) # signal variance + noise = _np.sqrt(_np.sum(svals[2:] ** 2)) # white noise snr = signal / noise nrsamples = self.nrsamples_pre + self.nrsamples_post phase_error = 1 / snr / _np.sqrt(nrsamples) @@ -985,22 +973,24 @@ def principal_components_analysis( # plot_results if plot: self.plot_betabeat_and_phase_error( - beta_model, beta, - phase_model, phase, + beta_model, + beta, + phase_model, + phase, title=f"PCA Analysis: beta{plane} & phase{plane}", compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use + bpms2use=self.bpms2use, ) # save analysis data - pca_data["singular_values_"+plane] = svals - pca_data["source_signals_"+plane] = signals - pca_data["mixing_matrix_"+plane] = mixing_matrix - pca_data["beta"+plane] = beta - pca_data["phase"+plane] = phase - pca_data["snr"+plane] = snr - pca_data["beta"+plane+"_err"] = beta_error - pca_data["phase"+plane+"_err"] = phase_error + pca_data["singular_values_" + plane] = svals + pca_data["source_signals_" + plane] = signals + pca_data["mixing_matrix_" + plane] = mixing_matrix + pca_data["beta" + plane] = beta + pca_data["phase" + plane] = phase + pca_data["snr" + plane] = snr + pca_data["beta" + plane + "_err"] = beta_error + pca_data["phase" + plane + "_err"] = phase_error self.pca_data = pca_data def identify_modes(self, tune1, tune2, tunex, tuney): @@ -1039,8 +1029,11 @@ def identify_modes(self, tune1, tune2, tunex, tuney): return xidcs, yidcs def independent_components_analysis( - self, n_components=8, method="FastICA", plot=True, - compare_meas2model=True + self, + n_components=8, + method="FastICA", + plot=True, + compare_meas2model=True, ): r"""Peforms Independent Components Analysis (ICA). @@ -1111,14 +1104,14 @@ def independent_components_analysis( else: traj = self.trajy label = "y" - tunes = self.tunex + 49., self.tuney + 14. + tunes = self.tunex + 49.0, self.tuney + 14.0 if self.model_optics is None: self._get_nominal_optics(tunes) # get model optics - beta_model = self.model_optics["beta"+label] - phase_model = self.model_optics["phase"+label] + beta_model = self.model_optics["beta" + label] + phase_model = self.model_optics["phase" + label] # perform Independent Component Analysis (ICA) if method == "FastICA": @@ -1126,7 +1119,7 @@ def independent_components_analysis( n_components=n_components, whiten="unit-variance", algorithm="parallel", - tol=1e-12 + tol=1e-12, ) if method == "SOBI": ica = _SOBI( @@ -1160,18 +1153,20 @@ def independent_components_analysis( # plot results if plot: self.plot_betabeat_and_phase_error( - beta_model, beta, - phase_model, phase, + beta_model, + beta, + phase_model, + phase, title=f"ICA Analysis: beta{label} & phase{label}", compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use + bpms2use=self.bpms2use, ) # save results - ica_data["source_signals_"+label] = signals - ica_data["mixing_matrix_"+label] = mixing_matrix - ica_data["beta"+label] = beta - ica_data["phase"+label] = phase + ica_data["source_signals_" + label] = signals + ica_data["mixing_matrix_" + label] = mixing_matrix + ica_data["beta" + label] = beta + ica_data["phase" + label] = phase self.ica_data = ica_data def equilibrium_params_analysis(self): @@ -1191,10 +1186,8 @@ def plot_trajs_spectrum( fig, axs = _mplt.subplots(2, 1, figsize=(12, 8)) - axs[0].plot( - tunesx, _np.abs(trajx_spec)[:, bpm_index]) - axs[1].plot( - tunesy, _np.abs(trajy_spec)[:, bpm_index]) + axs[0].plot(tunesx, _np.abs(trajx_spec)[:, bpm_index]) + axs[1].plot(tunesy, _np.abs(trajy_spec)[:, bpm_index]) if not title: title = "kicked beam trajectories spectrum \n" @@ -1202,9 +1195,9 @@ def plot_trajs_spectrum( axs[0].set_title(title) - axs[0].set_ylabel(r'$x$ [mm]') - axs[1].set_ylabel(r'$y$ [mm]') - axs[1].set_xlabel('tune') + axs[0].set_ylabel(r"$x$ [mm]") + axs[1].set_ylabel(r"$y$ [mm]") + axs[1].set_xlabel("tune") fig.tight_layout() return fig, axs @@ -1234,8 +1227,8 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): print(msg) compare_fit = False - nr_pre = self.data['nrsamples_pre'] - nr_post = self.data['nrsamples_post'] + nr_pre = self.data["nrsamples_pre"] + nr_post = self.data["nrsamples_post"] if not timescale: nmax_x, nmax_y = int(1 / self.tunex), int(1 / self.tuney) @@ -1274,12 +1267,18 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): ax[0].plot( _np.arange(init, end + 1, 1), fit[:, bpm_index], - "x-", mfc="none", color="blue", label="fit" + "x-", + mfc="none", + color="blue", + label="fit", ) ax[0].plot( _np.arange(init, end + 1, 1), res[:, bpm_index], - "x-", mfc="none", color="green", label="residue" + "x-", + mfc="none", + color="green", + label="residue", ) ax[0].legend() ax[0].set_xlim(slicex) @@ -1295,12 +1294,18 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): ax[1].plot( _np.arange(init, end + 1, 1), fit[:, bpm_index], - "x-", mfc="none", color="red", label="fit" + "x-", + mfc="none", + color="red", + label="fit", ) ax[1].plot( _np.arange(init, end + 1, 1), res[:, bpm_index], - "x-", mfc="none", color="green", label="residue" + "x-", + mfc="none", + color="green", + label="residue", ) ax[1].legend() ax[1].set_xlim(slicey) @@ -1340,34 +1345,34 @@ def plot_modal_analysis(self): spec1 = fig.add_subplot(gs[2, 1]) spec2 = fig.add_subplot(gs[3, 1], sharex=spec1, sharey=spec1) - svals.plot(s, 'o', color='k', mfc='none') - svals.set_title('singular values spectrum') + svals.plot(s, "o", color="k", mfc="none") + svals.set_title("singular values spectrum") svals.set_yscale("log") - var.plot(_np.cumsum(s) / _np.sum(s), 'o', color="k", mfc='none') + var.plot(_np.cumsum(s) / _np.sum(s), "o", color="k", mfc="none") var.set_title("explained variance") var.set_xlabel("rank") - source1.plot(u[:, 0], label='mode 0') - source1.plot(u[:, 1], label='mode 1') + source1.plot(u[:, 0], label="mode 0") + source1.plot(u[:, 1], label="mode 1") source1.set_title("temporal modes - axis 1") source1.set_xlabel("turns index") source1.legend() - source2.plot(u[:, 2], label='mode 2') - source2.plot(u[:, 3], label='mode 3') + source2.plot(u[:, 2], label="mode 2") + source2.plot(u[:, 3], label="mode 3") source2.set_title("temporal modes - axis 2") source2.set_xlabel("turns index") source2.legend() - spatial1.plot(vt.T[:, 0], label='mode 0') - spatial1.plot(vt.T[:, 1], label='mode 1') + spatial1.plot(vt.T[:, 0], label="mode 0") + spatial1.plot(vt.T[:, 1], label="mode 1") spatial1.set_title("spatial modes - axis 1") spatial1.set_xlabel("BPMs index (H/V)") spatial1.legend() - spatial2.plot(vt.T[:, 2], label='mode 2') - spatial2.plot(vt.T[:, 3], label='mode 3') + spatial2.plot(vt.T[:, 2], label="mode 2") + spatial2.plot(vt.T[:, 3], label="mode 3") spatial2.set_title("spatial modes - axis 2") spatial2.set_xlabel("BPMs index (H/V)") spatial2.legend() @@ -1375,22 +1380,30 @@ def plot_modal_analysis(self): freq, fourier = _np.fft.rfftfreq(n=u.shape[0]), _np.fft.rfft(u, axis=0) spec1.plot( - freq, _np.abs(fourier)[:, 0], 'o-', - color="C0", mfc="none", label='mode 0' + freq, + _np.abs(fourier)[:, 0], + "o-", + color="C0", + mfc="none", + label="mode 0", ) spec1.plot( - freq, _np.abs(fourier)[:, 1], 'x-', color="C0", label='mode 1' + freq, _np.abs(fourier)[:, 1], "x-", color="C0", label="mode 1" ) spec1.set_title("temporal modes spectrum - axis 1") spec1.set_xlabel("fractional tune") spec1.legend() spec2.plot( - freq, _np.abs(fourier)[:, 2], 'o-', - color="C1", mfc="none", label='mode 2' + freq, + _np.abs(fourier)[:, 2], + "o-", + color="C1", + mfc="none", + label="mode 2", ) spec2.plot( - freq, _np.abs(fourier)[:, 3], 'x-', color="C1", label='mode 3' + freq, _np.abs(fourier)[:, 3], "x-", color="C1", label="mode 3" ) spec2.set_title("temporal modes spectrum - axis 2") spec2.set_xlabel("fractional tune") @@ -1415,7 +1428,7 @@ def plot_fit_comparison(self, bpm_index=0, timescale=0): """ return self.plot_trajs( bpm_index=bpm_index, timescale=timescale, compare_fit=True - ) + ) def plot_rms_residue(self, axis=0): """Plot RMS fitting residue along BPMs or along Turns. @@ -1430,15 +1443,24 @@ def plot_rms_residue(self, axis=0): along = "BPMs" if not axis else "Turns" _mplt.title(f"RMS fitting residue along {along}") _mplt.plot( - self.fitting_data["fitting"+label+"_residue"].std(axis=axis), - "o-", mfc='none' + self.fitting_data["fitting" + label + "_residue"].std( + axis=axis + ), + "o-", + mfc="none", ) _mplt.xlabel(f"{along} index") _mplt.ylabel("residue [mm]") def plot_betabeat_and_phase_error( - self, beta_model, beta_meas, phase_model, phase_meas, title=None, - compare_meas2model=False, bpms2use=None + self, + beta_model, + beta_meas, + phase_model, + phase_meas, + title=None, + compare_meas2model=False, + bpms2use=None, ): """.""" # TODO: plot error bars if they are available @@ -1464,12 +1486,8 @@ def plot_betabeat_and_phase_error( # Beta plots if compare_meas2model: ax_beta = axs[0, 0] - ax_beta.plot( - beta_model, "o-", label="Model", mfc="none" - ) - ax_beta.plot( - beta_meas, "o--", label="Meas", mfc="none" - ) + ax_beta.plot(beta_model, "o-", label="Model", mfc="none") + ax_beta.plot(beta_meas, "o--", label="Meas", mfc="none") ax_beta.set_ylabel("beta function") ax_beta.legend() @@ -1492,12 +1510,8 @@ def plot_betabeat_and_phase_error( meas_phase_advance = _np.abs(_np.diff(phase_meas)) ax_phase = axs[1, 0] - ax_phase.plot( - model_phase_advance, "o-", label="Model", mfc="none" - ) - ax_phase.plot( - meas_phase_advance, "o--", label="Meas", mfc="none" - ) + ax_phase.plot(model_phase_advance, "o-", label="Model", mfc="none") + ax_phase.plot(meas_phase_advance, "o--", label="Meas", mfc="none") ax_phase.set_ylabel("BPMs phase advance [rad]") ax_phase.legend() @@ -1510,7 +1524,7 @@ def plot_betabeat_and_phase_error( rph_err, "o-", label=f"rms.err={rph_err[~_np.isnan(rph_err)].std():.2f}", - mfc="none" + mfc="none", ) ax_phase_err.set_ylabel("BPMs fractional phase advance error [rad]") ax_phase_err.legend() @@ -1575,8 +1589,10 @@ def fit_harmonic_model(self, from_turn2turn, traj, *params_guess): xdata = self._get_independent_variables(from_turn2turn, nbpms) # TODO: add exception in case fit fails popt, pcov = _curve_fit( - f=self.harmonic_tbt_model, xdata=xdata, ydata=bpmdata, - p0=params_guess + f=self.harmonic_tbt_model, + xdata=xdata, + ydata=bpmdata, + p0=params_guess, ) return popt, _np.diagonal(pcov) @@ -1603,7 +1619,7 @@ def calc_beta_and_phase_with_pca(self, matrix, beta_model=None): phase_meas = _np.arctan2( svals[1] * vtmat[1, :], svals[0] * vtmat[0, :] ) - phase_meas = (_np.unwrap(phase_meas)) + phase_meas = _np.unwrap(phase_meas) return phase_meas beta_meas = ( @@ -1632,7 +1648,7 @@ def get_beta_and_phase_from_betatron_modes( phase: 160-array of betatron phase at BPMs """ - beta = (sin_mode**2 + cos_mode**2) + beta = sin_mode**2 + cos_mode**2 beta /= _np.std(beta) / _np.std(beta_model) phase = _np.arctan2(sin_mode, cos_mode) phase = _np.unwrap(phase, discont=_np.pi) From 3f1115c6b5540eb8723366726339a22c073d5389 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 14 Jan 2025 18:08:08 -0300 Subject: [PATCH 107/144] COMMISS.TBT.WIP: migrating meas class to a threaded meas --- apsuite/commisslib/measure_tbt_data.py | 166 ++++++++++++++++--------- 1 file changed, 105 insertions(+), 61 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ffedb881..a68d53f5 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -17,6 +17,7 @@ from ..optics_analysis import ChromCorr as _ChromCorr, TuneCorr as _TuneCorr from .meas_bpms_signals import AcqBPMsSignals as _AcqBPMsSignals, \ AcqBPMsSignalsParams as _AcqBPMsSignalsParams +from ..utils import ThreadedMeasBaseClass as _ThreadBaseClass class TbTDataParams(_AcqBPMsSignalsParams): @@ -98,7 +99,7 @@ def pingers2kick(self, val): self._pingers2kick = str(val).lower() -class MeasureTbTData(_AcqBPMsSignals): +class MeasureTbTData(_ThreadBaseClass): """.""" PINGERH_TRIGGER = "SI-01SA:TI-InjDpKckr" @@ -106,12 +107,21 @@ class MeasureTbTData(_AcqBPMsSignals): def __init__(self, isonline=False): """.""" - super().__init__(isonline=isonline, ispost_mortem=False) self.params = TbTDataParams() + super().__init__( + params=self.params, target=self._do_measurement, isonline=isonline + ) + self._is_postmortem = False + self._meas_finished_ok = True + self._timing_ok = True + self._magnets_ok = True + + if self.isonline: + self._create_devices() - def create_devices(self): + def _create_devices(self): """.""" - super().create_devices() + _AcqBPMsSignals().create_devices() self.devices["pingh"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_INJ_DPKCKR ) @@ -121,9 +131,9 @@ def create_devices(self): self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) return - def get_timing_state(self): + def _get_timing_state(self): """.""" - state = super().get_timing_state() + state = _AcqBPMsSignals().get_timing_state() trigpingh = self.devices["trigpingh"] state["trigpingh_state"] = trigpingh.state @@ -137,17 +147,19 @@ def get_timing_state(self): return state - def prepare_timing(self, state=None): + def _prepare_timing(self, state=None): """.""" - print("Setting BPMs timing") - super().prepare_timing(state) # BPM trigger timing + print("Configuring BPMs timing...") + _AcqBPMsSignals().prepare_timing(state) # BPM trigger timing - state = dict() if state is None else state - print("Setting magnets timing") + state = dict() if state is None else state # ? + print("Configuring magnets timing...") # magnets trigger timing below prms = self.params - trigpingh = self.devices["trigpingh"] pingers2kick = prms.pingers2kick + trigpingh = self.devices["trigpingh"] + trigpingv = self.devices["trigpingv"] + if ("h" in pingers2kick) or (state.get("trigpingh_state", 0) == 1): trigh_ok = trigpingh.cmd_enable(timeout=prms.timeout) trigpingh.source = state.get("trigpingh_source", prms.timing_event) @@ -156,9 +168,8 @@ def prepare_timing(self, state=None): trigpingh.delay = dly else: trigh_ok = trigpingh.cmd_disable(timeout=prms.timeout) - print(f"PingerH trigger set: {trigh_ok}") + print(f"PingerH trigger configured: {trigh_ok}") - trigpingv = self.devices["trigpingv"] if ("v" in pingers2kick) or (state.get("trigpingv_state", 0) == 1): trigv_ok = trigpingv.cmd_enable(timeout=prms.timeout) trigpingv.source = state.get("trigpingv_source", prms.timing_event) @@ -167,11 +178,11 @@ def prepare_timing(self, state=None): trigpingv.delay = dly else: trigv_ok = trigpingv.cmd_disable(timeout=prms.timeout) - print(f"PingerV trigger set: {trigv_ok}") + print(f"PingerV trigger configured: {trigv_ok}") + _time.sleep(0.1) + self._timing_ok = trigh_ok and trigv_ok - return trigh_ok and trigv_ok - - def get_magnets_strength(self): + def _get_magnets_strength(self): """.""" pingh_cal = self.params.pingh_calibration pingv_cal = self.params.pingv_calibration @@ -179,7 +190,7 @@ def get_magnets_strength(self): pingv_str = self.devices["pingv"].strength / pingv_cal return pingh_str, pingv_str - def get_magnets_state(self): + def _get_magnets_state(self): """.""" state = dict() pingh, pingv = self.devices["pingh"], self.devices["pingv"] @@ -195,7 +206,7 @@ def get_magnets_state(self): return state - def set_magnets_state(self, state, wait_mon=True): + def _set_magnets_state(self, state, wait_mon=True): """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] @@ -263,7 +274,7 @@ def set_magnets_state(self, state, wait_mon=True): return pingh_ok and pingv_ok - def set_magnets_strength( + def _set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None, wait_mon=True ): """Set pingers strengths, check if was set & indicate which failed.""" @@ -317,7 +328,7 @@ def set_magnets_strength( return True - def prepare_magnets(self): + def _prepare_magnets(self): """.""" print("Preparing magnets...") pingers2kick = self.params.pingers2kick @@ -341,56 +352,74 @@ def prepare_magnets(self): return self.set_magnets_state(state) - def do_measurement(self): + def _do_measurement(self): """.""" currinfo = self.devices["currinfo"] - init_timing_state = self.get_timing_state() - init_magnets_state = self.get_magnets_state() + init_timing_state = self._get_timing_state() + init_magnets_state = self._get_magnets_state() current_before = currinfo.current - timing_ok = self.prepare_timing() - if timing_ok: - print("Timing configs. were successfully set") - mags_ok = self.prepare_magnets() # gets strengths from params - if mags_ok: - print("Magnets ready.") - try: - self.acquire_data() - # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - self.data["current_before"] = current_before - self.data["current_after"] = self.data.pop( - "stored_current" - ) - self.data["init_magnets_state"] = init_magnets_state - print("Acquisition was successful.") - except Exception as e: - print(f"An error occurred during acquisition: {e}") + + self._prepare_timing() + if self._stopevt.is_set() or not self._timing_ok: + self._prepare_timing(init_timing_state) + print("Stopped. Timing restored to initial state.") + return + else: + print("Timing was succesfully configured.") + + self._prepare_magnets() # gets strengths from params + if self._stopevt.is_set() or not self._magnets_ok: + self._prepare_magnets(init_magnets_state) + print("Stopped. Magnets restored to initial state.") + return else: - print("Did not measure") + print("Magnets were succesfully configured.") + + try: + _AcqBPMsSignals.acquire_data() + # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + self.data["magnets_state"] = self._get_magnets_state() + self.data["current_before"] = current_before + self.data["current_after"] = self.data.pop( + "stored_current" + ) + self.data["init_magnets_state"] = init_magnets_state + print("Acquisition was successful.") + except Exception as e: + self._meas_finished_ok = False + self._prepare_timing(init_timing_state) + self._set_magnets_state( + init_magnets_state, wait_mon=False + ) + print(f"An error occurred during acquisition: {e}") + return + if self.params.restore_init_state: print("Restoring magnets & timing initial state.") - timing_ok = self.prepare_timing(init_timing_state) - if not timing_ok: - print("Timing was not restored to initial state.") - mags_ok = self.set_magnets_state( + self._prepare_timing(init_timing_state) + self._set_magnets_state( init_magnets_state, wait_mon=False ) - if not mags_ok: - msg = "Magnets state or strengths were not restored." - msg += "Restore manually." - print(msg) - print(init_magnets_state) - else: - print("Magnets state & strengths successfully restored.") + if not self._timing_ok: + print("Timing was not restored to initial state.") + if not self._magnets_ok: + print("Magnets were not restored to initial state.") + print("Measurement finished.") + return - def get_data(self): + def check_machine_restored(self): """.""" - data = super().get_data() - data["magnets_state"] = self.get_magnets_state() - data["magnets_strengths"] = self.get_magnets_strength() # [mrad] - return data + if self.ismeasuring: + print("Measurement not finished.") + return + print(f"Timing ok? {self._timing_ok}") + print(f"Magnets ok? {self._magnets_ok}") + print(f"Timing restored?") + print(f"Magnets restored?") + raise NotImplementedError def get_default_fname(self): """.""" @@ -415,6 +444,21 @@ def get_default_fname(self): stg += f"{tmstp}" return stg + def check_measurement_finished_ok(self): + """Check if measument finished without errors. + + Returns: + bool: True if measurement finished without errors. + + """ + if self.ismeasuring: + print("Measurment not finished.") + return False + return self._meas_finished_ok + + def check_measurement_quality(self): + """.""" + raise NotImplementedError class TbTDataAnalysis(MeasureTbTData): """.""" From cebd60068697b7850ee7e0956054e58c37aff728 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 15 Jan 2025 14:08:47 -0300 Subject: [PATCH 108/144] COMMISS.TBT:WIP: improve `_do_measurement` flow --- apsuite/commisslib/measure_tbt_data.py | 61 ++++++++++++++------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index a68d53f5..26ace19e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -152,9 +152,8 @@ def _prepare_timing(self, state=None): print("Configuring BPMs timing...") _AcqBPMsSignals().prepare_timing(state) # BPM trigger timing - state = dict() if state is None else state # ? + state = dict() if state is None else state print("Configuring magnets timing...") - # magnets trigger timing below prms = self.params pingers2kick = prms.pingers2kick trigpingh = self.devices["trigpingh"] @@ -360,20 +359,22 @@ def _do_measurement(self): current_before = currinfo.current self._prepare_timing() - if self._stopevt.is_set() or not self._timing_ok: - self._prepare_timing(init_timing_state) - print("Stopped. Timing restored to initial state.") - return - else: - print("Timing was succesfully configured.") + if not self._timing_ok: + print("Failed at configuring timing!") + self._restore_and_exit(timing_state=init_timing_state) + print("Timing was succesfully configured.") + + if self._stopevt.is_set(): + self._restore_and_exit(timing_state=init_timing_state) self._prepare_magnets() # gets strengths from params - if self._stopevt.is_set() or not self._magnets_ok: - self._prepare_magnets(init_magnets_state) - print("Stopped. Magnets restored to initial state.") - return - else: - print("Magnets were succesfully configured.") + if not self._magnets_ok: + print("Failed at configuring magnets!") + self._restore_and_exit(init_timing_state, init_magnets_state) + print("Magnets were succesfully configured.") + + if self._stopevt.is_set(): + self._restore_and_exit(init_timing_state, init_magnets_state) try: _AcqBPMsSignals.acquire_data() @@ -389,23 +390,27 @@ def _do_measurement(self): print("Acquisition was successful.") except Exception as e: self._meas_finished_ok = False - self._prepare_timing(init_timing_state) - self._set_magnets_state( - init_magnets_state, wait_mon=False - ) print(f"An error occurred during acquisition: {e}") - return + self._restore_and_exit(init_timing_state, init_magnets_state) if self.params.restore_init_state: - print("Restoring magnets & timing initial state.") - self._prepare_timing(init_timing_state) - self._set_magnets_state( - init_magnets_state, wait_mon=False - ) - if not self._timing_ok: - print("Timing was not restored to initial state.") - if not self._magnets_ok: - print("Magnets were not restored to initial state.") + self._restore_and_exit(init_timing_state, init_magnets_state) + + print("Measurement finished.") + + def _restore_and_exit(self, timing_state=None, magnets_state=None): + """Restore timing and magnets state and exit the measurement.""" + print("Restoring machine initial state.") + + if timing_state is not None: + self._prepare_timing(timing_state) + print(f" Timing restored: {self._timing_ok}") + + if magnets_state is not None: + self._set_magnets_state(magnets_state, wait_mon=False) + # no need to wait_mon if exiting anyways. wait_mon is critical to + # prevent meas to start before magnets reach the required strentgh + print(f" Magnets restored: {self._magnets_ok}") print("Measurement finished.") return From 5bc632ba69a49bc8d429353403d3a846366b721f Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 15 Jan 2025 14:56:57 -0300 Subject: [PATCH 109/144] COMMISS.TBT.WIP: improve `set_magnets_strength`'s bad pinger logic --- apsuite/commisslib/measure_tbt_data.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 26ace19e..1b083157 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -318,11 +318,16 @@ def _set_magnets_strength( wait_mon=wait_mon, ) - if (not pingh_ok) or (not pingv_ok): - bad_pingers = "pingh " if not pingh_ok else "" - bad_pingers += "pingv" if not pingv_ok else "" - msg = "Some magnets strengths were not set.\n" - msg += f"\t Bad pingers: {bad_pingers}" + bad_pingers = [] + if not pingh_ok: + bad_pingers.append("pingh") + if not pingv_ok: + bad_pingers.append("pingv") + + if bad_pingers: + msg = "Some magnet strengths were not set.\n" + msg += f"\tBad pingers: {', '.join(bad_pingers)}" + print(msg) return False return True From b75e0caa29b0e02a5e2d6060de65f616844bb187 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 15 Jan 2025 16:14:50 -0300 Subject: [PATCH 110/144] COMMISS.TBT.WIP: `_set_magnets_strengths` improvements --- apsuite/commisslib/measure_tbt_data.py | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 1b083157..f4dc2b01 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -278,27 +278,31 @@ def _set_magnets_strength( ): """Set pingers strengths, check if was set & indicate which failed.""" pingh, pingv = self.devices["pingh"], self.devices["pingv"] + pingh_ok, pingv_ok = True, True + + # first, strengths are only set. No waiting reaching the desired values + # (i.e. timeout = 0) if hkick is not None: hkick *= self.params.pingh_calibration - print("changing pingh") - pingh.set_strength(hkick, timeout=0, wait_mon=wait_mon) - else: - pingh_ok = True + print(f"Setting pingh strength to {hkick:.3f} mrad...") + pingh.set_strength(hkick, timeout=0) if vkick is not None: vkick *= self.params.pingv_calibration - print("changing pingv") - pingv.set_strength(vkick, timeout=0, wait_mon=wait_mon) - else: - pingv_ok = True + print(f"Setting pingv strength to {hkick:.3f} mrad...") + pingv.set_strength(vkick, timeout=0) + + if not wait_mon: + return pingh_ok and pingv_ok + + # once strenghts are set and wait_mon is True + # we wait the magnets reach the values set - # wait magnets ramp and check if correctly set - if magnets_timeout is None: - magnets_timeout = self.params.magnets_timeout + magnets_timeout = magnets_timeout or self.params.magnets_timeout t0 = _time.time() if hkick is not None: - print("waiting pingh") + print("\twaiting pingh") pingh_ok = pingh.set_strength( hkick, tol=0.05 * abs(hkick), @@ -310,7 +314,7 @@ def _set_magnets_strength( magnets_timeout -= elapsed_time if vkick is not None: - print("waiting pingv") + print("\twaiting pingv") pingv_ok = pingv.set_strength( vkick, tol=0.05 * abs(vkick), From d9c5804ecdf1ff1c34d824e9274c4220dbe6a763 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 15 Jan 2025 17:04:13 -0300 Subject: [PATCH 111/144] COMMISS.TBT.WIP: prevent changing strengths if pinger not used --- apsuite/commisslib/measure_tbt_data.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f4dc2b01..9f5f6871 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -209,13 +209,15 @@ def _set_magnets_state(self, state, wait_mon=True): """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] + pingh_ok, pingv_ok = True, True # turn-off pulse before changing strengths + # prevent accidental activation with wrong strengths pingh_ok = pingh.cmd_turn_off_pulse(timeout) pingv_ok = pingv.cmd_turn_off_pulse(timeout) - # Power and strengths - if state.get("pingh_pwr", False): + # Set power state and strengths + if state["pingh_pwr"]: pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) if pingh_ok: pingh_ok = self.set_magnets_strength( @@ -232,7 +234,7 @@ def _set_magnets_state(self, state, wait_mon=True): timeout=self.params.magnets_timeout ) - if state.get("pingv_pwr", False): + if state["pingv_pwr"]: pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) if pingv_ok: pingv_ok = self.set_magnets_strength( @@ -344,6 +346,7 @@ def _prepare_magnets(self): if "h" in pingers2kick: state["pingh_pwr"] = 1 # always make sure its on state["pingh_pulse"] = 1 + state["pingh_strength"] = self.params.hkick else: # state["pingh_pwr"] = 0 # but will not be turning-off. state["pingh_pulse"] = 0 # only changing pulse-sts @@ -351,13 +354,11 @@ def _prepare_magnets(self): if "v" in pingers2kick: state["pingv_pwr"] = 1 state["pingv_pulse"] = 1 + state["pingv_strength"] = self.params.vkick else: # state["pingv_pwr"] = 0 state["pingv_pulse"] = 0 - state["pingh_strength"] = self.params.hkick - state["pingv_strength"] = self.params.vkick - return self.set_magnets_state(state) def _do_measurement(self): From 481b56590209a416ff2ee7f000881ce35ed6040f Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 16 Jan 2025 13:56:43 -0300 Subject: [PATCH 112/144] COMMISS.TBT.MEAS.BUG: fixes & formatting imprvmnts. --- apsuite/commisslib/measure_tbt_data.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9f5f6871..37965b38 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -193,7 +193,7 @@ def _get_magnets_state(self): """.""" state = dict() pingh, pingv = self.devices["pingh"], self.devices["pingv"] - pingh_str, pingv_str = self.get_magnets_strength() + pingh_str, pingv_str = self._get_magnets_strength() state["pingh_pwr"] = pingh.pwrstate state["pingv_pwr"] = pingv.pwrstate state["pingh_pulse"] = pingh.pulse @@ -220,13 +220,13 @@ def _set_magnets_state(self, state, wait_mon=True): if state["pingh_pwr"]: pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) if pingh_ok: - pingh_ok = self.set_magnets_strength( + pingh_ok = self._set_magnets_strength( hkick=state["pingh_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingh.") else: - pingh_ok = self.set_magnets_strength( + pingh_ok = self._set_magnets_strength( hkick=state["pingh_strength"], wait_mon=wait_mon ) if pingh_ok: @@ -237,13 +237,13 @@ def _set_magnets_state(self, state, wait_mon=True): if state["pingv_pwr"]: pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) if pingv_ok: - pingv_ok = self.set_magnets_strength( + pingv_ok = self._set_magnets_strength( vkick=state["pingv_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingv.") else: - pingv_ok = self.set_magnets_strength( + pingv_ok = self._set_magnets_strength( vkick=state["pingv_strength"], wait_mon=wait_mon ) if pingv_ok: @@ -254,7 +254,7 @@ def _set_magnets_state(self, state, wait_mon=True): if pingh_ok and pingv_ok: print("Magnets power-state and strengths set.") else: - print("Failed at setting magnets power-state and strengths") + print("Failed at setting magnets power-state and/or strengths") print("Changing pulse-state") @@ -339,7 +339,7 @@ def _set_magnets_strength( return True def _prepare_magnets(self): - """.""" + """Create magnets state dict from params.""" print("Preparing magnets...") pingers2kick = self.params.pingers2kick state = dict() @@ -371,7 +371,9 @@ def _do_measurement(self): self._prepare_timing() if not self._timing_ok: print("Failed at configuring timing!") + self._meas_finished_ok = False self._restore_and_exit(timing_state=init_timing_state) + print("Timing was succesfully configured.") if self._stopevt.is_set(): @@ -380,7 +382,9 @@ def _do_measurement(self): self._prepare_magnets() # gets strengths from params if not self._magnets_ok: print("Failed at configuring magnets!") + self._meas_finished_ok = False self._restore_and_exit(init_timing_state, init_magnets_state) + print("Magnets were succesfully configured.") if self._stopevt.is_set(): @@ -414,13 +418,13 @@ def _restore_and_exit(self, timing_state=None, magnets_state=None): if timing_state is not None: self._prepare_timing(timing_state) - print(f" Timing restored: {self._timing_ok}") + print(f"\tTiming restored: {self._timing_ok}") if magnets_state is not None: self._set_magnets_state(magnets_state, wait_mon=False) # no need to wait_mon if exiting anyways. wait_mon is critical to # prevent meas to start before magnets reach the required strentgh - print(f" Magnets restored: {self._magnets_ok}") + print(f"\tMagnets restored: {self._magnets_ok}") print("Measurement finished.") return @@ -429,7 +433,7 @@ def check_machine_restored(self): """.""" if self.ismeasuring: print("Measurement not finished.") - return + return False print(f"Timing ok? {self._timing_ok}") print(f"Magnets ok? {self._magnets_ok}") print(f"Timing restored?") From 150db00d76f230361002f2961ee64f4411867059 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 Jan 2025 14:14:06 -0300 Subject: [PATCH 113/144] COMMISS.TBT.MEAS.ENH: init states acquired during initialization --- apsuite/commisslib/measure_tbt_data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 37965b38..3d55d567 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -118,6 +118,8 @@ def __init__(self, isonline=False): if self.isonline: self._create_devices() + self._init_magnets_state = self._get_magnets_state() + self._init_timing_state = self._get_timing_state() def _create_devices(self): """.""" @@ -364,8 +366,8 @@ def _prepare_magnets(self): def _do_measurement(self): """.""" currinfo = self.devices["currinfo"] - init_timing_state = self._get_timing_state() - init_magnets_state = self._get_magnets_state() + init_timing_state = self._init_timing_state + init_magnets_state = self._init_magnets_state current_before = currinfo.current self._prepare_timing() From 5af51c858f83fd4c51f90c7814658922139455e2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 Jan 2025 14:25:20 -0300 Subject: [PATCH 114/144] COMMISS.TBT.MEAS.DEV: check if machine was restored --- apsuite/commisslib/measure_tbt_data.py | 45 ++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 3d55d567..9ba3cd09 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -436,10 +436,31 @@ def check_machine_restored(self): if self.ismeasuring: print("Measurement not finished.") return False - print(f"Timing ok? {self._timing_ok}") - print(f"Magnets ok? {self._magnets_ok}") - print(f"Timing restored?") - print(f"Magnets restored?") + + magnets_state = self._get_magnets_state() + timing_state = self._get_timing_state() + mags = self._init_magnets_state == magnets_state + timing = self._init_timing_state == timing_state + + print(f"Magnets restored:\t{mags}") + print(f"Timing restored:\t{timing}") + + return mags and timing + + def check_measurement_finished_ok(self): + """Check if measument finished without errors. + + Returns: + bool: True if measurement finished without errors. + + """ + if self.ismeasuring: + print("Measurment not finished.") + return False + return self._meas_finished_ok + + def check_measurement_quality(self): + """.""" raise NotImplementedError def get_default_fname(self): @@ -465,22 +486,6 @@ def get_default_fname(self): stg += f"{tmstp}" return stg - def check_measurement_finished_ok(self): - """Check if measument finished without errors. - - Returns: - bool: True if measurement finished without errors. - - """ - if self.ismeasuring: - print("Measurment not finished.") - return False - return self._meas_finished_ok - - def check_measurement_quality(self): - """.""" - raise NotImplementedError - class TbTDataAnalysis(MeasureTbTData): """.""" From 3b447529c19556a9a7777974065f705a110dccde Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 Jan 2025 15:45:20 -0300 Subject: [PATCH 115/144] COMMISS.TBT.ENH: change pingers delay to delay_raw --- apsuite/commisslib/measure_tbt_data.py | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9ba3cd09..5c3922e5 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -38,8 +38,8 @@ def __init__(self): self.vkick = None # [mrad] self.pingh_calibration = 1.5416651659146232 self.pingv_calibration = 1.02267573 - self.trigpingh_delay = None - self.trigpingv_delay = None + self.trigpingh_delay_raw = 36802990 # defined @ 2024-05-21 mach. study + self.trigpingv_delay_raw = 36802937 # defined @ 2024-05-21 mach. study self.magnets_timeout = 120.0 self.restore_init_state = True @@ -64,28 +64,28 @@ def __str__(self): stg += ftmp("pingh_calibration", self.pingh_calibration, "") stg += ftmp("pingv_calibration", self.pingv_calibration, "") - dly = self.trigpingh_delay + dly = self.trigpingh_delay_raw if dly is None: stg += stmp( - "trigpingh_delay", + "trigpingh_delay_raw", "same", "(current value will not be changed)", ) else: - stg += ftmp("trigpingh_delay", dly, "[us]") + stg += dtmp("trigpingh_delay_raw", dly, "") if self.vkick is None: stg += stmp("vkick", "same", "(current value will not be changed)") else: stg += ftmp("vkick", self.vkick, "[mrad]") - dly = self.trigpingv_delay + dly = self.trigpingv_delay_raw if dly is None: stg += stmp( - "trigpingv_delay", + "trigpingv_delay_raw", "same", "(current value will not be changed)", ) else: - stg += ftmp("trigpingv_delay", dly, "[us]") + stg += dtmp("trigpingv_delay_raw", dly, "") stg += stmp("restore_init_state", self.restore_init_state, "") return stg @@ -140,12 +140,12 @@ def _get_timing_state(self): trigpingh = self.devices["trigpingh"] state["trigpingh_state"] = trigpingh.state state["trigpingh_source"] = trigpingh.source_str - state["trigpingh_delay"] = trigpingh.delay + state["trigpingh_delay_raw"] = trigpingh.delay_raw trigpingv = self.devices["trigpingv"] state["trigpingv_state"] = trigpingv.state state["trigpingv_source"] = trigpingv.source_str - state["trigpingv_delay"] = trigpingv.delay + state["trigpingv_delay_raw"] = trigpingv.delay_raw return state @@ -164,7 +164,7 @@ def _prepare_timing(self, state=None): if ("h" in pingers2kick) or (state.get("trigpingh_state", 0) == 1): trigh_ok = trigpingh.cmd_enable(timeout=prms.timeout) trigpingh.source = state.get("trigpingh_source", prms.timing_event) - dly = state.get("trigpingh_delay", prms.trigpingh_delay) + dly = state.get("trigpingh_delay_raw", prms.trigpingh_delay_raw) if dly is not None: trigpingh.delay = dly else: @@ -174,7 +174,7 @@ def _prepare_timing(self, state=None): if ("v" in pingers2kick) or (state.get("trigpingv_state", 0) == 1): trigv_ok = trigpingv.cmd_enable(timeout=prms.timeout) trigpingv.source = state.get("trigpingv_source", prms.timing_event) - dly = state.get("trigpingv_delay", prms.trigpingv_delay) + dly = state.get("trigpingv_delay_raw", prms.trigpingv_delay_raw) if dly is not None: trigpingv.delay = dly else: @@ -530,9 +530,10 @@ def __str__(self): ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format stmp = "{0:26s} = {1:9} {2:s}\n".format + dtmp = "{0:26s} = {1:9d} {2:s}\n".format gtmp = "{0:<15s} = {1:} {2:}\n".format - stg += gtmp("timestamp", self.timestamp, "") + stg += gtmp("timestamp", self.timestamp, "") # TODO: convert tmstp stg += "\n" stg += "Storage Ring State\n" stg += "\n" @@ -583,8 +584,10 @@ def __str__(self): data["timing_state"]["trigpingh_source"], "", ) - stg += stmp( - "trigpingh_delay", data["timing_state"]["trigpingh_delay"], "" + stg += dtmp( + "trigpingh_delay_raw", + data["timing_state"]["trigpingh_delay_raw"], + "" ) stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") @@ -602,8 +605,10 @@ def __str__(self): data["timing_state"]["trigpingv_source"], "", ) - stg += stmp( - "trigpingv_delay", data["timing_state"]["trigpingv_delay"], "" + stg += dtmp( + "trigpingv_delay_raw", + data["timing_state"]["trigpingv_delay_raw"], + "" ) stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") From 43a6057aba922c5266e93fd46d0915666b2e3871 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 Jan 2025 16:47:05 -0300 Subject: [PATCH 116/144] COMMISS.TBT.MEAS.ENH: avoid code repetition in `_get_timing_state` --- apsuite/commisslib/measure_tbt_data.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 5c3922e5..501c176e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -135,19 +135,19 @@ def _create_devices(self): def _get_timing_state(self): """.""" - state = _AcqBPMsSignals().get_timing_state() + # BPMs trigger and EVG timing state + state_dict = _AcqBPMsSignals().get_timing_state() - trigpingh = self.devices["trigpingh"] - state["trigpingh_state"] = trigpingh.state - state["trigpingh_source"] = trigpingh.source_str - state["trigpingh_delay_raw"] = trigpingh.delay_raw + # Pingers trigger timing state + trigs = self.devices["trigpingh"], self.devices["trigpingv"] + keys = "h", "v" - trigpingv = self.devices["trigpingv"] - state["trigpingv_state"] = trigpingv.state - state["trigpingv_source"] = trigpingv.source_str - state["trigpingv_delay_raw"] = trigpingv.delay_raw + for key, trig in zip(keys, trigs) : + state_dict[f"trigping{key}_state"] = trig.state # enable state + state_dict[f"trigping{key}_source"] = trig.source_str + state_dict[f"trigping{key}_delay_raw"] = trig.delay_raw - return state + return state_dict def _prepare_timing(self, state=None): """.""" @@ -486,6 +486,7 @@ def get_default_fname(self): stg += f"{tmstp}" return stg + class TbTDataAnalysis(MeasureTbTData): """.""" From 82ac612bd72b2a6a0e318b9ba56275c4a46ec965 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 17 Jan 2025 16:53:18 -0300 Subject: [PATCH 117/144] COMMISS.TBT.MEAS.ENH: avoid code repeteition in `_get_magnets_strength` --- apsuite/commisslib/measure_tbt_data.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 501c176e..ed729b68 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -194,17 +194,15 @@ def _get_magnets_strength(self): def _get_magnets_state(self): """.""" state = dict() - pingh, pingv = self.devices["pingh"], self.devices["pingv"] pingh_str, pingv_str = self._get_magnets_strength() - state["pingh_pwr"] = pingh.pwrstate - state["pingv_pwr"] = pingv.pwrstate - state["pingh_pulse"] = pingh.pulse - state["pingv_pulse"] = pingv.pulse - state["pingh_strength"] = pingh_str - state["pingv_strength"] = pingv_str - state["pingh_voltage"] = pingh.voltage - state["pingv_voltage"] = pingv.voltage - + pings = self.devices["pingh"], self.devices["pingv"] + keys = "h", "v" + for key, ping in zip(keys, pings): + ping_str = pingh_str if key == "h" else pingv_str + state[f"ping{key}_pwr"] = ping.pwrstate + state[f"ping{key}_pulse"] = ping.pulse + state[f"ping{key}_strength"] = ping_str + state[f"ping{key}_voltage"] = ping.voltage return state def _set_magnets_state(self, state, wait_mon=True): From 3f95865f88a28233e8682989ce767fe2be479ab2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 20 Jan 2025 13:15:39 -0300 Subject: [PATCH 118/144] COMMISS.TBT.MEAS.ENH: add `_acquire_data` method --- apsuite/commisslib/measure_tbt_data.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ed729b68..42143052 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -366,7 +366,7 @@ def _do_measurement(self): currinfo = self.devices["currinfo"] init_timing_state = self._init_timing_state init_magnets_state = self._init_magnets_state - current_before = currinfo.current + self.data["current_before"] = currinfo.current self._prepare_timing() if not self._timing_ok: @@ -391,16 +391,7 @@ def _do_measurement(self): self._restore_and_exit(init_timing_state, init_magnets_state) try: - _AcqBPMsSignals.acquire_data() - # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - self.data["magnets_state"] = self._get_magnets_state() - self.data["current_before"] = current_before - self.data["current_after"] = self.data.pop( - "stored_current" - ) - self.data["init_magnets_state"] = init_magnets_state + self._acquire_data() print("Acquisition was successful.") except Exception as e: self._meas_finished_ok = False @@ -412,6 +403,18 @@ def _do_measurement(self): print("Measurement finished.") + def _acquire_data(self): + """.""" + _AcqBPMsSignals.acquire_data() + # BPMs signals + relevant info are acquired + # such as timestamps tunes, stored current + # rf frequency, acq rate, nr samples, etc. + self.data["magnets_state"] = self._get_magnets_state() + self.data["current_after"] = self.data.pop( + "stored_current" + ) + self.data["init_magnets_state"] = self._init_magnets_state + def _restore_and_exit(self, timing_state=None, magnets_state=None): """Restore timing and magnets state and exit the measurement.""" print("Restoring machine initial state.") From fe2b6f5050d8ab5a925285aa5aacb6452d503afa Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 3 Apr 2025 09:08:02 -0300 Subject: [PATCH 119/144] COMMISS.TBTMEAS.BUG: fixes - correct initialization w/ 2 parent classes - get magnets strength only if pwrstate is on. None otherwise - turn public some methods --- apsuite/commisslib/measure_tbt_data.py | 86 +++++++++++++++----------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 42143052..6b23bc78 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -99,7 +99,7 @@ def pingers2kick(self, val): self._pingers2kick = str(val).lower() -class MeasureTbTData(_ThreadBaseClass): +class MeasureTbTData(_ThreadBaseClass, _AcqBPMsSignals): """.""" PINGERH_TRIGGER = "SI-01SA:TI-InjDpKckr" @@ -107,21 +107,24 @@ class MeasureTbTData(_ThreadBaseClass): def __init__(self, isonline=False): """.""" + _AcqBPMsSignals.__init__( + self, + isonline=isonline, + ispost_mortem=False + ) self.params = TbTDataParams() - super().__init__( - params=self.params, target=self._do_measurement, isonline=isonline - ) - self._is_postmortem = False + self.target = self._do_measurement + self._meas_finished_ok = True self._timing_ok = True self._magnets_ok = True if self.isonline: - self._create_devices() - self._init_magnets_state = self._get_magnets_state() - self._init_timing_state = self._get_timing_state() + self.create_devices() + self._init_magnets_state = self.get_magnets_state() + self._init_timing_state = self.get_timing_state() - def _create_devices(self): + def create_devices(self): """.""" _AcqBPMsSignals().create_devices() self.devices["pingh"] = PowerSupplyPU( @@ -133,10 +136,10 @@ def _create_devices(self): self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) return - def _get_timing_state(self): + def get_timing_state(self): """.""" # BPMs trigger and EVG timing state - state_dict = _AcqBPMsSignals().get_timing_state() + state_dict = _AcqBPMsSignals(self).get_timing_state() # Pingers trigger timing state trigs = self.devices["trigpingh"], self.devices["trigpingv"] @@ -149,10 +152,10 @@ def _get_timing_state(self): return state_dict - def _prepare_timing(self, state=None): + def prepare_timing(self, state=None): """.""" print("Configuring BPMs timing...") - _AcqBPMsSignals().prepare_timing(state) # BPM trigger timing + _AcqBPMsSignals(self).prepare_timing(state) # BPM trigger timing state = dict() if state is None else state print("Configuring magnets timing...") @@ -166,7 +169,7 @@ def _prepare_timing(self, state=None): trigpingh.source = state.get("trigpingh_source", prms.timing_event) dly = state.get("trigpingh_delay_raw", prms.trigpingh_delay_raw) if dly is not None: - trigpingh.delay = dly + trigpingh.delay_raw = dly else: trigh_ok = trigpingh.cmd_disable(timeout=prms.timeout) print(f"PingerH trigger configured: {trigh_ok}") @@ -176,27 +179,36 @@ def _prepare_timing(self, state=None): trigpingv.source = state.get("trigpingv_source", prms.timing_event) dly = state.get("trigpingv_delay_raw", prms.trigpingv_delay_raw) if dly is not None: - trigpingv.delay = dly + trigpingv.delay_raw = dly else: trigv_ok = trigpingv.cmd_disable(timeout=prms.timeout) print(f"PingerV trigger configured: {trigv_ok}") _time.sleep(0.1) self._timing_ok = trigh_ok and trigv_ok - def _get_magnets_strength(self): + def get_magnets_strength(self): """.""" pingh_cal = self.params.pingh_calibration pingv_cal = self.params.pingv_calibration - pingh_str = self.devices["pingh"].strength / pingh_cal - pingv_str = self.devices["pingv"].strength / pingv_cal + + if self.devices["pingh"].pwrstate: + pingh_str = self.devices["pingh"].strength / pingh_cal + else: + pingh_str = None + + if self.devices["pingv"].pwrstate: + pingv_str = self.devices["pingv"].strength / pingv_cal + else: + pingv_str = None + return pingh_str, pingv_str - def _get_magnets_state(self): + def get_magnets_state(self): """.""" state = dict() - pingh_str, pingv_str = self._get_magnets_strength() pings = self.devices["pingh"], self.devices["pingv"] keys = "h", "v" + pingh_str, pingv_str = self.get_magnets_strength() for key, ping in zip(keys, pings): ping_str = pingh_str if key == "h" else pingv_str state[f"ping{key}_pwr"] = ping.pwrstate @@ -205,7 +217,7 @@ def _get_magnets_state(self): state[f"ping{key}_voltage"] = ping.voltage return state - def _set_magnets_state(self, state, wait_mon=True): + def set_magnets_state(self, state, wait_mon=True): """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] @@ -220,13 +232,13 @@ def _set_magnets_state(self, state, wait_mon=True): if state["pingh_pwr"]: pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) if pingh_ok: - pingh_ok = self._set_magnets_strength( + pingh_ok = self.set_magnets_strength( hkick=state["pingh_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingh.") else: - pingh_ok = self._set_magnets_strength( + pingh_ok = self.set_magnets_strength( hkick=state["pingh_strength"], wait_mon=wait_mon ) if pingh_ok: @@ -237,13 +249,13 @@ def _set_magnets_state(self, state, wait_mon=True): if state["pingv_pwr"]: pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) if pingv_ok: - pingv_ok = self._set_magnets_strength( + pingv_ok = self.set_magnets_strength( vkick=state["pingv_strength"], wait_mon=wait_mon ) else: print("Failed at turning-on pingv.") else: - pingv_ok = self._set_magnets_strength( + pingv_ok = self.set_magnets_strength( vkick=state["pingv_strength"], wait_mon=wait_mon ) if pingv_ok: @@ -275,7 +287,7 @@ def _set_magnets_state(self, state, wait_mon=True): return pingh_ok and pingv_ok - def _set_magnets_strength( + def set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None, wait_mon=True ): """Set pingers strengths, check if was set & indicate which failed.""" @@ -338,7 +350,7 @@ def _set_magnets_strength( return True - def _prepare_magnets(self): + def prepare_magnets(self): """Create magnets state dict from params.""" print("Preparing magnets...") pingers2kick = self.params.pingers2kick @@ -368,7 +380,7 @@ def _do_measurement(self): init_magnets_state = self._init_magnets_state self.data["current_before"] = currinfo.current - self._prepare_timing() + self.prepare_timing() if not self._timing_ok: print("Failed at configuring timing!") self._meas_finished_ok = False @@ -379,7 +391,7 @@ def _do_measurement(self): if self._stopevt.is_set(): self._restore_and_exit(timing_state=init_timing_state) - self._prepare_magnets() # gets strengths from params + self.prepare_magnets() # gets strengths from params if not self._magnets_ok: print("Failed at configuring magnets!") self._meas_finished_ok = False @@ -391,7 +403,7 @@ def _do_measurement(self): self._restore_and_exit(init_timing_state, init_magnets_state) try: - self._acquire_data() + self.acquire_data() print("Acquisition was successful.") except Exception as e: self._meas_finished_ok = False @@ -403,13 +415,13 @@ def _do_measurement(self): print("Measurement finished.") - def _acquire_data(self): + def acquire_data(self): """.""" - _AcqBPMsSignals.acquire_data() + _AcqBPMsSignals.acquire_data(self) # BPMs signals + relevant info are acquired # such as timestamps tunes, stored current # rf frequency, acq rate, nr samples, etc. - self.data["magnets_state"] = self._get_magnets_state() + self.data["magnets_state"] = self.get_magnets_state() self.data["current_after"] = self.data.pop( "stored_current" ) @@ -420,11 +432,11 @@ def _restore_and_exit(self, timing_state=None, magnets_state=None): print("Restoring machine initial state.") if timing_state is not None: - self._prepare_timing(timing_state) + self.prepare_timing(timing_state) print(f"\tTiming restored: {self._timing_ok}") if magnets_state is not None: - self._set_magnets_state(magnets_state, wait_mon=False) + self.set_magnets_state(magnets_state, wait_mon=False) # no need to wait_mon if exiting anyways. wait_mon is critical to # prevent meas to start before magnets reach the required strentgh print(f"\tMagnets restored: {self._magnets_ok}") @@ -438,8 +450,8 @@ def check_machine_restored(self): print("Measurement not finished.") return False - magnets_state = self._get_magnets_state() - timing_state = self._get_timing_state() + magnets_state = self.get_magnets_state() + timing_state = self.get_timing_state() mags = self._init_magnets_state == magnets_state timing = self._init_timing_state == timing_state From 01899a59f7b201439c4dbf762ace83110b6eec89 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 3 Apr 2025 10:27:54 -0300 Subject: [PATCH 120/144] COMMISS.TBT.MEAS.ENH: simplify setting strengths in `set_magnets_satate` --- apsuite/commisslib/measure_tbt_data.py | 52 ++++++++------------------ 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 6b23bc78..c6f62172 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -131,7 +131,6 @@ def create_devices(self): PowerSupplyPU.DEVICES.SI_INJ_DPKCKR ) self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) - self.devices["pingv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) return @@ -223,45 +222,25 @@ def set_magnets_state(self, state, wait_mon=True): pingh, pingv = self.devices["pingh"], self.devices["pingv"] pingh_ok, pingv_ok = True, True + # make sure mags are on to set their strengths + pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) + pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) # turn-off pulse before changing strengths - # prevent accidental activation with wrong strengths + # prevent accidental activation pingh_ok = pingh.cmd_turn_off_pulse(timeout) pingv_ok = pingv.cmd_turn_off_pulse(timeout) - # Set power state and strengths - if state["pingh_pwr"]: - pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) - if pingh_ok: - pingh_ok = self.set_magnets_strength( - hkick=state["pingh_strength"], wait_mon=wait_mon - ) - else: - print("Failed at turning-on pingh.") - else: - pingh_ok = self.set_magnets_strength( - hkick=state["pingh_strength"], wait_mon=wait_mon - ) - if pingh_ok: - pingh_ok = pingh.cmd_turn_off( - timeout=self.params.magnets_timeout - ) + self.set_magnets_strength( + hkick=state["pingh_strength"], + vkick=state["pingv_strength"], + wait_mon=wait_mon + ) - if state["pingv_pwr"]: - pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) - if pingv_ok: - pingv_ok = self.set_magnets_strength( - vkick=state["pingv_strength"], wait_mon=wait_mon - ) - else: - print("Failed at turning-on pingv.") - else: - pingv_ok = self.set_magnets_strength( - vkick=state["pingv_strength"], wait_mon=wait_mon - ) - if pingv_ok: - pingv_ok = pingv.cmd_turn_off( - timeout=self.params.magnets_timeout - ) + if not state["pingh_pwr"]: + pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) + + if not state["pingv_pwr"]: + pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) if pingh_ok and pingv_ok: print("Magnets power-state and strengths set.") @@ -269,8 +248,6 @@ def set_magnets_state(self, state, wait_mon=True): print("Failed at setting magnets power-state and/or strengths") print("Changing pulse-state") - - # Pulse state if state["pingh_pulse"]: pingh_ok = pingh_ok and pingh.cmd_turn_on_pulse(timeout) else: @@ -348,6 +325,7 @@ def set_magnets_strength( print(msg) return False + print("Strengths all set.") return True def prepare_magnets(self): From 4bf74159e499ae986449b00651fb6bb1a6d9f218 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 3 Apr 2025 11:04:40 -0300 Subject: [PATCH 121/144] COMMISS.TBT.MEAS.ENH: fixes in `set_magnets_strength` - no wait time during setting of strengths - introduce `mags_strength_rtol` param --- apsuite/commisslib/measure_tbt_data.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index c6f62172..0ac0c174 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -33,6 +33,7 @@ def __init__(self): self.nrpoints_before = 100 self.nrpoints_after = 2000 + self.mags_strength_rtol = 0.05 self._pingers2kick = "none" # 'H', 'V' or 'HV' self.hkick = None # [mrad] self.vkick = None # [mrad] @@ -57,12 +58,12 @@ def __str__(self): stg += "\n" stg += stmp("pingers2kick", self.pingers2kick, "") stg += ftmp("magnets_timeout", self.magnets_timeout, "[s]") + stg += ftmp("mags_strength_rtol", self.mags_strength_rtol, "") if self.hkick is None: stg += stmp("hkick", "same", "(current value will not be changed)") else: stg += ftmp("hkick", self.hkick, "[mrad]") stg += ftmp("pingh_calibration", self.pingh_calibration, "") - stg += ftmp("pingv_calibration", self.pingv_calibration, "") dly = self.trigpingh_delay_raw if dly is None: @@ -77,6 +78,7 @@ def __str__(self): stg += stmp("vkick", "same", "(current value will not be changed)") else: stg += ftmp("vkick", self.vkick, "[mrad]") + stg += ftmp("pingv_calibration", self.pingv_calibration, "") dly = self.trigpingv_delay_raw if dly is None: stg += stmp( @@ -276,12 +278,12 @@ def set_magnets_strength( if hkick is not None: hkick *= self.params.pingh_calibration print(f"Setting pingh strength to {hkick:.3f} mrad...") - pingh.set_strength(hkick, timeout=0) + pingh.strength = hkick if vkick is not None: vkick *= self.params.pingv_calibration print(f"Setting pingv strength to {hkick:.3f} mrad...") - pingv.set_strength(vkick, timeout=0) + pingv.strength = vkick if not wait_mon: return pingh_ok and pingv_ok @@ -293,10 +295,10 @@ def set_magnets_strength( t0 = _time.time() if hkick is not None: - print("\twaiting pingh") + print("\twaiting pingh reach strength set") pingh_ok = pingh.set_strength( hkick, - tol=0.05 * abs(hkick), + tol=self.params.mags_strength_rtol * abs(hkick), timeout=magnets_timeout, wait_mon=wait_mon, ) @@ -305,10 +307,10 @@ def set_magnets_strength( magnets_timeout -= elapsed_time if vkick is not None: - print("\twaiting pingv") + print("\twaiting pingv reach strength set") pingv_ok = pingv.set_strength( vkick, - tol=0.05 * abs(vkick), + tol=self.params.mags_strength_rtol * abs(vkick), timeout=magnets_timeout, wait_mon=wait_mon, ) From e462e72ae8d993b296b33dd4539da06dc9d81b54 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 3 Apr 2025 13:34:12 -0300 Subject: [PATCH 122/144] COMMISS.TBT.MEAS.BUG: use the `_magnets_ok` flag when changing mags --- apsuite/commisslib/measure_tbt_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 0ac0c174..7b8783ff 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -264,7 +264,7 @@ def set_magnets_state(self, state, wait_mon=True): msg = "pingv pulse set" if pingv_ok else "pingv pulse not set" print("\t" + msg) - return pingh_ok and pingv_ok + self._magnets_ok = pingh_ok and pingv_ok def set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None, wait_mon=True @@ -351,7 +351,7 @@ def prepare_magnets(self): # state["pingv_pwr"] = 0 state["pingv_pulse"] = 0 - return self.set_magnets_state(state) + self.set_magnets_state(state) def _do_measurement(self): """.""" From 0f87a97ff8bf72d8f8d02dacc1bc1989b6769f23 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 3 Apr 2025 13:55:59 -0300 Subject: [PATCH 123/144] COMMISS.TBT.MEAS.ENH: move data saving to `acquire_data` & `get_data` only --- apsuite/commisslib/measure_tbt_data.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 7b8783ff..f3c6af27 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -355,10 +355,8 @@ def prepare_magnets(self): def _do_measurement(self): """.""" - currinfo = self.devices["currinfo"] init_timing_state = self._init_timing_state init_magnets_state = self._init_magnets_state - self.data["current_before"] = currinfo.current self.prepare_timing() if not self._timing_ok: @@ -397,15 +395,18 @@ def _do_measurement(self): def acquire_data(self): """.""" + curr0 = self.devices["currinfo"].current _AcqBPMsSignals.acquire_data(self) - # BPMs signals + relevant info are acquired - # such as timestamps tunes, stored current - # rf frequency, acq rate, nr samples, etc. - self.data["magnets_state"] = self.get_magnets_state() - self.data["current_after"] = self.data.pop( - "stored_current" - ) - self.data["init_magnets_state"] = self._init_magnets_state + self.data["current_before"] = curr0 + + def get_data(self): + """.""" + data = _AcqBPMsSignals(self).get_data() + data["magnets_state"] = self.get_magnets_state() + data["current_after"] = data.pop("stored_current") + data["init_magnets_state"] = self._init_magnets_state + data["init_timing_state"] = self._init_timing_state + return data def _restore_and_exit(self, timing_state=None, magnets_state=None): """Restore timing and magnets state and exit the measurement.""" From 688f4722d21b175bc8760e78dc8ff93e6a72188a Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 7 Apr 2025 16:09:23 -0300 Subject: [PATCH 124/144] COMMISS.TBT.MEAS.ENH: automatically handle `pingers2kick` - changes `pingers2kick` according to the kick values set --- apsuite/commisslib/measure_tbt_data.py | 37 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index f3c6af27..100fecea 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -34,9 +34,9 @@ def __init__(self): self.nrpoints_after = 2000 self.mags_strength_rtol = 0.05 - self._pingers2kick = "none" # 'H', 'V' or 'HV' - self.hkick = None # [mrad] - self.vkick = None # [mrad] + self.pingers2kick = "" # 'H', 'V' or 'HV' + self._hkick = None # [mrad] + self._vkick = None # [mrad] self.pingh_calibration = 1.5416651659146232 self.pingv_calibration = 1.02267573 self.trigpingh_delay_raw = 36802990 # defined @ 2024-05-21 mach. study @@ -92,13 +92,32 @@ def __str__(self): return stg @property - def pingers2kick(self): - """.""" - return self._pingers2kick + def hkick(self): + """Horizontal kick.""" + return self._hkick + + @hkick.setter + def hkick(self, kick): + self._hkick = kick + if kick != 0: + if "h" not in self.pingers2kick: + self.pingers2kick += "h" + else: + self.pingers2kick = self.pingers2kick.replace("h", "") - @pingers2kick.setter - def pingers2kick(self, val): - self._pingers2kick = str(val).lower() + @property + def vkick(self): + """Vertical kick.""" + return self._vkick + + @vkick.setter + def vkick(self, kick): + self._vkick = kick + if kick != 0: + if "v" not in self.pingers2kick: + self.pingers2kick += "v" + else: + self.pingers2kick = self.pingers2kick.replace("v", "") class MeasureTbTData(_ThreadBaseClass, _AcqBPMsSignals): From bf3e2915a1e721fd6169c7f9480641c860c529e6 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 8 Apr 2025 15:00:59 -0300 Subject: [PATCH 125/144] COMMISS.TbT: Fix some bugs. --- apsuite/commisslib/meas_bpms_signals.py | 5 +- apsuite/commisslib/measure_tbt_data.py | 235 ++++++++++++------------ apsuite/utils.py | 2 +- 3 files changed, 123 insertions(+), 119 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 88b59888..d0767125 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -88,9 +88,10 @@ class AcqBPMsSignals(_BaseClass): BPM_TRIGGER = "SI-Fam:TI-BPM" PSM_TRIGGER = "SI-Fam:TI-BPM-PsMtm" - def __init__(self, isonline=True, ispost_mortem=False): + def __init__(self, params=None, isonline=True, ispost_mortem=False): """.""" - super().__init__(params=AcqBPMsSignalsParams(), isonline=isonline) + params = params or AcqBPMsSignalsParams() + super().__init__(params=params, isonline=isonline) self._ispost_mortem = ispost_mortem if self.isonline: diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 100fecea..db992376 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -37,8 +37,8 @@ def __init__(self): self.pingers2kick = "" # 'H', 'V' or 'HV' self._hkick = None # [mrad] self._vkick = None # [mrad] - self.pingh_calibration = 1.5416651659146232 - self.pingv_calibration = 1.02267573 + # self.pingh_calibration = 1.5416651659146232 + # self.pingv_calibration = 1.02267573 self.trigpingh_delay_raw = 36802990 # defined @ 2024-05-21 mach. study self.trigpingv_delay_raw = 36802937 # defined @ 2024-05-21 mach. study self.magnets_timeout = 120.0 @@ -128,38 +128,41 @@ class MeasureTbTData(_ThreadBaseClass, _AcqBPMsSignals): def __init__(self, isonline=False): """.""" + _ThreadBaseClass.__init__( + self, + params=TbTDataParams(), + target=self._do_measurement, + isonline=isonline + ) _AcqBPMsSignals.__init__( self, + params=self.params, isonline=isonline, ispost_mortem=False ) - self.params = TbTDataParams() - self.target = self._do_measurement self._meas_finished_ok = True self._timing_ok = True self._magnets_ok = True if self.isonline: - self.create_devices() self._init_magnets_state = self.get_magnets_state() self._init_timing_state = self.get_timing_state() def create_devices(self): """.""" - _AcqBPMsSignals().create_devices() + _AcqBPMsSignals.create_devices(self) self.devices["pingh"] = PowerSupplyPU( PowerSupplyPU.DEVICES.SI_INJ_DPKCKR ) self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) self.devices["pingv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) - return def get_timing_state(self): """.""" # BPMs trigger and EVG timing state - state_dict = _AcqBPMsSignals(self).get_timing_state() + state_dict = _AcqBPMsSignals.get_timing_state(self) # Pingers trigger timing state trigs = self.devices["trigpingh"], self.devices["trigpingv"] @@ -175,7 +178,7 @@ def get_timing_state(self): def prepare_timing(self, state=None): """.""" print("Configuring BPMs timing...") - _AcqBPMsSignals(self).prepare_timing(state) # BPM trigger timing + _AcqBPMsSignals.prepare_timing(self, state) # BPM trigger timing state = dict() if state is None else state print("Configuring magnets timing...") @@ -203,7 +206,7 @@ def prepare_timing(self, state=None): else: trigv_ok = trigpingv.cmd_disable(timeout=prms.timeout) print(f"PingerV trigger configured: {trigv_ok}") - _time.sleep(0.1) + self._timing_ok = trigh_ok and trigv_ok def get_magnets_strength(self): @@ -244,12 +247,12 @@ def set_magnets_state(self, state, wait_mon=True): pingh_ok, pingv_ok = True, True # make sure mags are on to set their strengths - pingh_ok = pingh.cmd_turn_on(timeout=self.params.magnets_timeout) - pingv_ok = pingv.cmd_turn_on(timeout=self.params.magnets_timeout) + pingh_ok &= pingh.cmd_turn_on(timeout=self.params.magnets_timeout) + pingv_ok &= pingv.cmd_turn_on(timeout=self.params.magnets_timeout) # turn-off pulse before changing strengths # prevent accidental activation - pingh_ok = pingh.cmd_turn_off_pulse(timeout) - pingv_ok = pingv.cmd_turn_off_pulse(timeout) + pingh_ok &= pingh.cmd_turn_off_pulse(timeout) + pingv_ok &= pingv.cmd_turn_off_pulse(timeout) self.set_magnets_strength( hkick=state["pingh_strength"], @@ -258,10 +261,10 @@ def set_magnets_state(self, state, wait_mon=True): ) if not state["pingh_pwr"]: - pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) + pingh_ok &= pingh.cmd_turn_off(timeout=self.params.magnets_timeout) if not state["pingv_pwr"]: - pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) + pingv_ok &= pingv.cmd_turn_off(timeout=self.params.magnets_timeout) if pingh_ok and pingv_ok: print("Magnets power-state and strengths set.") @@ -420,7 +423,7 @@ def acquire_data(self): def get_data(self): """.""" - data = _AcqBPMsSignals(self).get_data() + data = _AcqBPMsSignals.get_data(self) data["magnets_state"] = self.get_magnets_state() data["current_after"] = data.pop("stored_current") data["init_magnets_state"] = self._init_magnets_state @@ -442,7 +445,6 @@ def _restore_and_exit(self, timing_state=None, magnets_state=None): print(f"\tMagnets restored: {self._magnets_ok}") print("Measurement finished.") - return def check_machine_restored(self): """.""" @@ -509,7 +511,7 @@ def __init__(self, filename="", isonline=False): """Analysis of linear optics using Turn-by-turn data.""" super().__init__(isonline=isonline) - self._fname = filename + self._fname = None self.timestamp = None self.trajx, self.trajy = None, None # zero-mean trajectories in [mm] self.trajsum = None @@ -531,105 +533,107 @@ def __init__(self, filename="", isonline=False): self.pca_data = None self.ica_data = None - if self._fname: - self.load_and_apply(self._fname) + if filename: + self.load_and_apply(filename) def __str__(self): """.""" stg = "" data = self.data - if data: - stg += "\n" - stg += "Measurement data:\n" - - ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format - stmp = "{0:26s} = {1:9} {2:s}\n".format - dtmp = "{0:26s} = {1:9d} {2:s}\n".format - gtmp = "{0:<15s} = {1:} {2:}\n".format - - stg += gtmp("timestamp", self.timestamp, "") # TODO: convert tmstp - stg += "\n" - stg += "Storage Ring State\n" - stg += "\n" - - stg += ftmp("current_before", data["current_before"], "mA") - stg += ftmp("current_after", data["current_after"], "mA") - stg += ftmp("tunex", data["tunex"], "") - stg += ftmp("tuney", data["tuney"], "") - stg += stmp("tunex_enable", bool(data["tunex_enable"]), "") - stg += stmp("tuney_enable", bool(data["tuney_enable"]), "") - - stg += "\n" - stg += "EVT state\n" - - stg += stmp("evt_mode", data["timing_state"]["evt_mode"], "") - stg += stmp("evt_delay", data["timing_state"]["evt_delay"], "") - - stg += "\n" - stg += "BPMs state\n" - stg += "\n" - - stg += stmp("acq_rate", data["acq_rate"], "") - stg += stmp("nrsamples_pre", data["nrsamples_pre"], "") - stg += stmp("nrsamples_post", data["nrsamples_post"], "") - stg += stmp("switching_mode", data["switching_mode"], "") - stg += stmp("switching_frequency", data["switching_frequency"], "") - stg += stmp( - "trigbpm_source", data["timing_state"]["trigbpm_source"], "" - ) - stg += stmp( - "trigbpm_nrpulses", - data["timing_state"]["trigbpm_nrpulses"], - "", - ) - stg += stmp( - "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" - ) + if not data: + return stg - stg += "\n" - stg += "Pingers state\n" - stg += "\n" + stg += "\n" + stg += "Measurement data:\n" - stg += stmp( - "trigpingh_state", data["timing_state"]["trigpingh_state"], "" - ) - stg += stmp( - "trigpingh_source", - data["timing_state"]["trigpingh_source"], - "", - ) - stg += dtmp( - "trigpingh_delay_raw", - data["timing_state"]["trigpingh_delay_raw"], - "" - ) - stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") + ftmp = "{0:26s} = {1:9.6f} {2:s}\n".format + stmp = "{0:26s} = {1:9} {2:s}\n".format + dtmp = "{0:26s} = {1:9d} {2:s}\n".format + gtmp = "{0:<15s} = {1:} {2:}\n".format - stg += stmp( - "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" - ) - stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") - stg += "\n" + stg += gtmp("timestamp", self.timestamp, "") # TODO: convert tmstp + stg += "\n" + stg += "Storage Ring State\n" + stg += "\n" - stg += stmp( - "trigpingv_state", data["timing_state"]["trigpingv_state"], "" - ) - stg += stmp( - "trigpingv_source", - data["timing_state"]["trigpingv_source"], - "", - ) - stg += dtmp( - "trigpingv_delay_raw", - data["timing_state"]["trigpingv_delay_raw"], - "" - ) - stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") + stg += ftmp("current_before", data["current_before"], "mA") + stg += ftmp("current_after", data["current_after"], "mA") + stg += ftmp("tunex", data["tunex"], "") + stg += ftmp("tuney", data["tuney"], "") + stg += stmp("tunex_enable", bool(data["tunex_enable"]), "") + stg += stmp("tuney_enable", bool(data["tuney_enable"]), "") - stg += stmp( - "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" - ) - stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") + stg += "\n" + stg += "EVT state\n" + + stg += stmp("evt_mode", data["timing_state"]["evt_mode"], "") + stg += stmp("evt_delay", data["timing_state"]["evt_delay"], "") + + stg += "\n" + stg += "BPMs state\n" + stg += "\n" + + stg += stmp("acq_rate", data["acq_rate"], "") + stg += stmp("nrsamples_pre", data["nrsamples_pre"], "") + stg += stmp("nrsamples_post", data["nrsamples_post"], "") + stg += stmp("switching_mode", data["switching_mode"], "") + stg += stmp("switching_frequency", data["switching_frequency"], "") + stg += stmp( + "trigbpm_source", data["timing_state"]["trigbpm_source"], "" + ) + stg += stmp( + "trigbpm_nrpulses", + data["timing_state"]["trigbpm_nrpulses"], + "", + ) + stg += stmp( + "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" + ) + + stg += "\n" + stg += "Pingers state\n" + stg += "\n" + + stg += stmp( + "trigpingh_state", data["timing_state"]["trigpingh_state"], "" + ) + stg += stmp( + "trigpingh_source", + data["timing_state"]["trigpingh_source"], + "", + ) + stg += dtmp( + "trigpingh_delay_raw", + data["timing_state"]["trigpingh_delay_raw"], + "" + ) + stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") + + stg += stmp( + "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" + ) + stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") + stg += "\n" + + stg += stmp( + "trigpingv_state", data["timing_state"]["trigpingv_state"], "" + ) + stg += stmp( + "trigpingv_source", + data["timing_state"]["trigpingv_source"], + "", + ) + stg += dtmp( + "trigpingv_delay_raw", + data["timing_state"]["trigpingv_delay_raw"], + "" + ) + stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") + + stg += stmp( + "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" + ) + stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") return stg @@ -638,12 +642,6 @@ def fname(self): """.""" return self._fname - @fname.setter - def fname(self, val): - """.""" - self._fname = val - self.load_and_apply(val) - @property def tunex(self): """.""" @@ -672,7 +670,13 @@ def tuney(self, val): def load_and_apply(self, fname): """Load data and copy often used data to class attributes.""" - keys = super().load_and_apply(fname) + try: + keys = super().load_and_apply(fname) + except Exception: + print('Problem loading file {fname}.') + return + self._fname = fname + if keys: print("The following keys were not used:") print(" ", str(keys)) @@ -703,7 +707,6 @@ def load_and_apply(self, fname): self.rf_freq = data.get("rf_frequency", None) self.sampling_freq = data.get("sampling_frequency", None) self.switching_freq = data.get("switching_frequency", None) - return def linear_optics_analysis(self): """Linear optics (beta-beating & phase adv. errors) analysis. diff --git a/apsuite/utils.py b/apsuite/utils.py index 897db1ce..5cbf4d9d 100644 --- a/apsuite/utils.py +++ b/apsuite/utils.py @@ -159,7 +159,7 @@ class ThreadedMeasBaseClass(MeasBaseClass): def __init__(self, params=None, target=None, isonline=True): """.""" - super().__init__(params=params, isonline=isonline) + MeasBaseClass.__init__(self, params=params, isonline=isonline) self._target = target self._stopevt = _Event() self._finished = _Event() From ca704b7c17491803f37c1782fc719786be0ef69e Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 9 Apr 2025 13:30:37 -0300 Subject: [PATCH 126/144] COMMISS.TbT.MEAS.ENH: some improvements - `pingers2kick` set by user manually, undoing the second-to-last commit; - default kicks to 0; - stop using pingers calibrations during meas - pingers calibration factors now class constants in analysis class - avoid initialization of `SOFBFactory` during params & meas classes instantiation --- apsuite/commisslib/measure_tbt_data.py | 81 ++++++++++---------------- 1 file changed, 30 insertions(+), 51 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index db992376..9adead7a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -7,10 +7,11 @@ import matplotlib.pyplot as _mplt import numpy as _np import pyaccel as _pa -from mathphys.sobi import SOBI as _SOBI +# from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys from pymodels import si as _si from scipy.optimize import curve_fit as _curve_fit -from siriuspy.devices import PowerSupplyPU, Trigger +from siriuspy.devices import PowerSupplyPU as _PowerSupplyPU, \ + Trigger as _Trigger from siriuspy.sofb.csdev import SOFBFactory from sklearn.decomposition import FastICA as _FastICA @@ -23,8 +24,6 @@ class TbTDataParams(_AcqBPMsSignalsParams): """.""" - BPMS_NAMES = SOFBFactory.create("SI").bpm_names - def __init__(self): """.""" super().__init__() @@ -32,17 +31,15 @@ def __init__(self): self.acq_rate = "TbT" self.nrpoints_before = 100 self.nrpoints_after = 2000 + self.restore_init_state = True self.mags_strength_rtol = 0.05 - self.pingers2kick = "" # 'H', 'V' or 'HV' - self._hkick = None # [mrad] - self._vkick = None # [mrad] - # self.pingh_calibration = 1.5416651659146232 - # self.pingv_calibration = 1.02267573 + self._pingers2kick = "" # 'h', 'v' or 'hv' + self.hkick = 0 # [mrad] + self.vkick = 0 # [mrad] self.trigpingh_delay_raw = 36802990 # defined @ 2024-05-21 mach. study self.trigpingv_delay_raw = 36802937 # defined @ 2024-05-21 mach. study self.magnets_timeout = 120.0 - self.restore_init_state = True def __str__(self): """.""" @@ -63,7 +60,6 @@ def __str__(self): stg += stmp("hkick", "same", "(current value will not be changed)") else: stg += ftmp("hkick", self.hkick, "[mrad]") - stg += ftmp("pingh_calibration", self.pingh_calibration, "") dly = self.trigpingh_delay_raw if dly is None: @@ -78,7 +74,6 @@ def __str__(self): stg += stmp("vkick", "same", "(current value will not be changed)") else: stg += ftmp("vkick", self.vkick, "[mrad]") - stg += ftmp("pingv_calibration", self.pingv_calibration, "") dly = self.trigpingv_delay_raw if dly is None: stg += stmp( @@ -92,32 +87,14 @@ def __str__(self): return stg @property - def hkick(self): - """Horizontal kick.""" - return self._hkick - - @hkick.setter - def hkick(self, kick): - self._hkick = kick - if kick != 0: - if "h" not in self.pingers2kick: - self.pingers2kick += "h" - else: - self.pingers2kick = self.pingers2kick.replace("h", "") + def pingers2kick(self): + """.""" + return self._pingers2kick - @property - def vkick(self): - """Vertical kick.""" - return self._vkick - - @vkick.setter - def vkick(self, kick): - self._vkick = kick - if kick != 0: - if "v" not in self.pingers2kick: - self.pingers2kick += "v" - else: - self.pingers2kick = self.pingers2kick.replace("v", "") + @pingers2kick.setter + def pingers2kick(self, val): + """Must be 'h', 'v' or 'vh'/'hv'.""" + self._pingers2kick = str(val).lower() class MeasureTbTData(_ThreadBaseClass, _AcqBPMsSignals): @@ -152,12 +129,14 @@ def __init__(self, isonline=False): def create_devices(self): """.""" _AcqBPMsSignals.create_devices(self) - self.devices["pingh"] = PowerSupplyPU( - PowerSupplyPU.DEVICES.SI_INJ_DPKCKR + self.devices["pingh"] = _PowerSupplyPU( + _PowerSupplyPU.DEVICES.SI_INJ_DPKCKR + ) + self.devices["trigpingh"] = _Trigger(self.PINGERH_TRIGGER) + self.devices["pingv"] = _PowerSupplyPU( + _PowerSupplyPU.DEVICES.SI_PING_V ) - self.devices["trigpingh"] = Trigger(self.PINGERH_TRIGGER) - self.devices["pingv"] = PowerSupplyPU(PowerSupplyPU.DEVICES.SI_PING_V) - self.devices["trigpingv"] = Trigger(self.PINGERV_TRIGGER) + self.devices["trigpingv"] = _Trigger(self.PINGERV_TRIGGER) def get_timing_state(self): """.""" @@ -211,16 +190,13 @@ def prepare_timing(self, state=None): def get_magnets_strength(self): """.""" - pingh_cal = self.params.pingh_calibration - pingv_cal = self.params.pingv_calibration - if self.devices["pingh"].pwrstate: - pingh_str = self.devices["pingh"].strength / pingh_cal + pingh_str = self.devices["pingh"].strength else: pingh_str = None if self.devices["pingv"].pwrstate: - pingv_str = self.devices["pingv"].strength / pingv_cal + pingv_str = self.devices["pingv"].strength else: pingv_str = None @@ -298,12 +274,10 @@ def set_magnets_strength( # first, strengths are only set. No waiting reaching the desired values # (i.e. timeout = 0) if hkick is not None: - hkick *= self.params.pingh_calibration print(f"Setting pingh strength to {hkick:.3f} mrad...") pingh.strength = hkick if vkick is not None: - vkick *= self.params.pingv_calibration print(f"Setting pingv strength to {hkick:.3f} mrad...") pingv.strength = vkick @@ -504,7 +478,8 @@ def get_default_fname(self): class TbTDataAnalysis(MeasureTbTData): """.""" - + PINGH_CALIBRATION = 1.5416651659146232 # pingers calibration factors + PINGV_CALIBRATION = 1.02267573 # should be in the mags exc. curve SYNCH_TUNE = 0.004713 # check this def __init__(self, filename="", isonline=False): @@ -532,6 +507,7 @@ def __init__(self, filename="", isonline=False): self.fitting_data = None self.pca_data = None self.ica_data = None + self._bpms_names = None if filename: self.load_and_apply(filename) @@ -1343,8 +1319,11 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): trajy = self.trajy[:, bpm_index] trajsum = self.trajsum[:, bpm_index] + if self._bpms_names is None: + self._bpms_names = SOFBFactory.create("SI").bpm_names + fig, ax = _mplt.subplots(1, 3, figsize=(15, 5)) - name = self.params.BPMS_NAMES[bpm_index] + name = self._bpms_names[bpm_index] fig.suptitle( f"{self.acq_rate.upper()} acq. at BPM {bpm_index:03d} ({name})" ) From d92a2b0c3cd36a1c22717d8d3e258c11bdfa72d2 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 9 Apr 2025 16:51:46 -0300 Subject: [PATCH 127/144] COMMISS.TBT.ENH: improve meas flow checkings -`set_magnets_state` now exits soon after failing - flags for the meas flow control are returned by `prepare_timing` and `prepare_magnets`, instead of manipulating a class var - stopping the meas externally counts as `meas_finished_ok = False` --- apsuite/commisslib/measure_tbt_data.py | 115 ++++++++++++++----------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 9adead7a..d55dd435 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -119,8 +119,6 @@ def __init__(self, isonline=False): ) self._meas_finished_ok = True - self._timing_ok = True - self._magnets_ok = True if self.isonline: self._init_magnets_state = self.get_magnets_state() @@ -174,7 +172,7 @@ def prepare_timing(self, state=None): trigpingh.delay_raw = dly else: trigh_ok = trigpingh.cmd_disable(timeout=prms.timeout) - print(f"PingerH trigger configured: {trigh_ok}") + print(f"\tpingh trigger configured: {trigh_ok}") if ("v" in pingers2kick) or (state.get("trigpingv_state", 0) == 1): trigv_ok = trigpingv.cmd_enable(timeout=prms.timeout) @@ -184,9 +182,9 @@ def prepare_timing(self, state=None): trigpingv.delay_raw = dly else: trigv_ok = trigpingv.cmd_disable(timeout=prms.timeout) - print(f"PingerV trigger configured: {trigv_ok}") + print(f"\tpingv trigger configured: {trigv_ok}") - self._timing_ok = trigh_ok and trigv_ok + return trigh_ok and trigv_ok def get_magnets_strength(self): """.""" @@ -220,49 +218,65 @@ def set_magnets_state(self, state, wait_mon=True): """Set magnets strengths, pwr and pulse states.""" timeout = self.params.magnets_timeout pingh, pingv = self.devices["pingh"], self.devices["pingv"] - pingh_ok, pingv_ok = True, True + print("Setting magnets states...") # make sure mags are on to set their strengths - pingh_ok &= pingh.cmd_turn_on(timeout=self.params.magnets_timeout) - pingv_ok &= pingv.cmd_turn_on(timeout=self.params.magnets_timeout) + pingh_ok = pingh.cmd_turn_on(timeout=timeout) + pingv_ok = pingv.cmd_turn_on(timeout=timeout) + + if not pingh_ok or not pingv_ok: + print("\tpingers could not be turned-on for setting strengths.") + print(f"\t\tpingh-on: {pingh_ok}; pingv_on: {pingv_ok}") + return False + # turn-off pulse before changing strengths # prevent accidental activation - pingh_ok &= pingh.cmd_turn_off_pulse(timeout) - pingv_ok &= pingv.cmd_turn_off_pulse(timeout) + pingh_ok = pingh.cmd_turn_off_pulse(timeout=timeout) + pingv_ok = pingv.cmd_turn_off_pulse(timeout=timeout) - self.set_magnets_strength( + if not pingh_ok or not pingv_ok: + msg = "\tpingers pulse-status could not be turned-off " + msg += "for securely setting the strengths." + print(msg) + print(f"\t\tpingh pulse: {pingh_ok}; pingv pulse: {pingv_ok}") + return False + + pingh_ok, pingv_ok = self.set_magnets_strength( hkick=state["pingh_strength"], vkick=state["pingv_strength"], wait_mon=wait_mon ) + if not pingh_ok or not pingv_ok: + print("\tpingers strenghts could not be set.") + return False + if not state["pingh_pwr"]: - pingh_ok &= pingh.cmd_turn_off(timeout=self.params.magnets_timeout) + print() + pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) if not state["pingv_pwr"]: - pingv_ok &= pingv.cmd_turn_off(timeout=self.params.magnets_timeout) + pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) - if pingh_ok and pingv_ok: - print("Magnets power-state and strengths set.") - else: - print("Failed at setting magnets power-state and/or strengths") + if not pingh_ok or not pingv_ok: + print("\tfailed at setting magnets power-state") + return False - print("Changing pulse-state") if state["pingh_pulse"]: - pingh_ok = pingh_ok and pingh.cmd_turn_on_pulse(timeout) + pingh_ok = pingh.cmd_turn_on_pulse(timeout) else: - pingh_ok = pingh_ok and pingh.cmd_turn_off_pulse(timeout) - msg = "pingh pulse set" if pingh_ok else "pingh pulse not set" - print("\t" + msg) + pingh_ok = pingh.cmd_turn_off_pulse(timeout) + if not pingh_ok: + print("\tpingh pulse-status could not be set") if state["pingv_pulse"]: - pingv_ok = pingv_ok and pingv.cmd_turn_on_pulse(timeout) + pingv_ok = pingv.cmd_turn_on_pulse(timeout) else: - pingv_ok = pingv_ok and pingv.cmd_turn_off_pulse(timeout) - msg = "pingv pulse set" if pingv_ok else "pingv pulse not set" - print("\t" + msg) + pingv_ok = pingv.cmd_turn_off_pulse(timeout) + if not pingv_ok: + print("\tpingv pulse-status could not be set") - self._magnets_ok = pingh_ok and pingv_ok + return pingh_ok and pingv_ok def set_magnets_strength( self, hkick=None, vkick=None, magnets_timeout=None, wait_mon=True @@ -274,15 +288,15 @@ def set_magnets_strength( # first, strengths are only set. No waiting reaching the desired values # (i.e. timeout = 0) if hkick is not None: - print(f"Setting pingh strength to {hkick:.3f} mrad...") + print(f"\tSetting pingh strength to {hkick:.3f} mrad...") pingh.strength = hkick if vkick is not None: - print(f"Setting pingv strength to {hkick:.3f} mrad...") + print(f"\tSetting pingv strength to {hkick:.3f} mrad...") pingv.strength = vkick if not wait_mon: - return pingh_ok and pingv_ok + return pingh_ok, pingv_ok # once strenghts are set and wait_mon is True # we wait the magnets reach the values set @@ -291,7 +305,7 @@ def set_magnets_strength( t0 = _time.time() if hkick is not None: - print("\twaiting pingh reach strength set") + print("\t\twaiting pingh reach strength set") pingh_ok = pingh.set_strength( hkick, tol=self.params.mags_strength_rtol * abs(hkick), @@ -303,7 +317,7 @@ def set_magnets_strength( magnets_timeout -= elapsed_time if vkick is not None: - print("\twaiting pingv reach strength set") + print("\t\twaiting pingv reach strength set") pingv_ok = pingv.set_strength( vkick, tol=self.params.mags_strength_rtol * abs(vkick), @@ -318,13 +332,12 @@ def set_magnets_strength( bad_pingers.append("pingv") if bad_pingers: - msg = "Some magnet strengths were not set.\n" - msg += f"\tBad pingers: {', '.join(bad_pingers)}" + msg = "\tSome magnet strengths were not set.\n" + msg += f"\t\tBad pingers: {', '.join(bad_pingers)}" print(msg) - return False - print("Strengths all set.") - return True + print("\tStrengths all set.") + return pingh_ok, pingv_ok def prepare_magnets(self): """Create magnets state dict from params.""" @@ -347,33 +360,37 @@ def prepare_magnets(self): # state["pingv_pwr"] = 0 state["pingv_pulse"] = 0 - self.set_magnets_state(state) + return self.set_magnets_state(state) def _do_measurement(self): """.""" init_timing_state = self._init_timing_state init_magnets_state = self._init_magnets_state - self.prepare_timing() - if not self._timing_ok: - print("Failed at configuring timing!") + timing_ok = self.prepare_timing() + if not timing_ok: + print("Failed at configuring timing! Exiting.") self._meas_finished_ok = False self._restore_and_exit(timing_state=init_timing_state) print("Timing was succesfully configured.") if self._stopevt.is_set(): + print("Measurement stopped.") + self._meas_finished_ok = False self._restore_and_exit(timing_state=init_timing_state) - self.prepare_magnets() # gets strengths from params - if not self._magnets_ok: - print("Failed at configuring magnets!") + mags_ok = self.prepare_magnets() # gets strengths from params + if not mags_ok: + print("Failed at configuring magnets! Exiting.") self._meas_finished_ok = False self._restore_and_exit(init_timing_state, init_magnets_state) print("Magnets were succesfully configured.") if self._stopevt.is_set(): + print("Measurement stopped.") + self._meas_finished_ok = False self._restore_and_exit(init_timing_state, init_magnets_state) try: @@ -409,14 +426,14 @@ def _restore_and_exit(self, timing_state=None, magnets_state=None): print("Restoring machine initial state.") if timing_state is not None: - self.prepare_timing(timing_state) - print(f"\tTiming restored: {self._timing_ok}") + timing_ok = self.prepare_timing(timing_state) + print(f"\tTiming restored: {timing_ok}") if magnets_state is not None: - self.set_magnets_state(magnets_state, wait_mon=False) + mags_ok = self.set_magnets_state(magnets_state, wait_mon=False) # no need to wait_mon if exiting anyways. wait_mon is critical to - # prevent meas to start before magnets reach the required strentgh - print(f"\tMagnets restored: {self._magnets_ok}") + # prevent meas starting before magnets reach the required strentgh + print(f"\tMagnets restored: {mags_ok}") print("Measurement finished.") From d16ed58f9b413d3e714321024c378adb381972cc Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 28 Apr 2025 12:12:14 -0300 Subject: [PATCH 128/144] COMMISS.ACQBPMS.ENH: restore support for automatic EVG pulses - from past discussions we agreed automatic pulsing should not be allowed and the user should always tirgger events manually - however, without this functionality, the code cannot be automated in a loop this commit undoes changes from commit 021c505e1e459465d7531fbc5c1823220d4121ad while preserving changes from 9c408633f1791f272b6eb01c9cd6aaf81ff7e150 --- apsuite/commisslib/meas_bpms_signals.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index d0767125..6956eb77 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -20,6 +20,7 @@ def __init__(self): """.""" self.trigbpm_delay = None self.trigbpm_nrpulses = 1 + self.do_pulse_evg = True self._timing_event = "Study" self.event_delay = None self.event_mode = "External" @@ -44,6 +45,7 @@ def __str__(self): else: stg += ftmp("trigbpm_delay", dly, "[us]") stg += dtmp("trigbpm_nrpulses", self.trigbpm_nrpulses, "") + stg += stmp("do_pulse_evg", str(self.do_pulse_evg), "") stg += stmp("timing_event", self.timing_event, "") dly = self.event_delay if dly is None: @@ -148,6 +150,8 @@ def get_timing_state(self): state["trigbpm_source"] = trigbpm.source_str state["trigbpm_nrpulses"] = trigbpm.nr_pulses state["trigbpm_delay"] = trigbpm.delay + if self.params.do_pulse_evg: + state["evg_nrpulses"] = self.devices["evg"].nrpulses evt = self._get_event(self.params.timing_event) if evt is not None: @@ -182,6 +186,24 @@ def prepare_timing(self, state=None): evt.mode = state.get("evt_mode", self.params.event_mode) self.devices["evg"].cmd_update_events() + nrpul = 1 if self.params.do_pulse_evg else None + nrpul = state.get("evg_nrpulses", nrpul) + if nrpul is not None: + evg = self.devices["evg"] + evg.set_nrpulses(nrpul) + evg.cmd_update_events() # need to be called 2x? + + def trigger_timing_signal(self): + """.""" + if not self.params.do_pulse_evg: + print("Waiting for trigger event to be fired.") + return + evt = self._get_event(self.params.timing_event) + if evt is not None and evt.mode_str == "External": + evt.cmd_external_trigger() + else: + self.devices["evg"].cmd_turn_on_injection() + def prepare_bpms_acquisition(self): """.""" fambpms = self.devices["fambpms"] @@ -206,8 +228,7 @@ def acquire_data(self): fambpms.reset_mturn_initial_state() - # NOTE: user must trigger timing event - print("Ready for acquisition. Waiting for trigger event.") + self.trigger_timing_signal() time0 = _time.time() ret = fambpms.wait_update_mturn(timeout=self.params.timeout) From 5c2cfc392933831e4e5c67c6e62ed00651558a6c Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 28 Apr 2025 12:50:16 -0300 Subject: [PATCH 129/144] COMMISS.TBT.MEAS.ENH: change default parameter initialization (nr points, do_pulse_evg) --- apsuite/commisslib/measure_tbt_data.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d55dd435..351b9d80 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -29,9 +29,10 @@ def __init__(self): super().__init__() self.signals2acq = "XYS" self.acq_rate = "TbT" - self.nrpoints_before = 100 - self.nrpoints_after = 2000 + self.nrpoints_before = 50 + self.nrpoints_after = 750 self.restore_init_state = True + self.do_pulse_evg = True self.mags_strength_rtol = 0.05 self._pingers2kick = "" # 'h', 'v' or 'hv' From 69dabc0d2f14eb1619088f55b17e1e951a185878 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 30 Apr 2025 09:48:36 -0300 Subject: [PATCH 130/144] COMMISS.ANLY.TMP: comment unused param for old data compatibility --- apsuite/commisslib/measure_tbt_data.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index d55dd435..896cd95e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -595,11 +595,11 @@ def __str__(self): data["timing_state"]["trigpingh_source"], "", ) - stg += dtmp( - "trigpingh_delay_raw", - data["timing_state"]["trigpingh_delay_raw"], - "" - ) + # stg += dtmp( + # "trigpingh_delay_raw", + # data["timing_state"]["trigpingh_delay_raw"], + # "" + # ) stg += stmp("pingh_pwr", data["magnets_state"]["pingh_pwr"], "") stg += stmp( @@ -616,11 +616,11 @@ def __str__(self): data["timing_state"]["trigpingv_source"], "", ) - stg += dtmp( - "trigpingv_delay_raw", - data["timing_state"]["trigpingv_delay_raw"], - "" - ) + # stg += dtmp( + # "trigpingv_delay_raw", + # data["timing_state"]["trigpingv_delay_raw"], + # "" + # ) stg += stmp("pingv_pwr", data["magnets_state"]["pingv_pwr"], "") stg += stmp( From 7ce968a86de448da3d6427a9fcfb591016722367 Mon Sep 17 00:00:00 2001 From: Matheus Velloso Date: Mon, 5 May 2025 14:07:04 -0300 Subject: [PATCH 131/144] COMMISS.TBT.BUG: fixes during online tests - `get_magnets_strengths` return strengths even if pingers are off - pingers are turned-on on `set_magnets_state` as called by `prepare_magnets` only if indicated in `pingers2kick` - bugfixes related to absent dict keys in `state` dict of `set_magnets_state` - fix access to the magnets strengths in the str method of analysis class --- apsuite/commisslib/measure_tbt_data.py | 39 +++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index a5a9bea0..35607acb 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -189,15 +189,8 @@ def prepare_timing(self, state=None): def get_magnets_strength(self): """.""" - if self.devices["pingh"].pwrstate: - pingh_str = self.devices["pingh"].strength - else: - pingh_str = None - - if self.devices["pingv"].pwrstate: - pingv_str = self.devices["pingv"].strength - else: - pingv_str = None + pingh_str = self.devices["pingh"].strength + pingv_str = self.devices["pingv"].strength return pingh_str, pingv_str @@ -221,9 +214,12 @@ def set_magnets_state(self, state, wait_mon=True): pingh, pingv = self.devices["pingh"], self.devices["pingv"] print("Setting magnets states...") + pingh_ok, pingv_ok = True, True # make sure mags are on to set their strengths - pingh_ok = pingh.cmd_turn_on(timeout=timeout) - pingv_ok = pingv.cmd_turn_on(timeout=timeout) + if "h" in self.params.pingers2kick: + pingh_ok = pingh.cmd_turn_on(timeout=timeout) + if "v" in self.params.pingers2kick: + pingv_ok = pingv.cmd_turn_on(timeout=timeout) if not pingh_ok or not pingv_ok: print("\tpingers could not be turned-on for setting strengths.") @@ -243,8 +239,8 @@ def set_magnets_state(self, state, wait_mon=True): return False pingh_ok, pingv_ok = self.set_magnets_strength( - hkick=state["pingh_strength"], - vkick=state["pingv_strength"], + hkick=state.get("pingh_strength", None), + vkick=state.get("pingv_strength", None), wait_mon=wait_mon ) @@ -252,25 +248,24 @@ def set_magnets_state(self, state, wait_mon=True): print("\tpingers strenghts could not be set.") return False - if not state["pingh_pwr"]: - print() + if not state.get("pingh_pwr", True): pingh_ok = pingh.cmd_turn_off(timeout=self.params.magnets_timeout) - if not state["pingv_pwr"]: + if not state.get("pingv_pwr", True): pingv_ok = pingv.cmd_turn_off(timeout=self.params.magnets_timeout) if not pingh_ok or not pingv_ok: print("\tfailed at setting magnets power-state") return False - if state["pingh_pulse"]: + if state.get("pingh_pulse", False): pingh_ok = pingh.cmd_turn_on_pulse(timeout) else: pingh_ok = pingh.cmd_turn_off_pulse(timeout) if not pingh_ok: print("\tpingh pulse-status could not be set") - if state["pingv_pulse"]: + if state.get("pingv_pulse", False): pingv_ok = pingv.cmd_turn_on_pulse(timeout) else: pingv_ok = pingv.cmd_turn_off_pulse(timeout) @@ -293,7 +288,7 @@ def set_magnets_strength( pingh.strength = hkick if vkick is not None: - print(f"\tSetting pingv strength to {hkick:.3f} mrad...") + print(f"\tSetting pingv strength to {vkick:.3f} mrad...") pingv.strength = vkick if not wait_mon: @@ -606,7 +601,7 @@ def __str__(self): stg += stmp( "pingh_pulse", data["magnets_state"]["pingh_pulse"], "" ) - stg += ftmp("hkick", data["magnets_strengths"][0], "mrad") + stg += ftmp("hkick", data["magnets_state"]["pingh_strength"], "mrad") stg += "\n" stg += stmp( @@ -627,7 +622,7 @@ def __str__(self): stg += stmp( "pingv_pulse", data["magnets_state"]["pingv_pulse"], "" ) - stg += ftmp("vkick", data["magnets_strengths"][1], "mrad") + stg += ftmp("vkick", data["magnets_state"]["pingv_strength"], "mrad") return stg @@ -1778,4 +1773,4 @@ def _get_nominal_optics(self, tunes=None, chroms=None): model_optics["phasex"] = twiss.mux[bpms_idcs] model_optics["betay"] = twiss.betay[bpms_idcs] model_optics["phasey"] = twiss.muy[bpms_idcs] - self.model_optics = model_optics + self.model_optics = model_optics \ No newline at end of file From 2a072b027d864363bb2e97319708cc54e115cc5e Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 7 May 2025 16:11:34 -0300 Subject: [PATCH 132/144] COMMISS.TBT.ANLY.BUG: avoid operations with None object if orbx, orby are missing. --- apsuite/commisslib/measure_tbt_data.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 35607acb..20d94949 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -683,10 +683,16 @@ def load_and_apply(self, fname): trajy = data.get("orby", None).copy() * 1e-3 trajsum = data.get("sumdata", None).copy() + if trajsum is not None: + self.trajsum = trajsum + # zero mean in samples dimension - trajx -= trajx.mean(axis=0)[None, :] - trajy -= trajy.mean(axis=0)[None, :] - self.trajx, self.trajy, self.trajsum = trajx, trajy, trajsum + if trajx is not None: + trajx -= trajx.mean(axis=0)[None, :] + self.trajx = trajx + if trajy is not None: + trajy -= trajy.mean(axis=0)[None, :] + self.trajy = trajy self.nrsamples_pre = data.get("nrsamples_pre", None) self.nrsamples_post = data.get("nrsamples_post", None) @@ -1773,4 +1779,4 @@ def _get_nominal_optics(self, tunes=None, chroms=None): model_optics["phasex"] = twiss.mux[bpms_idcs] model_optics["betay"] = twiss.betay[bpms_idcs] model_optics["phasey"] = twiss.muy[bpms_idcs] - self.model_optics = model_optics \ No newline at end of file + self.model_optics = model_optics From c488994b2b6421211ec9e1ad66ba72b310287212 Mon Sep 17 00:00:00 2001 From: Matheus Date: Wed, 7 May 2025 16:28:47 -0300 Subject: [PATCH 133/144] COMMISS.ANLY.ENH: change default timescale in `plot_trajs`& minor sty changes --- apsuite/commisslib/measure_tbt_data.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 20d94949..e06d9c12 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1289,7 +1289,7 @@ def plot_trajs_spectrum( return fig, axs - def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): + def plot_trajs(self, bpm_index=0, timescale=2, compare_fit=False): """Plot trajectories and sum-signal at a given BPM and timescale. Args: @@ -1301,7 +1301,7 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): modulation timescale = 2 : ~ 2000 turns; transverse decoherence modulations - Defaults to 0. + Defaults to 2. compare_fit (bool, optional): whether to plot acquisitions and the fitting and the fit residue. Defaults to False. @@ -1319,20 +1319,17 @@ def plot_trajs(self, bpm_index=0, timescale=0, compare_fit=False): if not timescale: nmax_x, nmax_y = int(1 / self.tunex), int(1 / self.tuney) - slicex = (nr_pre, nr_pre + nmax_x + 1) - slicey = (nr_pre, nr_pre + nmax_y + 1) - slicesum = (nr_pre, nr_pre + max(nmax_x, nmax_y) + 1) + begin = max(nr_pre - 5, 0) + slicex = (begin, nr_pre + nmax_x + 1) + slicey = (begin, nr_pre + nmax_y + 1) + slicesum = (begin, nr_pre + max(nmax_x, nmax_y) + 1) nmax = int(1 / self.SYNCH_TUNE) if timescale == 1: - slicex = (nr_pre, nr_pre + nmax + 1) - slicey = slicex - slicesum = slicex + slicex = slicey = slicesum = (0, nr_pre + nmax + 1) elif timescale == 2: - slicex = (nr_pre + nmax, nr_pre + nr_post + 1) - slicey = slicex - slicesum = slicex + slicex = slicey = slicesum = (0, nr_pre + nr_post + 1) trajx = self.trajx[:, bpm_index] trajy = self.trajy[:, bpm_index] From 934ca0877261c6b523e60b1a01b0b16439363d2d Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 8 May 2025 15:01:57 -0300 Subject: [PATCH 134/144] COMMISS.TBTANLY.ENH: refactor PCA analysis - PCA is applied both planes always - `plot_modal_analysis` reuses code from PCA function - stetic improvements to `plot_modal_analysis` - now highlighting singular values of each plane, fixes hor. & ver. signals spatio-temp. -modes - new structure for `linear_optics_analysis`: choose a method and proceed - this way, PCA function is more modular. It solely calculates PCA analysis stuff. - still missing propagate this to ICA analysis and harmonic analysis --- apsuite/commisslib/measure_tbt_data.py | 395 ++++++++++++------------- 1 file changed, 191 insertions(+), 204 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index e06d9c12..cf68f693 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -703,7 +703,9 @@ def load_and_apply(self, fname): self.sampling_freq = data.get("sampling_frequency", None) self.switching_freq = data.get("switching_frequency", None) - def linear_optics_analysis(self): + def linear_optics_analysis( + self, method="PCA", compare_meas2model=True + ): # TODO: Missing kwargs for each method """Linear optics (beta-beating & phase adv. errors) analysis. Determines beta-functions and phase-advances on BPMs via sinusoidal @@ -711,13 +713,46 @@ def linear_optics_analysis(self): spatio-temporal modal analysis using Principal Components Analysis (PCA) and Independent Components Analysis (ICA). """ - self.harmonic_analysis() - self.principal_components_analysis() - self.independent_components_analysis() + if method.lower() == "fitting": + self.harmonic_analysis() + data = self.fitting_data + elif method.lower() == "pca": + self.principal_components_analysis(calc_optics=True) + data = self.pca_data + elif method.lower() == "ica": + self.independent_components_analysis() + data = self.ica_data + + betax = data["betax"] + betay = data["betay"] + phasex = data["phasex"] + phasey = data["phasey"] - def harmonic_analysis( - self, guess_tunes=True, plot=True, compare_meas2model=True - ): + betax_model = self.model_optics["betax"] + betay_model = self.model_optics["betay"] + phasex_model = self.model_optics["phasex"] + phasey_model = self.model_optics["phasey"] + + self.plot_betabeat_and_phase_error( + betax_model, + betax, + phasex_model, + phasex, + title=f"{method} Optics Analysis: betax & phasex", + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use, + ) + self.plot_betabeat_and_phase_error( + betay_model, + betay, + phasey_model, + phasey, + title=f"{method} Optics Analysis: betay & phasey", + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use, + ) + + def harmonic_analysis(self, guess_tunes=True): r"""Linear optics analysis using sinusoidal model for TbT data. TbT motion at the i-th turn and j-th BPM in the timescale of less @@ -814,7 +849,6 @@ def harmonic_analysis( if self.model_optics is None: self._get_nominal_optics(tunes=(tunex + 49.0, tuney + 14.0)) beta_model = self.model_optics["beta" + label] - phases_model = self.model_optics["phase" + label] # fit beta-function & action from fitted amplitudes & nominal beta beta_fit, action = self.calc_beta_and_action(amps, beta_model) @@ -837,23 +871,9 @@ def harmonic_analysis( fitting_data["traj" + label + "_final_fit"] = final_fit fitting_data["fitting" + label + "_residue"] = residue - # Plot results (beta_beating and phase adv. error) - if plot: - title = f"Sinusoidal fit analysis - beta{label} & phase{label}" - self.plot_betabeat_and_phase_error( - beta_model, - beta_fit, - phases_model, - phases_fit, - title=title, - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) self.fitting_data = fitting_data - def principal_components_analysis( - self, stackxy=True, planes="xy", plot=True, compare_meas2model=True - ): + def principal_components_analysis(self, calc_optics=True): r"""Peform linear optics analysis Principal Components Analysis (PCA). Calculates beta-functions and betatron phase-advance at the BPMs using @@ -917,167 +937,94 @@ def principal_components_analysis( [3] Scikit-learn examples. "Blind Source Separation using FastICA". https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py """ - tunes = self.tunex + 49.0, self.tuney + 14.0 - if self.model_optics is None: - self._get_nominal_optics(tunes) - - # get model optics - betax_model = self.model_optics["betax"] - betay_model = self.model_optics["betay"] - phasex_model = self.model_optics["phasex"] - phasey_model = self.model_optics["phasey"] - pca_data = dict() - if stackxy: - traj = _np.concatenate((self.trajx, self.trajy), axis=1) - # perform PCA via SVD of history matrix - umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) - - # collect source signals and mixing matrix - signals = umat * _np.sqrt(traj.shape[0] - 1) # data signals - mixing_matrix = vtmat.T @ _np.diag(svals) - mixing_matrix /= _np.sqrt(traj.shape[0] - 1) - - # calulate tunes of the source signals - spec1, tunes1 = self.calc_spectrum(signals[:, 0], axis=0) - spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) - # modes 0 & 1 and modes 2 & 3 have have equal spectra - # therefore, it suffices to analyze mode 0 and mode 2 only - - tune1 = tunes1[_np.argmax(_np.abs(spec1))] - tune2 = tunes2[_np.argmax(_np.abs(spec2))] - - # identify which signal is which (x or y) - xidcs, yidcs = self.identify_modes( - tune1, tune2, self.tunex, self.tuney - ) - # extract betatron sine & cosine modes fom mixing matrix - sin_modex = mixing_matrix[:160, xidcs[-1]] - cos_modex = mixing_matrix[:160, xidcs[0]] - sin_modey = mixing_matrix[160:, yidcs[-1]] - cos_modey = mixing_matrix[160:, yidcs[0]] + traj = _np.concatenate((self.trajx, self.trajy), axis=1) + # perform PCA via SVD of history matrix + umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) - # calculate beta function & phase from betatron modes - betax, phasex = self.get_beta_and_phase_from_betatron_modes( - sin_modex, cos_modex, betax_model - ) - betay, phasey = self.get_beta_and_phase_from_betatron_modes( - sin_modey, cos_modey, betay_model - ) + # collect source signals and mixing matrix + signals = umat * _np.sqrt(traj.shape[0] - 1) # data signals + mixing_matrix = vtmat.T @ _np.diag(svals) + mixing_matrix /= _np.sqrt(traj.shape[0] - 1) - # concatenate results - beta = _np.concatenate((betax, betay)) - phase = _np.concatenate((phasex, phasey)) - - # calculate signal variance - signalx = _np.sqrt(_np.sum(svals[xidcs[0] : xidcs[1] + 1] ** 2)) - signaly = _np.sqrt(_np.sum(svals[yidcs[0] : yidcs[1] + 1] ** 2)) - # and signal noise - noise = _np.sqrt(_np.sum(svals[3:] ** 2)) - snrx, snry = signalx / noise, signaly / noise - - # calculate error bars as in Appendix A of - # Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance - # and ${\beta}$ function measurements using model-independent - # analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. - # DOI 10.1103/PhysRevSTAB.6.104001 - - nrsamples = self.nrsamples_pre + self.nrsamples_post - phasex_error = 1 / snrx / _np.sqrt(nrsamples) - phasex_error *= _np.sqrt(betax_model.mean() / 2 / betax_model) - betax_error = 2 * betax_model * phasex_error - - phasey_error = 1 / snry / _np.sqrt(nrsamples) - phasey_error *= _np.sqrt(betay_model.mean() / 2 / betay_model) - betay_error = 2 * betay_model * phasey_error - - # TODO: plot stacked data - # plot_results - if plot: - self.plot_betabeat_and_phase_error( - _np.concatenate((betax_model, betay_model)), - beta, - _np.concatenate((phasex_model, phasey_model)), - phase, - title="PCA Analysis: beta & phase (stacked x/y)", - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) - - # save analysis data - pca_data["singular_values"] = svals - pca_data["source_signals"] = signals - pca_data["mixing_matrix"] = mixing_matrix - pca_data["xidcs"] = xidcs - pca_data["yidcs"] = yidcs - pca_data["beta"] = beta - pca_data["phase"] = phase - pca_data["snrx"] = snrx - pca_data["snry"] = snry - pca_data["beta_err"] = _np.concatenate((betax_error, betay_error)) - pca_data["phase_err"] = _np.concatenate( - (phasex_error, phasey_error) - ) - self.pca_data = pca_data - return + # calulate tunes of the source signals + spec1, tunes1 = self.calc_spectrum(signals[:, 0], axis=0) + spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) + # modes 0 & 1 and modes 2 & 3 have have equal spectra + # therefore, it suffices to analyze mode 0 and mode 2 only - for plane in planes: - if plane == "x": - traj = self.trajx - else: - traj = self.trajy + tune1 = tunes1[_np.argmax(_np.abs(spec1))] + tune2 = tunes2[_np.argmax(_np.abs(spec2))] - # get model optics - beta_model = self.model_optics["beta" + plane] - phase_model = self.model_optics["phase" + plane] + # identify which signal is which (x or y) + xidcs, yidcs = self.identify_modes( + tune1, tune2, self.tunex, self.tuney + ) - # perform PCA via SVD of history matrix - umat, svals, vtmat = self.calc_svd(traj, full_matrices=False) + pca_data["singular_values"] = svals + pca_data["source_signals"] = signals + pca_data["mixing_matrix"] = mixing_matrix + pca_data["xidcs"] = xidcs + pca_data["yidcs"] = yidcs - # collect source signals and mixing matrix - signals = umat * _np.sqrt(traj.shape[0] - 1) # data signals - mixing_matrix = vtmat.T @ _np.diag(svals) - mixing_matrix /= _np.sqrt(traj.shape[0] - 1) + if not calc_optics: + self.pca_data = pca_data + return - # determine betatron sine and cosine modes - sin_mode = mixing_matrix[:, 1] # check this - cos_mode = mixing_matrix[:, 0] + if self.model_optics is None: + tunes = self.tunex + 49.0, self.tuney + 14.0 + self._get_nominal_optics(tunes) - # calculate beta function & phase from betatron modes - beta, phase = self.get_beta_and_phase_from_betatron_modes( - sin_mode, cos_mode, beta_model - ) + betax_model = self.model_optics["betax"] + betay_model = self.model_optics["betay"] - signal = _np.sqrt(_np.sum(svals[:2] ** 2)) # signal variance - noise = _np.sqrt(_np.sum(svals[2:] ** 2)) # white noise - snr = signal / noise - nrsamples = self.nrsamples_pre + self.nrsamples_post - phase_error = 1 / snr / _np.sqrt(nrsamples) - phase_error *= _np.sqrt(beta_model.mean() / 2 / beta_model) - beta_error = 2 * beta_model * phase_error + # extract betatron sine & cosine modes fom mixing matrix + sin_modex = mixing_matrix[:160, xidcs[-1]] + cos_modex = mixing_matrix[:160, xidcs[0]] + sin_modey = mixing_matrix[160:, yidcs[-1]] + cos_modey = mixing_matrix[160:, yidcs[0]] - # plot_results - if plot: - self.plot_betabeat_and_phase_error( - beta_model, - beta, - phase_model, - phase, - title=f"PCA Analysis: beta{plane} & phase{plane}", - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) + # calculate beta function & phase from betatron modes + betax, phasex = self.get_beta_and_phase_from_betatron_modes( + sin_modex, cos_modex, betax_model + ) + betay, phasey = self.get_beta_and_phase_from_betatron_modes( + sin_modey, cos_modey, betay_model + ) - # save analysis data - pca_data["singular_values_" + plane] = svals - pca_data["source_signals_" + plane] = signals - pca_data["mixing_matrix_" + plane] = mixing_matrix - pca_data["beta" + plane] = beta - pca_data["phase" + plane] = phase - pca_data["snr" + plane] = snr - pca_data["beta" + plane + "_err"] = beta_error - pca_data["phase" + plane + "_err"] = phase_error + # calculate signal variance + signalx = _np.sqrt(_np.sum(svals[xidcs[0] : xidcs[1] + 1] ** 2)) + signaly = _np.sqrt(_np.sum(svals[yidcs[0] : yidcs[1] + 1] ** 2)) + # and signal noise + noise = _np.sqrt(_np.sum(svals[3:] ** 2)) + snrx, snry = signalx / noise, signaly / noise + + # calculate error bars as in Appendix A of + # Wang, Chun-xi and Sajaev, Vadim and Yao, Chih-Yuan. Phase advance + # and ${\beta}$ function measurements using model-independent + # analysis. Phys. Rev. ST Accel. Beams. Vol 6, issue 10. + # DOI 10.1103/PhysRevSTAB.6.104001 + + nrsamples = self.nrsamples_pre + self.nrsamples_post + phasex_error = 1 / snrx / _np.sqrt(nrsamples) + phasex_error *= _np.sqrt(betax_model.mean() / 2 / betax_model) + betax_error = 2 * betax_model * phasex_error + + phasey_error = 1 / snry / _np.sqrt(nrsamples) + phasey_error *= _np.sqrt(betay_model.mean() / 2 / betay_model) + betay_error = 2 * betay_model * phasey_error + + pca_data["betax"] = betax + pca_data["betay"] = betay + pca_data["phasex"] = phasex + pca_data["phasey"] = phasey + pca_data["snrx"] = snrx + pca_data["snry"] = snry + pca_data["betax_err"] = betax_error + pca_data["betay_err"] = betay_error + pca_data["phasex_err"] = phasex_error + pca_data["phasex_err"] = phasey_error self.pca_data = pca_data def identify_modes(self, tune1, tune2, tunex, tuney): @@ -1411,10 +1358,14 @@ def plot_trajs(self, bpm_index=0, timescale=2, compare_fit=False): def plot_modal_analysis(self): """.""" - # TODO: reuse code from `principal_components_analysis` - trajs = _np.concatenate((self.trajx, self.trajy), axis=1) + if self.pca_data is None: + self.principal_components_analysis(calc_optics=False) - u, s, vt = self.calc_svd(trajs, full_matrices=False) + s = self.pca_data["singular_values"] + u = self.pca_data["source_signals"] + vt = self.pca_data["mixing_matrix"].T + xidcs = self.pca_data["xidcs"] + yidcs = self.pca_data["yidcs"] fig = _mplt.figure(figsize=(14, 12)) @@ -1433,69 +1384,105 @@ def plot_modal_analysis(self): spec2 = fig.add_subplot(gs[3, 1], sharex=spec1, sharey=spec1) svals.plot(s, "o", color="k", mfc="none") + idx = _np.arange(len(s)) + sel = _np.array(xidcs) + svals.plot( + idx[sel], s[sel], "o", color="b", + mfc="none", label="hor. signals singular values" + ) + sel = _np.array(yidcs) + svals.plot( + idx[sel], s[sel], "o", color="r", + mfc="none", label='ver. signals singular values') svals.set_title("singular values spectrum") svals.set_yscale("log") + svals.legend() + + variance = _np.cumsum(s) / _np.sum(s) + var.plot(variance, "o", color="k", mfc="none") + idx = _np.arange(len(variance)) + sel = _np.array(xidcs) + var.plot(idx[sel], variance[sel], "o", color="b", mfc="none") + + sel = _np.array(yidcs) + var.plot(idx[sel], variance[sel], "o", color="r", mfc="none") - var.plot(_np.cumsum(s) / _np.sum(s), "o", color="k", mfc="none") var.set_title("explained variance") var.set_xlabel("rank") - source1.plot(u[:, 0], label="mode 0") - source1.plot(u[:, 1], label="mode 1") - source1.set_title("temporal modes - axis 1") + source1.plot(u[:, xidcs[0]], label="hor. mode 0") + source1.plot(u[:, xidcs[1]], label="hor. mode 1") + source1.set_title("temporal modes - hor. source signals") source1.set_xlabel("turns index") source1.legend() - source2.plot(u[:, 2], label="mode 2") - source2.plot(u[:, 3], label="mode 3") - source2.set_title("temporal modes - axis 2") + source2.plot(u[:, yidcs[0]], label="ver. mode 0") + source2.plot(u[:, yidcs[1]], label="ver. mode 1") + source2.set_title("temporal modes - ver. source signals") source2.set_xlabel("turns index") source2.legend() - spatial1.plot(vt.T[:, 0], label="mode 0") - spatial1.plot(vt.T[:, 1], label="mode 1") - spatial1.set_title("spatial modes - axis 1") + spatial1.plot(vt.T[:, xidcs[0]], label="hor. mode 0") + spatial1.plot(vt.T[:, xidcs[1]], label="hor. mode 1") + spatial1.set_title("spatial modes - hor. source signals") spatial1.set_xlabel("BPMs index (H/V)") spatial1.legend() - spatial2.plot(vt.T[:, 2], label="mode 2") - spatial2.plot(vt.T[:, 3], label="mode 3") - spatial2.set_title("spatial modes - axis 2") + spatial2.plot(vt.T[:, yidcs[0]], label="ver. mode 0") + spatial2.plot(vt.T[:, yidcs[1]], label="ver. mode 1") + spatial2.set_title("spatial modes - ver. source signals") spatial2.set_xlabel("BPMs index (H/V)") spatial2.legend() freq, fourier = _np.fft.rfftfreq(n=u.shape[0]), _np.fft.rfft(u, axis=0) + specx = _np.abs(fourier)[:, _np.array(xidcs)] spec1.plot( - freq, - _np.abs(fourier)[:, 0], - "o-", - color="C0", - mfc="none", - label="mode 0", + freq, specx[:, 0], "o-", color="b", + mfc="none", label="hor. mode 0" ) spec1.plot( - freq, _np.abs(fourier)[:, 1], "x-", color="C0", label="mode 1" + freq, specx[:, 1], "x-", color="b", + label="hor. mode 1" ) - spec1.set_title("temporal modes spectrum - axis 1") + spec1.set_title("temporal modes spectrum - hor. source signals") spec1.set_xlabel("fractional tune") spec1.legend() + peak_idx = _np.argmax(specx[:, 0]) + peak_val = specx[peak_idx, 0] + peak_freq = freq[peak_idx] + spec1.annotate( + f"hor. tune = {peak_freq:.4f}", + xy=(peak_freq, peak_val), + xytext=(peak_freq + 0.05, 0.9 * peak_val), + arrowprops=dict(arrowstyle="->", color="k"), + bbox=dict(boxstyle="round,pad=0.2", fc="w", ec="k", lw=0.5), + fontsize=10, + ) + specy = _np.abs(fourier)[:, _np.array(yidcs)] spec2.plot( - freq, - _np.abs(fourier)[:, 2], - "o-", - color="C1", - mfc="none", - label="mode 2", + freq, specy[:, 0], "o-", color="r", + mfc="none", label="ver. mode 0", ) spec2.plot( - freq, _np.abs(fourier)[:, 3], "x-", color="C1", label="mode 3" + freq, specy[:, 1], "x-", color="r", + label="ver. mode 1" ) - spec2.set_title("temporal modes spectrum - axis 2") + spec2.set_title("temporal modes spectrum - ver. source signals") spec2.set_xlabel("fractional tune") spec2.legend() - + peak_idx = _np.argmax(specy[:, 0]) + peak_val = specy[peak_idx, 0] + peak_freq = freq[peak_idx] + spec2.annotate( + f"ver. tune = {peak_freq:.4f}", + xy=(peak_freq, peak_val), + xytext=(peak_freq + 0.05, 0.9 * peak_val), + arrowprops=dict(arrowstyle="->", color="k"), + bbox=dict(boxstyle="round,pad=0.2", fc="w", ec="k", lw=0.5), + fontsize=10, + ) _mplt.tight_layout() _mplt.show() From 7c140c83db28569015373b192e1fcda4c4aff4a5 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 16 Jun 2025 16:29:27 -0300 Subject: [PATCH 135/144] COMMISS.AcqBPMs.ENH: changes during online tests - standardize event related properties: event_mode, event_delay, etc. - use delay raw instead of delays - avoid changing `evg.nr_pulse` if not necessary --- apsuite/commisslib/meas_bpms_signals.py | 50 +++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/apsuite/commisslib/meas_bpms_signals.py b/apsuite/commisslib/meas_bpms_signals.py index 6956eb77..38d5a64d 100644 --- a/apsuite/commisslib/meas_bpms_signals.py +++ b/apsuite/commisslib/meas_bpms_signals.py @@ -18,11 +18,11 @@ class AcqBPMsSignalsParams(_ParamsBaseClass): def __init__(self): """.""" - self.trigbpm_delay = None + self.trigbpm_delay_raw = None self.trigbpm_nrpulses = 1 self.do_pulse_evg = True self._timing_event = "Study" - self.event_delay = None + self.event_delay_raw = None self.event_mode = "External" self.timeout = 40 self.nrpoints_before = 0 @@ -37,23 +37,27 @@ def __str__(self): dtmp = "{0:26s} = {1:9d} {2:s}\n".format stmp = "{0:26s} = {1:9} {2:s}\n".format stg = "" - dly = self.trigbpm_delay + dly = self.trigbpm_delay_raw if dly is None: stg += stmp( - "trigbpm_delay", "same", "(current value will not be changed)" + "trigbpm_delay_raw", + "same", + "(current value will not be changed)" ) else: - stg += ftmp("trigbpm_delay", dly, "[us]") + stg += dtmp("trigbpm_delay_raw", int(dly), "") stg += dtmp("trigbpm_nrpulses", self.trigbpm_nrpulses, "") stg += stmp("do_pulse_evg", str(self.do_pulse_evg), "") stg += stmp("timing_event", self.timing_event, "") - dly = self.event_delay + dly = self.event_delay_raw if dly is None: stg += stmp( - "event_delay", "same", "(current value will not be changed)" + "event_delay_raw", + "same", + "(current value will not be changed)" ) else: - stg += ftmp("event_delay", dly, "[us]") + stg += dtmp("event_delay_raw", int(dly), "") stg += stmp("event_mode", self.event_mode, "") stg += ftmp("timeout", self.timeout, "[s]") stg += dtmp("nrpoints_before", self.nrpoints_before, "") @@ -149,14 +153,14 @@ def get_timing_state(self): state = dict() state["trigbpm_source"] = trigbpm.source_str state["trigbpm_nrpulses"] = trigbpm.nr_pulses - state["trigbpm_delay"] = trigbpm.delay + state["trigbpm_delay_raw"] = trigbpm.delay_raw if self.params.do_pulse_evg: state["evg_nrpulses"] = self.devices["evg"].nrpulses evt = self._get_event(self.params.timing_event) if evt is not None: - state["evt_delay"] = evt.delay - state["evt_mode"] = evt.mode_str + state["event_delay_raw"] = evt.delay_raw + state["event_mode"] = evt.mode_str return state def recover_timing_state(self, state): @@ -168,9 +172,9 @@ def prepare_timing(self, state=None): state = dict() if state is None else state trigbpm = self.devices["trigbpm"] - dly = state.get("trigbpm_delay", self.params.trigbpm_delay) + dly = state.get("trigbpm_delay_raw", self.params.trigbpm_delay_raw) if dly is not None: - trigbpm.delay = dly + trigbpm.delay_raw = dly trigbpm.nr_pulses = state.get( "trigbpm_nrpulses", self.params.trigbpm_nrpulses @@ -180,18 +184,18 @@ def prepare_timing(self, state=None): evt = self._get_event(self.params.timing_event) if evt is not None: - dly = state.get("evt_delay", self.params.event_delay) + dly = state.get("event_delay_raw", self.params.event_delay_raw) if dly is not None: - evt.delay = dly - evt.mode = state.get("evt_mode", self.params.event_mode) - self.devices["evg"].cmd_update_events() + evt.delay_raw = dly + evt.mode = state.get("event_mode", self.params.event_mode) + + if self.params.event_mode != "External": + nrpul = 1 if self.params.do_pulse_evg else None + nrpul = state.get("evg_nrpulses", nrpul) + if nrpul is not None: + self.devices["evg"].set_nrpulses(nrpul) - nrpul = 1 if self.params.do_pulse_evg else None - nrpul = state.get("evg_nrpulses", nrpul) - if nrpul is not None: - evg = self.devices["evg"] - evg.set_nrpulses(nrpul) - evg.cmd_update_events() # need to be called 2x? + self.devices["evg"].cmd_update_events() def trigger_timing_signal(self): """.""" From 75164ff23180882667c3bae8d645b24271e98471 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 16 Jun 2025 16:35:29 -0300 Subject: [PATCH 136/144] COMMISS.TBT.MEAS.ENH: changes during online tests - default `do_pulse_evg` to False - init state properties and function to update init states - propagate changes from AcqBPMsSginals to standardize "event" properties --- apsuite/commisslib/measure_tbt_data.py | 40 ++++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index cf68f693..85d002ef 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -32,7 +32,7 @@ def __init__(self): self.nrpoints_before = 50 self.nrpoints_after = 750 self.restore_init_state = True - self.do_pulse_evg = True + self.do_pulse_evg = False self.mags_strength_rtol = 0.05 self._pingers2kick = "" # 'h', 'v' or 'hv' @@ -120,10 +120,16 @@ def __init__(self, isonline=False): ) self._meas_finished_ok = True + self._init_timing_state = dict() + self._init_magnets_state = dict() - if self.isonline: - self._init_magnets_state = self.get_magnets_state() - self._init_timing_state = self.get_timing_state() + @property + def init_timing_state(self): + return self._init_timing_state + + @property + def init_magnets_state(self): + return self._init_magnets_state def create_devices(self): """.""" @@ -417,6 +423,13 @@ def get_data(self): data["init_timing_state"] = self._init_timing_state return data + def update_initial_states(self): + """.""" + if not self.connected: + raise RuntimeError("Not connected yet!") + self._init_timing_state = self.get_timing_state() + self._init_magnets_state = self.get_magnets_state() + def _restore_and_exit(self, timing_state=None, magnets_state=None): """Restore timing and magnets state and exit the measurement.""" print("Restoring machine initial state.") @@ -431,8 +444,6 @@ def _restore_and_exit(self, timing_state=None, magnets_state=None): # prevent meas starting before magnets reach the required strentgh print(f"\tMagnets restored: {mags_ok}") - print("Measurement finished.") - def check_machine_restored(self): """.""" if self.ismeasuring: @@ -555,9 +566,12 @@ def __str__(self): stg += "\n" stg += "EVT state\n" - stg += stmp("evt_mode", data["timing_state"]["evt_mode"], "") - stg += stmp("evt_delay", data["timing_state"]["evt_delay"], "") - + stg += stmp("event_mode", data["timing_state"]["event_mode"], "") + stg += dtmp( + "event_delay_raw", + int(data["timing_state"]["event_delay_raw"]), + "" + ) stg += "\n" stg += "BPMs state\n" stg += "\n" @@ -570,13 +584,15 @@ def __str__(self): stg += stmp( "trigbpm_source", data["timing_state"]["trigbpm_source"], "" ) - stg += stmp( + stg += dtmp( "trigbpm_nrpulses", data["timing_state"]["trigbpm_nrpulses"], "", ) - stg += stmp( - "trigbpm_delay", data["timing_state"]["trigbpm_delay"], "" + stg += dtmp( + "trigbpm_delay_raw", + int(data["timing_state"]["trigbpm_delay_raw"]), + "" ) stg += "\n" From 9396eef103a940284d38fba1c5917aa7d0c7c092 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 26 Jun 2025 09:39:52 -0300 Subject: [PATCH 137/144] COMMISS.TBT.ANLY.ENH: refactor PCA & modal analysis - generalized framework for modal analysis to be also applied to ICA - automatic detection of sin/cos modes - now also storing sin/cos spatial/temporal modes in `pca_data` - `plot_modal_analysis` now also indicates sin/cos temporal/spatial modes - WIP: refactor of ICA --- apsuite/commisslib/measure_tbt_data.py | 446 ++++++++++++++----------- 1 file changed, 255 insertions(+), 191 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 85d002ef..cca5adc6 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -965,24 +965,66 @@ def principal_components_analysis(self, calc_optics=True): mixing_matrix /= _np.sqrt(traj.shape[0] - 1) # calulate tunes of the source signals - spec1, tunes1 = self.calc_spectrum(signals[:, 0], axis=0) + spec0, tunes0 = self.calc_spectrum(signals[:, 0], axis=0) + spec1, tunes1 = self.calc_spectrum(signals[:, 1], axis=0) spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) - # modes 0 & 1 and modes 2 & 3 have have equal spectra - # therefore, it suffices to analyze mode 0 and mode 2 only + spec3, tunes3 = self.calc_spectrum(signals[:, 3], axis=0) + tune0 = tunes0[_np.argmax(_np.abs(spec0))] tune1 = tunes1[_np.argmax(_np.abs(spec1))] tune2 = tunes2[_np.argmax(_np.abs(spec2))] + tune3 = tunes3[_np.argmax(_np.abs(spec3))] # identify which signal is which (x or y) - xidcs, yidcs = self.identify_modes( - tune1, tune2, self.tunex, self.tuney + xidcs, yidcs = self.identify_modes_planes( + tunes=[tune0, tune1, tune2, tune3], + tunex=self.tunex, + tuney=self.tuney, ) + # extract betatron modes fom mixing matrix + xspatial_modes = mixing_matrix[:, xidcs] + xtemp_modes = signals[:, xidcs] + + yspatial_modes = mixing_matrix[:, yidcs] + ytemp_modes = signals[:, yidcs] + + # determine sin and cos modes temporal modes + # sin/cos temporal modes correspond to cos/sin spatial modes + # spatial modes are used for calc. optics + # as in Xiaobiao's book , eqs. 5.31-5.32 (PCA), 5.55 (ICA) + sinx_idx, cosx_idx = self.identify_sin_cos_modes( + sources=xtemp_modes, + plane="hor", + ) + siny_idx, cosy_idx = self.identify_sin_cos_modes( + sources=ytemp_modes, + plane="ver", + ) + + sin_temp_modex = xtemp_modes[:, sinx_idx] # cos/sin spatial mode + cos_temp_modex = xtemp_modes[:, cosx_idx] # corresponds to + sin_spatial_modex = xspatial_modes[:, cosx_idx] # sin/cos temp. + cos_spatial_modex = xspatial_modes[:, sinx_idx] + + sin_temp_modey = ytemp_modes[:, siny_idx] + cos_temp_modey = ytemp_modes[:, cosy_idx] + sin_spatial_modey = yspatial_modes[:, cosy_idx] + cos_spatial_modey = yspatial_modes[:, siny_idx] + pca_data["singular_values"] = svals pca_data["source_signals"] = signals pca_data["mixing_matrix"] = mixing_matrix pca_data["xidcs"] = xidcs pca_data["yidcs"] = yidcs + pca_data["sin_spatial_modex"] = sin_spatial_modex + pca_data["cos_spatial_modex"] = cos_spatial_modex + pca_data["sin_temp_modex"] = sin_temp_modex + pca_data["cos_temp_modex"] = cos_temp_modex + pca_data["sin_spatial_modey"] = sin_spatial_modey + pca_data["cos_spatial_modey"] = cos_spatial_modey + pca_data["sin_temp_modey"] = sin_temp_modey + pca_data["cos_temp_modey"] = cos_temp_modey if not calc_optics: self.pca_data = pca_data @@ -995,23 +1037,17 @@ def principal_components_analysis(self, calc_optics=True): betax_model = self.model_optics["betax"] betay_model = self.model_optics["betay"] - # extract betatron sine & cosine modes fom mixing matrix - sin_modex = mixing_matrix[:160, xidcs[-1]] - cos_modex = mixing_matrix[:160, xidcs[0]] - sin_modey = mixing_matrix[160:, yidcs[-1]] - cos_modey = mixing_matrix[160:, yidcs[0]] - - # calculate beta function & phase from betatron modes + # calculate beta function & phase from spatial betatron modes betax, phasex = self.get_beta_and_phase_from_betatron_modes( - sin_modex, cos_modex, betax_model + sin_spatial_modex[:160], cos_spatial_modex[:160], betax_model ) betay, phasey = self.get_beta_and_phase_from_betatron_modes( - sin_modey, cos_modey, betay_model + sin_spatial_modey[160:], cos_spatial_modey[160:], betay_model ) # calculate signal variance - signalx = _np.sqrt(_np.sum(svals[xidcs[0] : xidcs[1] + 1] ** 2)) - signaly = _np.sqrt(_np.sum(svals[yidcs[0] : yidcs[1] + 1] ** 2)) + signalx = _np.sqrt(_np.sum(svals[xidcs] ** 2)) + signaly = _np.sqrt(_np.sum(svals[yidcs] ** 2)) # and signal noise noise = _np.sqrt(_np.sum(svals[3:] ** 2)) snrx, snry = signalx / noise, signaly / noise @@ -1043,10 +1079,10 @@ def principal_components_analysis(self, calc_optics=True): pca_data["phasex_err"] = phasey_error self.pca_data = pca_data - def identify_modes(self, tune1, tune2, tunex, tuney): - """Identify the x and y betatron modes in mixing matrix. + def identify_modes_planes(self, tunes, tunex, tuney): + """Identify the x and y betatron modes idcs in PCA/ICA decomposition. - When calculating the mixing matrix via PCA or ICA, the horizontal and + When calculating the mixing matrix via PCA or ICA, the hor. and ver. betatron modes need the be determined. PCA sorts the modes with increasing variance (singular values), while ICA sorts the modes arbitrairly. By calculating the tune of the source signals @@ -1055,169 +1091,188 @@ def identify_modes(self, tune1, tune2, tunex, tuney): beatron modes can be identified. Args: - tune1 (float): tune of the 1st & 2nd mode - tune2 (float): tune of the 3rd and 4th mode - tunex (float): _description_ - tuney (float): _description_ + tunes (list, tuple): calculated tunes for the candidate modes + tunex (float): hor. tune + tuney (float): ver. tune Returns: - _type_: _description_ + xidcs, yidcs: arrays containig the hor./ver. modes indices for PCA/ + ICA decompositions. """ - diff1x = _np.abs(tune1 - tunex) - diff1y = _np.abs(tune1 - tuney) - diff2x = _np.abs(tune2 - tunex) - diff2y = _np.abs(tune2 - tuney) - - # Assign based on minimum differences - if diff1x < diff2x and diff2y < diff1y: - xidcs = 0, 1 - yidcs = 2, 3 - else: - xidcs = 2, 3 - yidcs = 0, 1 - + tunes = _np.array(tunes)[None, :] + diff = _np.array([tunex, tuney])[:, None] - tunes + idcs = _np.argmin(_np.abs(diff), axis=0).astype(bool) + xidcs = _np.argwhere(~idcs).squeeze() + yidcs = _np.argwhere(idcs).squeeze() return xidcs, yidcs - def independent_components_analysis( - self, - n_components=8, - method="FastICA", - plot=True, - compare_meas2model=True, - ): - r"""Peforms Independent Components Analysis (ICA). - - Calculates beta-functions and betatron phase-advance at the BPMs using - ICA. - - ICA aims to identify the linear transformation (unmixing matrix) - revealing statistically independent source signals. Just as in PCA, - the beatron motion sine and cosine modes can be used to calculate - beta-functions and BPMs phase advances. - - While PCA aims to identify the linear transformation revealing - uncorrelated source signals, ICA seeks the transformation - revealing statistically independent signals, a requirement much - stronger than uncorrelatedness. - - ICA often performs better at blind source separation for linear - mixtures of sinals with non-gaussian distributions, which is relevant - for when several source signals have similar variance. This method - thus is generally more robust at betatron motion identification when - there are contaminating signals, bad acquisitions or similar variance - between horizontal and vertical modes (partticularly relevant when - betatron coupling is significant). - - ICA can be implemented with second-order blind source identification - (SOBI) [1], based on simultaneous diagonalization of the time-shifted - data covariance matrices or with information-theoretic - approaches seeking the maximization of the statistical indpendence of - the estimated source signals [2]. We use the latter, as implemented in - scikit-learn's FastICA [3,4]. - - The variance convention is the same as in PCA analysis: whiten source - signals, with the mixing matrix containing the modes energy/variance - [4]. - - Args: - n_components (int, optional): number of independent components to - decompose the data - - plot (bool, optiional): whether to plot the analysis results. - Defaults to True - - compare_meas2model (bool, optional): whether to plot measured and - nominal beta-functions and BPMs phase-advance, as well as - beta-beting and phase-advance errors or plot only beta-beating and - phase-advance-errors. Defaults to True - - References: - - [1] Huang, X. Beam-Based Correction and Optimization for Accelerators, - Section 5.2.3. CRC Press. 2020. - - [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms - and applications. Neural Networks. Volume 13, Issues 4-5, 2000, - Pages 411-430, https://doi.org/10.1016/S0893-6080(00)00026-5. - - [3] scikit-learn.decomposition.FastICA documentation. - https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html - - [4] Scikit-learn examples. "Blind Source Separation using FastICA" - https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py - """ - ica_data = dict() - for pinger in self.params.pingers2kick: - if pinger == "h": - traj = self.trajx - label = "x" - else: - traj = self.trajy - label = "y" - tunes = self.tunex + 49.0, self.tuney + 14.0 - - if self.model_optics is None: - self._get_nominal_optics(tunes) - - # get model optics - beta_model = self.model_optics["beta" + label] - phase_model = self.model_optics["phase" + label] - - # perform Independent Component Analysis (ICA) - if method == "FastICA": - ica = _FastICA( - n_components=n_components, - whiten="unit-variance", - algorithm="parallel", - tol=1e-12, - ) - if method == "SOBI": - ica = _SOBI( - n_components=n_components, - n_lags=5, - whiten="unit-variance", - isreal=True, - verbose=False, - ) - # collect source signals & mixing matrix - signals = ica.fit_transform(traj) - mixing_matrix = ica.mixing_ - - # determine betatron modes from mixing matrix - # largest variance should be contained in the betatron modes - idcs = _np.argsort(_np.std(mixing_matrix, axis=0))[-2:] - sin_mode = mixing_matrix[:, idcs[0]] - cos_mode = mixing_matrix[:, idcs[-1]] - - # determine which betatron mode is the sine & which is cosine - # sine mode starts off close to zero - if _np.abs(sin_mode[0]) > _np.abs(cos_mode[0]): - cos_mode, sin_mode = sin_mode, cos_mode - idcs[0], idcs[1] = idcs[1], idcs[0] - - # calculate beta function & phase from betatron modes - beta, phase = self.get_beta_and_phase_from_betatron_modes( - sin_mode, cos_mode, beta_model - ) - - # plot results - if plot: - self.plot_betabeat_and_phase_error( - beta_model, - beta, - phase_model, - phase, - title=f"ICA Analysis: beta{label} & phase{label}", - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) - - # save results - ica_data["source_signals_" + label] = signals - ica_data["mixing_matrix_" + label] = mixing_matrix - ica_data["beta" + label] = beta - ica_data["phase" + label] = phase - self.ica_data = ica_data + def identify_sin_cos_modes(self, sources=None, plane="hor"): + """Identify which TEMPORAl modes are sin/cos modes.""" + if plane.lower() == "hor": + init, fin = self.trajx_turns_slice + tune = self.tunex + else: + init, fin = self.trajx_turns_slice + tune = self.tuney + + n = _np.arange(fin-init) + sin = _np.sin(2 * _np.pi * tune * n)[:, None] + cos = _np.cos(2 * _np.pi * tune * n)[:, None] + ref = _np.concatenate((sin, cos), axis=1) + corr = sources[init:fin].T @ ref + idcs = _np.abs(corr).argmax(axis=0) + sin_mode_idx = idcs[0] + cos_mode_idx = idcs[1] + return sin_mode_idx, cos_mode_idx + + # def independent_components_analysis( + # self, + # n_components=4, + # method="SOBI", + # plot=True, + # compare_meas2model=True, + # **kwargs + # ): + # r"""Peforms Independent Components Analysis (ICA). + + # Calculates beta-functions and betatron phase-advance at the BPMs using + # ICA. + + # ICA aims to identify the linear transformation (unmixing matrix) + # revealing statistically independent source signals. Just as in PCA, + # the beatron motion sine and cosine modes can be used to calculate + # beta-functions and BPMs phase advances. + + # While PCA aims to identify the linear transformation revealing + # uncorrelated source signals, ICA seeks the transformation + # revealing statistically independent signals, a requirement much + # stronger than uncorrelatedness. + + # ICA often performs better at blind source separation for linear + # mixtures of sinals with non-gaussian distributions, which is relevant + # for when several source signals have similar variance. This method + # thus is generally more robust at betatron motion identification when + # there are contaminating signals, bad acquisitions or similar variance + # between horizontal and vertical modes (partticularly relevant when + # betatron coupling is significant). + + # ICA can be implemented with second-order blind source identification + # (SOBI) [1], based on simultaneous diagonalization of the time-shifted + # data covariance matrices or with information-theoretic + # approaches seeking the maximization of the statistical indpendence of + # the estimated source signals [2]. We use the latter, as implemented in + # scikit-learn's FastICA [3,4]. + + # The variance convention is the same as in PCA analysis: whiten source + # signals, with the mixing matrix containing the modes energy/variance + # [4]. + + # Args: + # n_components (int, optional): number of independent components to + # decompose the data + + # plot (bool, optiional): whether to plot the analysis results. + # Defaults to True + + # compare_meas2model (bool, optional): whether to plot measured and + # nominal beta-functions and BPMs phase-advance, as well as + # beta-beting and phase-advance errors or plot only beta-beating and + # phase-advance-errors. Defaults to True + + # References: + + # [1] Huang, X. Beam-Based Correction and Optimization for Accelerators, + # Section 5.2.3. CRC Press. 2020. + + # [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms + # and applications. Neural Networks. Volume 13, Issues 4-5, 2000, + # Pages 411-430, https://doi.org/10.1016/S0893-6080(00)00026-5. + + # [3] scikit-learn.decomposition.FastICA documentation. + # https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html + + # [4] Scikit-learn examples. "Blind Source Separation using FastICA" + # https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py + # """ + # ica_data = dict() + + # traj = _np.concatenate((self.trajx, self.trajy), axis=1) + + # # perform Independent Component Analysis (ICA) + # if method.lower() == "fastica": + # ica_ = _FastICA + # elif method.lower() == "sobi": + # ica_ = _SOBI + # else: + # ica_ = _SOBI + # raise Warning("Unknown ICA method. Using SOBI.") + + # ica = ica_(n_components=n_components, **kwargs) + + # # collect source signals & mixing matrix + # signals = ica.fit_transform(traj) + # mixing_matrix = ica.mixing_ + + # # determine betatron modes from mixing matrix + # # largest variance should be contained in the betatron modes + # idcs = _np.argsort(_np.std(mixing_matrix, axis=0))[-4:] + + # # calulate tunes of the source signals + # spec0, tunes0 = self.calc_spectrum(signals[:, 0], axis=0) + # spec1, tunes1 = self.calc_spectrum(signals[:, 1], axis=0) + # spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) + # spec3, tunes3 = self.calc_spectrum(signals[:, 3], axis=0) + + # tune0 = tunes0[_np.argmax(_np.abs(spec0))] + # tune1 = tunes1[_np.argmax(_np.abs(spec1))] + # tune2 = tunes2[_np.argmax(_np.abs(spec2))] + # tune3 = tunes3[_np.argmax(_np.abs(spec3))] + + # # identify which signal is which (x or y) + # xidcs, yidcs = self.identify_modes( + # tune1, tune2, self.tunex, self.tuney + + # sin_mode = mixing_matrix[:, idcs[0]] + # cos_mode = mixing_matrix[:, idcs[-1]] + + # # determine which betatron mode is the sine & which is cosine + # # sine mode starts off close to zero + # if _np.abs(sin_mode[0]) > _np.abs(cos_mode[0]): + # cos_mode, sin_mode = sin_mode, cos_mode + # idcs[0], idcs[1] = idcs[1], idcs[0] + + # ica_data = dict() + # if self.model_optics is None: + # tunes = self.tunex + 49.0, self.tuney + 14.0 + # self._get_nominal_optics(tunes) + + # # get model optics + # beta_model = self.model_optics["beta" + label] + # phase_model = self.model_optics["phase" + label] + + # # calculate beta function & phase from betatron modes + # beta, phase = self.get_beta_and_phase_from_betatron_modes( + # sin_mode, cos_mode, beta_model + # ) + + # # plot results + # if plot: + # self.plot_betabeat_and_phase_error( + # beta_model, + # beta, + # phase_model, + # phase, + # title=f"ICA Analysis: beta{label} & phase{label}", + # compare_meas2model=compare_meas2model, + # bpms2use=self.bpms2use, + # ) + + # # save results + # ica_data["source_signals_" + label] = signals + # ica_data["mixing_matrix_" + label] = mixing_matrix + # ica_data["beta" + label] = beta + # ica_data["phase" + label] = phase + # self.ica_data = ica_data def equilibrium_params_analysis(self): """.""" @@ -1379,10 +1434,19 @@ def plot_modal_analysis(self): s = self.pca_data["singular_values"] u = self.pca_data["source_signals"] - vt = self.pca_data["mixing_matrix"].T xidcs = self.pca_data["xidcs"] yidcs = self.pca_data["yidcs"] + sin_temp_modex = self.pca_data["sin_temp_modex"] + sin_temp_modey = self.pca_data["sin_temp_modey"] + cos_temp_modex = self.pca_data["cos_temp_modex"] + cos_temp_modey = self.pca_data["cos_temp_modey"] + + sin_spatial_modex = self.pca_data["sin_spatial_modex"] + sin_spatial_modey = self.pca_data["sin_spatial_modey"] + cos_spatial_modex = self.pca_data["cos_spatial_modex"] + cos_spatial_modey = self.pca_data["cos_spatial_modey"] + fig = _mplt.figure(figsize=(14, 12)) gs = _gridspec.GridSpec(4, 2) @@ -1426,26 +1490,26 @@ def plot_modal_analysis(self): var.set_title("explained variance") var.set_xlabel("rank") - source1.plot(u[:, xidcs[0]], label="hor. mode 0") - source1.plot(u[:, xidcs[1]], label="hor. mode 1") + source1.plot(sin_temp_modex, label="hor. sin mode") + source1.plot(cos_temp_modex, label="hor. cos mode") source1.set_title("temporal modes - hor. source signals") source1.set_xlabel("turns index") source1.legend() - source2.plot(u[:, yidcs[0]], label="ver. mode 0") - source2.plot(u[:, yidcs[1]], label="ver. mode 1") + source2.plot(sin_temp_modey, label="ver. sin mode") + source2.plot(cos_temp_modey, label="ver. cos mode") source2.set_title("temporal modes - ver. source signals") source2.set_xlabel("turns index") source2.legend() - spatial1.plot(vt.T[:, xidcs[0]], label="hor. mode 0") - spatial1.plot(vt.T[:, xidcs[1]], label="hor. mode 1") + spatial1.plot(cos_spatial_modex, label="hor. cos mode") + spatial1.plot(sin_spatial_modex, label="hor. sin mode") spatial1.set_title("spatial modes - hor. source signals") spatial1.set_xlabel("BPMs index (H/V)") spatial1.legend() - spatial2.plot(vt.T[:, yidcs[0]], label="ver. mode 0") - spatial2.plot(vt.T[:, yidcs[1]], label="ver. mode 1") + spatial2.plot(cos_spatial_modey, label="ver. cos mode") + spatial2.plot(sin_spatial_modey, label="ver. sin mode") spatial2.set_title("spatial modes - ver. source signals") spatial2.set_xlabel("BPMs index (H/V)") spatial2.legend() @@ -1455,11 +1519,11 @@ def plot_modal_analysis(self): specx = _np.abs(fourier)[:, _np.array(xidcs)] spec1.plot( freq, specx[:, 0], "o-", color="b", - mfc="none", label="hor. mode 0" + mfc="none", label="hor. cos mode" ) spec1.plot( freq, specx[:, 1], "x-", color="b", - label="hor. mode 1" + label="hor. sin mode" ) spec1.set_title("temporal modes spectrum - hor. source signals") spec1.set_xlabel("fractional tune") @@ -1479,11 +1543,11 @@ def plot_modal_analysis(self): specy = _np.abs(fourier)[:, _np.array(yidcs)] spec2.plot( freq, specy[:, 0], "o-", color="r", - mfc="none", label="ver. mode 0", + mfc="none", label="ver. cos mode", ) spec2.plot( freq, specy[:, 1], "x-", color="r", - label="ver. mode 1" + label="ver. sin mode" ) spec2.set_title("temporal modes spectrum - ver. source signals") spec2.set_xlabel("fractional tune") @@ -1741,7 +1805,7 @@ def get_beta_and_phase_from_betatron_modes( beta = sin_mode**2 + cos_mode**2 beta /= _np.std(beta) / _np.std(beta_model) phase = _np.arctan2(sin_mode, cos_mode) - phase = _np.unwrap(phase, discont=_np.pi) + phase = _np.unwrap(phase) return beta, phase def _get_nominal_optics(self, tunes=None, chroms=None): From 0aa9acce2ff480cf47dccac3ec512ceea9ac03b8 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 26 Jun 2025 14:50:08 -0300 Subject: [PATCH 138/144] COMMISS.TBT.ANLY.ENH: refactor ICA - refactor ICA analysis for stacked hor/ver. planes analysis, by default, as in PCA. - include alternative to view ICA temporal/spatial modes in `plot_modal_analysis` Obs: ICA via FastICA is failing to separate signals, and this workflow is failing. SOBI is "working" (seems only to perform PCA), but not understood. --- apsuite/commisslib/measure_tbt_data.py | 380 ++++++++++++++----------- 1 file changed, 215 insertions(+), 165 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index cca5adc6..7f7f950c 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -2,12 +2,13 @@ import datetime as _datetime import time as _time +import warnings import matplotlib.gridspec as _gridspec import matplotlib.pyplot as _mplt import numpy as _np import pyaccel as _pa -# from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys +from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys from pymodels import si as _si from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU as _PowerSupplyPU, \ @@ -720,7 +721,7 @@ def load_and_apply(self, fname): self.switching_freq = data.get("switching_frequency", None) def linear_optics_analysis( - self, method="PCA", compare_meas2model=True + self, method="PCA", compare_meas2model=True, **kwargs ): # TODO: Missing kwargs for each method """Linear optics (beta-beating & phase adv. errors) analysis. @@ -736,7 +737,7 @@ def linear_optics_analysis( self.principal_components_analysis(calc_optics=True) data = self.pca_data elif method.lower() == "ica": - self.independent_components_analysis() + self.independent_components_analysis(calc_optics=True, **kwargs) data = self.ica_data betax = data["betax"] @@ -1125,154 +1126,189 @@ def identify_sin_cos_modes(self, sources=None, plane="hor"): cos_mode_idx = idcs[1] return sin_mode_idx, cos_mode_idx - # def independent_components_analysis( - # self, - # n_components=4, - # method="SOBI", - # plot=True, - # compare_meas2model=True, - # **kwargs - # ): - # r"""Peforms Independent Components Analysis (ICA). - - # Calculates beta-functions and betatron phase-advance at the BPMs using - # ICA. - - # ICA aims to identify the linear transformation (unmixing matrix) - # revealing statistically independent source signals. Just as in PCA, - # the beatron motion sine and cosine modes can be used to calculate - # beta-functions and BPMs phase advances. - - # While PCA aims to identify the linear transformation revealing - # uncorrelated source signals, ICA seeks the transformation - # revealing statistically independent signals, a requirement much - # stronger than uncorrelatedness. - - # ICA often performs better at blind source separation for linear - # mixtures of sinals with non-gaussian distributions, which is relevant - # for when several source signals have similar variance. This method - # thus is generally more robust at betatron motion identification when - # there are contaminating signals, bad acquisitions or similar variance - # between horizontal and vertical modes (partticularly relevant when - # betatron coupling is significant). - - # ICA can be implemented with second-order blind source identification - # (SOBI) [1], based on simultaneous diagonalization of the time-shifted - # data covariance matrices or with information-theoretic - # approaches seeking the maximization of the statistical indpendence of - # the estimated source signals [2]. We use the latter, as implemented in - # scikit-learn's FastICA [3,4]. - - # The variance convention is the same as in PCA analysis: whiten source - # signals, with the mixing matrix containing the modes energy/variance - # [4]. - - # Args: - # n_components (int, optional): number of independent components to - # decompose the data - - # plot (bool, optiional): whether to plot the analysis results. - # Defaults to True - - # compare_meas2model (bool, optional): whether to plot measured and - # nominal beta-functions and BPMs phase-advance, as well as - # beta-beting and phase-advance errors or plot only beta-beating and - # phase-advance-errors. Defaults to True - - # References: - - # [1] Huang, X. Beam-Based Correction and Optimization for Accelerators, - # Section 5.2.3. CRC Press. 2020. - - # [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms - # and applications. Neural Networks. Volume 13, Issues 4-5, 2000, - # Pages 411-430, https://doi.org/10.1016/S0893-6080(00)00026-5. - - # [3] scikit-learn.decomposition.FastICA documentation. - # https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html - - # [4] Scikit-learn examples. "Blind Source Separation using FastICA" - # https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py - # """ - # ica_data = dict() - - # traj = _np.concatenate((self.trajx, self.trajy), axis=1) - - # # perform Independent Component Analysis (ICA) - # if method.lower() == "fastica": - # ica_ = _FastICA - # elif method.lower() == "sobi": - # ica_ = _SOBI - # else: - # ica_ = _SOBI - # raise Warning("Unknown ICA method. Using SOBI.") - - # ica = ica_(n_components=n_components, **kwargs) - - # # collect source signals & mixing matrix - # signals = ica.fit_transform(traj) - # mixing_matrix = ica.mixing_ - - # # determine betatron modes from mixing matrix - # # largest variance should be contained in the betatron modes - # idcs = _np.argsort(_np.std(mixing_matrix, axis=0))[-4:] - - # # calulate tunes of the source signals - # spec0, tunes0 = self.calc_spectrum(signals[:, 0], axis=0) - # spec1, tunes1 = self.calc_spectrum(signals[:, 1], axis=0) - # spec2, tunes2 = self.calc_spectrum(signals[:, 2], axis=0) - # spec3, tunes3 = self.calc_spectrum(signals[:, 3], axis=0) - - # tune0 = tunes0[_np.argmax(_np.abs(spec0))] - # tune1 = tunes1[_np.argmax(_np.abs(spec1))] - # tune2 = tunes2[_np.argmax(_np.abs(spec2))] - # tune3 = tunes3[_np.argmax(_np.abs(spec3))] - - # # identify which signal is which (x or y) - # xidcs, yidcs = self.identify_modes( - # tune1, tune2, self.tunex, self.tuney - - # sin_mode = mixing_matrix[:, idcs[0]] - # cos_mode = mixing_matrix[:, idcs[-1]] - - # # determine which betatron mode is the sine & which is cosine - # # sine mode starts off close to zero - # if _np.abs(sin_mode[0]) > _np.abs(cos_mode[0]): - # cos_mode, sin_mode = sin_mode, cos_mode - # idcs[0], idcs[1] = idcs[1], idcs[0] - - # ica_data = dict() - # if self.model_optics is None: - # tunes = self.tunex + 49.0, self.tuney + 14.0 - # self._get_nominal_optics(tunes) - - # # get model optics - # beta_model = self.model_optics["beta" + label] - # phase_model = self.model_optics["phase" + label] - - # # calculate beta function & phase from betatron modes - # beta, phase = self.get_beta_and_phase_from_betatron_modes( - # sin_mode, cos_mode, beta_model - # ) - - # # plot results - # if plot: - # self.plot_betabeat_and_phase_error( - # beta_model, - # beta, - # phase_model, - # phase, - # title=f"ICA Analysis: beta{label} & phase{label}", - # compare_meas2model=compare_meas2model, - # bpms2use=self.bpms2use, - # ) - - # # save results - # ica_data["source_signals_" + label] = signals - # ica_data["mixing_matrix_" + label] = mixing_matrix - # ica_data["beta" + label] = beta - # ica_data["phase" + label] = phase - # self.ica_data = ica_data + def independent_components_analysis( + self, + n_components=4, + ica_method="SOBI", + calc_optics=True, + **kwargs + ): + r"""Peforms Independent Components Analysis (ICA). + + Calculates beta-functions and betatron phase-advance at the BPMs using + ICA. + + ICA aims to identify the linear transformation (unmixing matrix) + revealing statistically independent source signals. Just as in PCA, + the beatron motion sine and cosine modes can be used to calculate + beta-functions and BPMs phase advances. + + While PCA aims to identify the linear transformation revealing + uncorrelated source signals, ICA seeks the transformation + revealing statistically independent signals, a requirement much + stronger than uncorrelatedness. + + ICA often performs better at blind source separation for linear + mixtures of sinals with non-gaussian distributions, which is relevant + for when several source signals have similar variance. This method + thus is generally more robust at betatron motion identification when + there are contaminating signals, bad acquisitions or similar variance + between horizontal and vertical modes (partticularly relevant when + betatron coupling is significant). + + ICA can be implemented with second-order blind source identification + (SOBI) [1], based on simultaneous diagonalization of the time-shifted + data covariance matrices or with information-theoretic + approaches seeking the maximization of the statistical indpendence of + the estimated source signals [2]. We use the latter, as implemented in + scikit-learn's FastICA [3,4]. + + The variance convention is the same as in PCA analysis: whiten source + signals, with the mixing matrix containing the modes energy/variance + [4]. + + Args: + n_components (int, optional): number of independent components to + decompose the data + + plot (bool, optiional): whether to plot the analysis results. + Defaults to True + + compare_meas2model (bool, optional): whether to plot measured and + nominal beta-functions and BPMs phase-advance, as well as + beta-beting and phase-advance errors or plot only beta-beating and + phase-advance-errors. Defaults to True + + References: + + [1] Huang, X. Beam-Based Correction and Optimization for Accelerators, + Section 5.2.3. CRC Press. 2020. + + [2] A. Hyvärinen, E. Oja. Independent component analysis: algorithms + and applications. Neural Networks. Volume 13, Issues 4-5, 2000, + Pages 411-430, https://doi.org/10.1016/S0893-6080(00)00026-5. + + [3] scikit-learn.decomposition.FastICA documentation. + https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html + + [4] Scikit-learn examples. "Blind Source Separation using FastICA" + https://scikit-learn.org/stable/auto_examples/decomposition/plot_ica_blind_source_separation.html#sphx-glr-auto-examples-decomposition-plot-ica-blind-source-separation-py + """ + ica_data = dict() + + traj = _np.concatenate((self.trajx, self.trajy), axis=1) + + # perform Independent Component Analysis (ICA) + if ica_method.lower() == "fastica": + ica_ = _FastICA + elif ica_method.lower() == "sobi": + ica_ = _SOBI + else: + ica_ = _SOBI + warnings.warn("Unknown ICA method. Using SOBI.") + + ica = ica_(n_components=n_components, **kwargs) + + # collect source signals & mixing matrix + signals = ica.fit_transform(traj) + mixing_matrix = ica.mixing_ + + # determine betatron modes from mixing matrix + # largest variance should be contained in the betatron modes + # 4 modes = 2 modes for hor. & ver. planes, each + idcs = _np.argsort(_np.std(mixing_matrix, axis=0))[-4:] + betatron_temp_modes = signals[:, idcs] + betatron_spatial_modes = mixing_matrix[:, idcs] + + # calulate tunes of the source signals + spec0, tunes0 = self.calc_spectrum(betatron_temp_modes[:, 0], axis=0) + spec1, tunes1 = self.calc_spectrum(betatron_temp_modes[:, 1], axis=0) + spec2, tunes2 = self.calc_spectrum(betatron_temp_modes[:, 2], axis=0) + spec3, tunes3 = self.calc_spectrum(betatron_temp_modes[:, 3], axis=0) + + tune0 = tunes0[_np.argmax(_np.abs(spec0))] + tune1 = tunes1[_np.argmax(_np.abs(spec1))] + tune2 = tunes2[_np.argmax(_np.abs(spec2))] + tune3 = tunes3[_np.argmax(_np.abs(spec3))] + + # identify which signal is which (x or y) + xidcs, yidcs = self.identify_modes_planes( + tunes=[tune0, tune1, tune2, tune3], + tunex=self.tunex, + tuney=self.tuney, + ) + + # extract betatron modes fom mixing matrix + xspatial_modes = betatron_spatial_modes[:, xidcs] + xtemp_modes = betatron_temp_modes[:, xidcs] + + yspatial_modes = betatron_spatial_modes[:, yidcs] + ytemp_modes = betatron_temp_modes[:, yidcs] + + # determine sin and cos modes temporal modes + # sin/cos temporal modes correspond to cos/sin spatial modes + # spatial modes are used for calc. optics + # as in Xiaobiao's book , eqs. 5.31-5.32 (PCA), 5.55 (ICA) + sinx_idx, cosx_idx = self.identify_sin_cos_modes( + sources=xtemp_modes, plane="hor", + ) + siny_idx, cosy_idx = self.identify_sin_cos_modes( + sources=ytemp_modes, plane="ver", + ) + + sin_temp_modex = xtemp_modes[:, sinx_idx] # cos/sin spatial mode + cos_temp_modex = xtemp_modes[:, cosx_idx] # corresponds to + sin_spatial_modex = xspatial_modes[:, cosx_idx] # sin/cos temp. + cos_spatial_modex = xspatial_modes[:, sinx_idx] + + sin_temp_modey = ytemp_modes[:, siny_idx] + cos_temp_modey = ytemp_modes[:, cosy_idx] + sin_spatial_modey = yspatial_modes[:, cosy_idx] + cos_spatial_modey = yspatial_modes[:, siny_idx] + + ica_data["source_signals"] = signals + ica_data["mixing_matrix"] = mixing_matrix + ica_data["betatron_temp_modes"] = betatron_temp_modes + ica_data["betatron_spatial_modes"] = betatron_spatial_modes + ica_data["xidcs"] = xidcs + ica_data["yidcs"] = yidcs + ica_data["sin_spatial_modex"] = sin_spatial_modex + ica_data["cos_spatial_modex"] = cos_spatial_modex + ica_data["sin_temp_modex"] = sin_temp_modex + ica_data["cos_temp_modex"] = cos_temp_modex + ica_data["sin_spatial_modey"] = sin_spatial_modey + ica_data["cos_spatial_modey"] = cos_spatial_modey + ica_data["sin_temp_modey"] = sin_temp_modey + ica_data["cos_temp_modey"] = cos_temp_modey + + if not calc_optics: + self.ica_data = ica_data + return + + if self.model_optics is None: + tunes = self.tunex + 49.0, self.tuney + 14.0 + self._get_nominal_optics(tunes) + + betax_model = self.model_optics["betax"] + betay_model = self.model_optics["betay"] + + # calculate beta function & phase from spatial betatron modes + betax, phasex = self.get_beta_and_phase_from_betatron_modes( + sin_spatial_modex[:160], cos_spatial_modex[:160], betax_model + ) + betay, phasey = self.get_beta_and_phase_from_betatron_modes( + sin_spatial_modey[160:], cos_spatial_modey[160:], betay_model + ) + + # save results + ica_data["betax"] = betax + ica_data["betay"] = betay + ica_data["phasex"] = phasex + ica_data["phasey"] = phasey + # TODO: error bars can be calculated similarly to PCA's case + # however, variance is not explictly available as singular values + # it must be extracted from the spatial modes. + self.ica_data = ica_data def equilibrium_params_analysis(self): """.""" @@ -1427,25 +1463,39 @@ def plot_trajs(self, bpm_index=0, timescale=2, compare_fit=False): _mplt.show() return fig, ax - def plot_modal_analysis(self): + def plot_modal_analysis(self, method="PCA", **kwargs): """.""" if self.pca_data is None: self.principal_components_analysis(calc_optics=False) + if method.lower() == "pca": + data = self.pca_data + u = data["source_signals"] + elif method.lower() == "ica": + if self.ica_data is None: + self.independent_components_analysis( + calc_optics=False, **kwargs + ) + data = self.ica_data + u = data["betatron_temp_modes"] + else: + raise ValueError("Unknown modal analysis method.") s = self.pca_data["singular_values"] - u = self.pca_data["source_signals"] - xidcs = self.pca_data["xidcs"] - yidcs = self.pca_data["yidcs"] + s_xidcs = self.pca_data["xidcs"] + s_yidcs = self.pca_data["xidcs"] + + xidcs = data["xidcs"] + yidcs = data["yidcs"] - sin_temp_modex = self.pca_data["sin_temp_modex"] - sin_temp_modey = self.pca_data["sin_temp_modey"] - cos_temp_modex = self.pca_data["cos_temp_modex"] - cos_temp_modey = self.pca_data["cos_temp_modey"] + sin_temp_modex = data["sin_temp_modex"] + sin_temp_modey = data["sin_temp_modey"] + cos_temp_modex = data["cos_temp_modex"] + cos_temp_modey = data["cos_temp_modey"] - sin_spatial_modex = self.pca_data["sin_spatial_modex"] - sin_spatial_modey = self.pca_data["sin_spatial_modey"] - cos_spatial_modex = self.pca_data["cos_spatial_modex"] - cos_spatial_modey = self.pca_data["cos_spatial_modey"] + sin_spatial_modex = data["sin_spatial_modex"] + sin_spatial_modey = data["sin_spatial_modey"] + cos_spatial_modex = data["cos_spatial_modex"] + cos_spatial_modey = data["cos_spatial_modey"] fig = _mplt.figure(figsize=(14, 12)) @@ -1465,12 +1515,12 @@ def plot_modal_analysis(self): svals.plot(s, "o", color="k", mfc="none") idx = _np.arange(len(s)) - sel = _np.array(xidcs) + sel = _np.array(s_xidcs) svals.plot( idx[sel], s[sel], "o", color="b", mfc="none", label="hor. signals singular values" ) - sel = _np.array(yidcs) + sel = _np.array(s_yidcs) svals.plot( idx[sel], s[sel], "o", color="r", mfc="none", label='ver. signals singular values') From d86bafb79239fb5bedbc7a8906c133d3085d5d51 Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 27 Jun 2025 13:54:01 -0300 Subject: [PATCH 139/144] COMMISS.TBT.ANLY.BUG: minor fixes --- apsuite/commisslib/measure_tbt_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 7f7f950c..4b0d992a 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1113,7 +1113,7 @@ def identify_sin_cos_modes(self, sources=None, plane="hor"): init, fin = self.trajx_turns_slice tune = self.tunex else: - init, fin = self.trajx_turns_slice + init, fin = self.trajy_turns_slice tune = self.tuney n = _np.arange(fin-init) @@ -1482,7 +1482,7 @@ def plot_modal_analysis(self, method="PCA", **kwargs): s = self.pca_data["singular_values"] s_xidcs = self.pca_data["xidcs"] - s_yidcs = self.pca_data["xidcs"] + s_yidcs = self.pca_data["yidcs"] xidcs = data["xidcs"] yidcs = data["yidcs"] From 85ebb23a3338f6a442668c7279df586246b1c3fd Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 Aug 2025 14:57:35 -0300 Subject: [PATCH 140/144] TBT.MNT: minor changes - comment import from unfinished work in mathphys - dummy docstrings to properties to remove ruff's complaints --- apsuite/commisslib/measure_tbt_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 4b0d992a..6cc43f8e 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -8,7 +8,7 @@ import matplotlib.pyplot as _mplt import numpy as _np import pyaccel as _pa -from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys +# from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys from pymodels import si as _si from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU as _PowerSupplyPU, \ @@ -126,10 +126,12 @@ def __init__(self, isonline=False): @property def init_timing_state(self): + """.""" return self._init_timing_state @property def init_magnets_state(self): + """.""" return self._init_magnets_state def create_devices(self): From f4e40770c1bfebe83d7764aee6ada2fe67ec2454 Mon Sep 17 00:00:00 2001 From: Matheus Date: Thu, 14 Aug 2025 10:58:42 -0300 Subject: [PATCH 141/144] TBT.ANLY.ENH: handle possibly missing dict keys --- apsuite/commisslib/measure_tbt_data.py | 58 +++++++++++++------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 6cc43f8e..ee4386c3 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -742,34 +742,36 @@ def linear_optics_analysis( self.independent_components_analysis(calc_optics=True, **kwargs) data = self.ica_data - betax = data["betax"] - betay = data["betay"] - phasex = data["phasex"] - phasey = data["phasey"] - - betax_model = self.model_optics["betax"] - betay_model = self.model_optics["betay"] - phasex_model = self.model_optics["phasex"] - phasey_model = self.model_optics["phasey"] - - self.plot_betabeat_and_phase_error( - betax_model, - betax, - phasex_model, - phasex, - title=f"{method} Optics Analysis: betax & phasex", - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) - self.plot_betabeat_and_phase_error( - betay_model, - betay, - phasey_model, - phasey, - title=f"{method} Optics Analysis: betay & phasey", - compare_meas2model=compare_meas2model, - bpms2use=self.bpms2use, - ) + betay = data.get("betay", None) + betax = data.get("betax", None) + phasex = data.get("phasex", None) + phasey = data.get("phasey", None) + + betax_model = self.model_optics.get("betax", None) + betay_model = self.model_optics.get("betay", None) + phasex_model = self.model_optics.get("phasex", None) + phasey_model = self.model_optics.get("phasey", None) + + if betax is not None: + self.plot_betabeat_and_phase_error( + betax_model, + betax, + phasex_model, + phasex, + title=f"{method} Optics Analysis: betax & phasex", + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use, + ) + if betay is not None: + self.plot_betabeat_and_phase_error( + betay_model, + betay, + phasey_model, + phasey, + title=f"{method} Optics Analysis: betay & phasey", + compare_meas2model=compare_meas2model, + bpms2use=self.bpms2use, + ) def harmonic_analysis(self, guess_tunes=True): r"""Linear optics analysis using sinusoidal model for TbT data. From 1dba7821a9c690dec323dc3641a8893e957b0026 Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 18 Aug 2025 15:01:41 -0300 Subject: [PATCH 142/144] COMMISS.TBT.ANLY.ENH: some standardization - standardize how hormonic analysis is called (include calc_optitcs arg) - remove SOBI (unfinished work) as ICA method --- apsuite/commisslib/measure_tbt_data.py | 53 ++++++++++++-------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index ee4386c3..485b130d 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -8,7 +8,6 @@ import matplotlib.pyplot as _mplt import numpy as _np import pyaccel as _pa -# from mathphys.sobi import SOBI as _SOBI # unfinished work in mathphys from pymodels import si as _si from scipy.optimize import curve_fit as _curve_fit from siriuspy.devices import PowerSupplyPU as _PowerSupplyPU, \ @@ -724,7 +723,7 @@ def load_and_apply(self, fname): def linear_optics_analysis( self, method="PCA", compare_meas2model=True, **kwargs - ): # TODO: Missing kwargs for each method + ): """Linear optics (beta-beating & phase adv. errors) analysis. Determines beta-functions and phase-advances on BPMs via sinusoidal @@ -733,7 +732,8 @@ def linear_optics_analysis( (PCA) and Independent Components Analysis (ICA). """ if method.lower() == "fitting": - self.harmonic_analysis() + # TODO: avoid recalculating if already available + self.harmonic_analysis(calc_optics=True, **kwargs) data = self.fitting_data elif method.lower() == "pca": self.principal_components_analysis(calc_optics=True) @@ -773,7 +773,7 @@ def linear_optics_analysis( bpms2use=self.bpms2use, ) - def harmonic_analysis(self, guess_tunes=True): + def harmonic_analysis(self, calc_optics=True, guess_tunes=False): r"""Linear optics analysis using sinusoidal model for TbT data. TbT motion at the i-th turn and j-th BPM in the timescale of less @@ -866,31 +866,34 @@ def harmonic_analysis(self, guess_tunes=True): tune = params_fit[0] amps = _np.array(params_fit[1 : nbpms + 1]) phases_fit = _np.array(params_fit[-nbpms:]) + residue = traj - final_fit + + # store fitting data + fitting_data["tune" + label] = tune + fitting_data["amp" + label] = amps + fitting_data["phase" + label] = phases_fit + fitting_data["tune_err" + label] = params_error[0] + fitting_data["amp" + label + "_err"] = params_error[1 : nbpms + 1] + fitting_data["phase" + label + "_err"] = params_error[-nbpms:] + fitting_data["traj" + label + "_init_fit"] = initial_fit + fitting_data["traj" + label + "_final_fit"] = final_fit + fitting_data["fitting" + label + "_residue"] = residue + + if not calc_optics: + self.fitting_data = fitting_data + return if self.model_optics is None: self._get_nominal_optics(tunes=(tunex + 49.0, tuney + 14.0)) + beta_model = self.model_optics["beta" + label] # fit beta-function & action from fitted amplitudes & nominal beta beta_fit, action = self.calc_beta_and_action(amps, beta_model) - - # evaluate fitting residues - residue = traj - final_fit - - # store fitting data - fitting_data["tune" + label] = tune - fitting_data["tune_err" + label] = params_error[0] - fitting_data["amplitudes" + label] = amps fitting_data["beta" + label] = beta_fit # TODO: propagate amplitude errors to beta errors - fitting_data["beta" + label + "_err"] = params_error[1 : nbpms + 1] - fitting_data["phase" + label] = phases_fit - fitting_data["phase" + label + "_err"] = params_error[-nbpms:] fitting_data["action" + label] = action # TODO: propagate amplitude errors to action error - fitting_data["traj" + label + "_init_fit"] = initial_fit - fitting_data["traj" + label + "_final_fit"] = final_fit - fitting_data["fitting" + label + "_residue"] = residue self.fitting_data = fitting_data @@ -1133,7 +1136,6 @@ def identify_sin_cos_modes(self, sources=None, plane="hor"): def independent_components_analysis( self, n_components=4, - ica_method="SOBI", calc_optics=True, **kwargs ): @@ -1202,16 +1204,9 @@ def independent_components_analysis( traj = _np.concatenate((self.trajx, self.trajy), axis=1) - # perform Independent Component Analysis (ICA) - if ica_method.lower() == "fastica": - ica_ = _FastICA - elif ica_method.lower() == "sobi": - ica_ = _SOBI - else: - ica_ = _SOBI - warnings.warn("Unknown ICA method. Using SOBI.") - - ica = ica_(n_components=n_components, **kwargs) + ica = _FastICA( + n_components=n_components, **kwargs + ) # collect source signals & mixing matrix signals = ica.fit_transform(traj) From b1af1e4b3e5292c8e2e886a5650cff1965d1d4da Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 18 Aug 2025 15:16:13 -0300 Subject: [PATCH 143/144] COMMISS.TBT..ANLY.ENH: show phase error in plots - instead of relative phase error --- apsuite/commisslib/measure_tbt_data.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 485b130d..53a933dd 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -1701,7 +1701,7 @@ def plot_betabeat_and_phase_error( ax_beat.plot( beta_beat, "o-", - label=f"rms = {beta_beat[~_np.isnan(beta_beat)].std():.2f} %", + label=f"rms beat = {_np.nanstd(beta_beat):.2f} %", mfc="none", ) ax_beat.set_ylabel("beta beating [%]") @@ -1720,16 +1720,14 @@ def plot_betabeat_and_phase_error( # Phase advance error plot ax_phase_err = axs[1, 1] if compare_meas2model else axs[1] - rph_err = model_phase_advance - meas_phase_advance - rph_err /= model_phase_advance - rph_err *= 100 # [%] + ph_err = model_phase_advance - meas_phase_advance ax_phase_err.plot( - rph_err, + ph_err, "o-", - label=f"rms.err={rph_err[~_np.isnan(rph_err)].std():.2f}", + label=f"rms err={_np.nanstd(ph_err):.2f}", mfc="none", ) - ax_phase_err.set_ylabel("BPMs fractional phase advance error [rad]") + ax_phase_err.set_ylabel("BPMs phase advance error [rad]") ax_phase_err.legend() fig.supxlabel("BPM index") From 5bf04616cc25ecd45dc151970a769bc373f3931b Mon Sep 17 00:00:00 2001 From: Matheus Date: Mon, 25 Aug 2025 17:07:52 -0300 Subject: [PATCH 144/144] COMMISS.TBT.ANLY.MNT: plot relevent planes only - plot only the relevant plane info (hor/ver) in `linear_optics_analysis` - change ylabel in sum singal plots --- apsuite/commisslib/measure_tbt_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apsuite/commisslib/measure_tbt_data.py b/apsuite/commisslib/measure_tbt_data.py index 53a933dd..81fadc71 100644 --- a/apsuite/commisslib/measure_tbt_data.py +++ b/apsuite/commisslib/measure_tbt_data.py @@ -752,7 +752,7 @@ def linear_optics_analysis( phasex_model = self.model_optics.get("phasex", None) phasey_model = self.model_optics.get("phasey", None) - if betax is not None: + if betax is not None and "h" in self.params.pingers2kick: self.plot_betabeat_and_phase_error( betax_model, betax, @@ -762,7 +762,7 @@ def linear_optics_analysis( compare_meas2model=compare_meas2model, bpms2use=self.bpms2use, ) - if betay is not None: + if betay is not None and "v" in self.params.pingers2kick: self.plot_betabeat_and_phase_error( betay_model, betay, @@ -1455,7 +1455,7 @@ def plot_trajs(self, bpm_index=0, timescale=2, compare_fit=False): ax[2].plot(trajsum, "-", mfc="none", color="k") ax[2].set_xlim(slicesum) - ax[2].set_ylabel("sum [a.u.]") + ax[2].set_ylabel("sum [counts]") fig.supxlabel("turn index") fig.tight_layout()