diff --git a/siriuspy/siriuspy/currinfo/csdev.py b/siriuspy/siriuspy/currinfo/csdev.py index 47c34ab5a7..7d0579ec27 100644 --- a/siriuspy/siriuspy/currinfo/csdev.py +++ b/siriuspy/siriuspy/currinfo/csdev.py @@ -14,6 +14,7 @@ class ETypes(_csdev.ETypes): DCCTSELECTIONTYP = ('DCCT13C4', 'DCCT14C4') BUFFAUTORSTTYP = ('Off', 'DCurrCheck') FITTYP = ('Exponential', 'Linear') + LTCALCSTS = ('CalcOk', 'ErrAboveThres') _et = ETypes # syntactic sugar @@ -28,6 +29,7 @@ class Const(_csdev.Const): DCCTFltCheck = _csdev.Const.register('DCCTFltCheck', _et.OFF_ON) BuffAutoRst = _csdev.Const.register('BuffAutoRst', _et.BUFFAUTORSTTYP) Fit = _csdev.Const.register('Fit', _et.FITTYP) + LtCalcSts = _csdev.Const.register('LtCalcSts', _et.LTCALCSTS) _c = Const # syntactic sugar @@ -189,6 +191,15 @@ def get_lifetime_database(): 'type': 'float', 'value': 0.0, 'prec': 3, 'unit': 'mA', 'lolim': -10.0, 'low': -10.0, 'lolo': -10.0, 'hilim': 10.0, 'high': 10.0, 'hihi': 10.0}, + 'LtRelErrThres-SP': { + 'type': 'float', 'value': 30.0, 'prec': 1, 'unit': '%', + 'lolim': 0.0, 'hilim': 100.0}, + 'LtRelErrThres-RB': { + 'type': 'float', 'value': 30.0, 'prec': 1, 'unit': '%', + 'lolim': 0.0, 'hilim': 100.0}, + 'LtCalcStatus-Mon': { + 'type': 'enum', 'enums': _et.LTCALCSTS, + 'value': _c.LtCalcSts.CalcOk}, 'BuffRst-Cmd': {'type': 'int', 'value': 0}, 'BuffAutoRst-Sel': { @@ -198,11 +209,11 @@ def get_lifetime_database(): 'type': 'enum', 'enums': _et.BUFFAUTORSTTYP, 'value': _c.BuffAutoRst.DCurrCheck}, 'BuffAutoRstDCurr-SP': { - 'type': 'float', 'value': 0.01, 'prec': 2, 'unit': 'mA', + 'type': 'float', 'value': 0.005, 'prec': 3, 'unit': 'mA', 'lolim': -300.0, 'low': -300.0, 'lolo': -300.0, 'hilim': 300.0, 'high': 300.0, 'hihi': 300.0}, 'BuffAutoRstDCurr-RB': { - 'type': 'float', 'value': 0.01, 'prec': 2, 'unit': 'mA', + 'type': 'float', 'value': 0.005, 'prec': 3, 'unit': 'mA', 'lolim': -300.0, 'low': -300.0, 'lolo': -300.0, 'hilim': 300.0, 'high': 300.0, 'hihi': 300.0}, @@ -226,8 +237,11 @@ def get_lifetime_database(): 'lolim': -1.0, 'hilim': 2e10}, 'Lifetime-Mon': { - 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's', - 'scan': 0.5}, + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, + 'LifetimeRaw-Mon': { + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, + 'LifetimeErr-Mon': { + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, 'BuffSize-Mon': {'type': 'int', 'value': 0}, 'BuffSizeTot-Mon': {'type': 'int', 'value': 0}, 'BufferValue-Mon': { @@ -238,8 +252,11 @@ def get_lifetime_database(): 'value': [0.0, ] * 100000}, 'LifetimeBPM-Mon': { - 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's', - 'scan': 0.5}, + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, + 'LifetimeRawBPM-Mon': { + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, + 'LifetimeErrBPM-Mon': { + 'type': 'float', 'value': 0.0, 'prec': 2, 'unit': 's'}, 'BuffSizeBPM-Mon': {'type': 'int', 'value': 0}, 'BuffSizeTotBPM-Mon': {'type': 'int', 'value': 0}, 'BufferValueBPM-Mon': { diff --git a/siriuspy/siriuspy/currinfo/lifetime/check_fiterr.py b/siriuspy/siriuspy/currinfo/lifetime/check_fiterr.py new file mode 100644 index 0000000000..e9295e1c26 --- /dev/null +++ b/siriuspy/siriuspy/currinfo/lifetime/check_fiterr.py @@ -0,0 +1,159 @@ +import numpy as np +import matplotlib.pyplot as mplt +from siriuspy.clientarch import PVDataSet, Time + + +def fit_pol(x, y, deg=1, polyfit=False): + # handle big timestamps + x = x - x[-1] + # considering y = b + ax + if polyfit: + (a, b), cov = np.polyfit(x, y, deg=deg, cov=True) + db_sqr, da_sqr = np.diag(cov) + else: + (b, a), details = np.polynomial.polynomial.polyfit(x, y, deg=deg, full=True) + + residues = details[0] + + lhs = np.polynomial.polynomial.polyvander(x, order-1) + scale = np.sqrt((lhs*lhs).sum(axis=0)) + + lhs /= scale + lhs_inv = np.linalg.inv(np.dot(lhs.T, lhs)) + lhs_inv /= np.outer(scale, scale) + + cov = lhs_inv * (residues/(lhs.shape[0] - order)) + + da_sqr, db_sqr = np.diag(cov) + + lt = - b / a + dlt_sqr_rel = da_sqr/a/a + db_sqr/b/b + dlt = np.sqrt(dlt_sqr_rel)*np.abs(lt) + + return a, np.sqrt(da_sqr), b, np.sqrt(db_sqr), lt, dlt + + +# datasets +dataset = { + 0: { + 'title': '2023-01-23, 6-8h, N~4000', + 't_start': Time(2023, 1, 23, 6, 0, 0), + 't_stop': Time(2023, 1, 23, 8, 0, 0), + 'nrpts_fit': 4000, + 'lt_ylim': [0, 100], + 'err_ylim': [0, 1.0], + }, + 1: { + 'title': '2023-01-23, 10-11h', + 't_start': Time(2023, 1, 23, 10, 0, 0), + 't_stop': Time(2023, 1, 23, 11, 0, 0), + 'nrpts_fit': 200, + 'lt_ylim': [0, 100], + 'err_ylim': [0, 1.0], + }, + 2: { + 'title': '2023-01-23, 12h30-12h35', + 't_start': Time(2023, 1, 23, 12, 30, 0), + 't_stop': Time(2023, 1, 23, 12, 35, 0), + 'nrpts_fit': 200, + 'lt_ylim': [0, 200], + 'err_ylim': [-100.0, 100.0], + }, + 3: { + 'title': '2023-01-23, 18h35-19h25', + 't_start': Time(2023, 1, 23, 18, 35, 0), + 't_stop': Time(2023, 1, 23, 19, 25, 0), + 'nrpts_fit': 4000, + 'lt_ylim': [0, 100], + 'err_ylim': [0, 1.0], + }, + 4: { + 'title': '2023-02-08 8h15-20h', + 't_start': Time(2023, 2, 8, 8, 15, 0), + 't_stop': Time(2023, 2, 8, 20, 0, 0), + 'nrpts_fit': 500, + 'lt_ylim': [0, 100], + 'err_ylim': [0, 1.0], + }, + 5: { + 'title': '2023-02-14 16h-16h30', + 't_start': Time(2023, 2, 14, 16, 0, 0), + 't_stop': Time(2023, 2, 14, 16, 30, 0), + 'nrpts_fit': 1000, + 'lt_ylim': [0, 100], + 'err_ylim': [0, 1.0], + }, +} + + +pvnames = [ + 'SI-Glob:AP-CurrInfo:Lifetime-Mon', + 'SI-Glob:AP-CurrInfo:Current-Mon' +] +pvds = PVDataSet(pvnames) +pvds.timeout = 30 + +# for setid in dataset: +setid = 0 + +# get archiver data +pvds.time_start = dataset[setid]['t_start'] +pvds.time_stop = dataset[setid]['t_stop'] +pvds.update() + +ycurr = np.asarray(pvds['SI-Glob:AP-CurrInfo:Current-Mon'].value) +dcurr = np.hstack([0, np.diff(ycurr)]) +xcurr = np.asarray(pvds['SI-Glob:AP-CurrInfo:Current-Mon'].timestamp) +xcudt = [Time(t) for t in xcurr] +yltim = np.asarray(pvds['SI-Glob:AP-CurrInfo:Lifetime-Mon'].value) +xltim = np.asarray(pvds['SI-Glob:AP-CurrInfo:Lifetime-Mon'].timestamp) +xltdt = [Time(t) for t in xltim] +thres = 0.01 + +# fit lifetime and errors +lenb = dataset[setid]['nrpts_fit'] # [#] +order = 2 +ltfit, lterr = [], [] +for i in range(lenb, len(ycurr)): + slc = slice(i-lenb, i) + idx = np.where(dcurr[slc] > thres)[0] + if idx.size: + slc = slice(idx[-1], i) + data = fit_pol(xcurr[slc], ycurr[slc], deg=order-1, polyfit=True) + ltfit.append(data[4]) + lterr.append(data[5]/data[4]) +ltfit, lterr = np.asarray(ltfit), np.asarray(lterr) + +# plot +fig, ax = mplt.subplots() +fig.subplots_adjust(right=0.75) +tkw = dict(size=4, width=1.5) + +ax.set_title(dataset[setid]['title'] + ', N~' + str(lenb)) +ax.set_xlabel('Time [s]') +ax.tick_params(axis='x', **tkw) +ax.xaxis.axis_date() + +ax.set_ylabel('Current [mA]') +p, = ax.plot(xcudt, ycurr, 'b', label='Current') +ax.tick_params(axis='y', colors=p.get_color(), **tkw) +ax.yaxis.label.set_color(p.get_color()) + +axlt = ax.twinx() +axlt.set_ylabel('Lifetime [h]') +po, = axlt.plot(xltdt, yltim/60/60, 'g', label='Lifetime Orig') +pf, = axlt.plot(xcudt[lenb:], ltfit/60/60, 'r', label='Lifetime Fit') +axlt.yaxis.label.set_color(po.get_color()) +axlt.tick_params(axis='y', colors=po.get_color(), **tkw) +axlt.set_ylim(dataset[setid]['lt_ylim']) + +axerr = ax.twinx() +axerr.set_ylabel('Lifetime Fit Rel. Error') +axerr.spines['right'].set_position(("axes", 1.2)) +p, = axerr.plot(xcudt[lenb:], lterr, 'k', label='Lifetime Fit Error') +axerr.yaxis.label.set_color(p.get_color()) +axerr.tick_params(axis='y', colors=p.get_color(), **tkw) +axerr.set_ylim(dataset[setid]['err_ylim']) + +fig.legend() +fig.show() diff --git a/siriuspy/siriuspy/currinfo/lifetime/main.py b/siriuspy/siriuspy/currinfo/lifetime/main.py index 9821d459f9..52ee827f84 100644 --- a/siriuspy/siriuspy/currinfo/lifetime/main.py +++ b/siriuspy/siriuspy/currinfo/lifetime/main.py @@ -4,6 +4,7 @@ import time as _time import numpy as _np from epics import PV as _PV +from epics.ca import CAThread as _Thread from ...callbacks import Callback as _Callback from ...epics import SiriusPVTimeSerie as _SiriusPVTimeSerie @@ -15,7 +16,7 @@ class SILifetimeApp(_Callback): - """Main Class of the IOC Logic.""" + """SI Lifetime App.""" def __init__(self): """Class constructor.""" @@ -65,6 +66,9 @@ def __init__(self): self._bpmsum_pv.add_callback(self._callback_calclifetime) self._storedebeam_pv.add_callback(self._callback_get_storedebeam) + self._thread = _Thread(target=self._update_lifetime, daemon=True) + self._thread.start() + @property def pvs_database(self): """Return pvs database.""" @@ -86,52 +90,6 @@ def process(self, interval): def read(self, reason): """Read from IOC database.""" value = None - if reason in ['Lifetime-Mon', 'LifetimeBPM-Mon']: - is_bpm = 'BPM' in reason - lt_type = 'BPM' if is_bpm else '' - lt_name = '_lifetime'+('_bpm' if is_bpm else '') - buffer_dt = self._bpmsum_buffer if is_bpm else self._current_buffer - - # get first and last sample - now = _time.time() - self._update_times(now) - first_name = '_frst_smpl_ts'+('_bpm' if is_bpm else '_dcct') - first_smpl = getattr(self, first_name) - last_name = '_last_smpl_ts'+('_bpm' if is_bpm else '_dcct') - last_smpl = getattr(self, last_name) - last_smpl = now if last_smpl == -1 else min(last_smpl, now) - intvl_name = '_smpl_intvl_mon'+('_bpm' if is_bpm else '_dcct') - intvl_smpl = getattr(self, intvl_name) - - # calculate lifetime - ts_abs_dqorg, val_dqorg = buffer_dt.get_serie(time_absolute=True) - ts_dqorg = ts_abs_dqorg - now - ts_dq, val_dq, ts_abs_dq = self._filter_buffer( - ts_dqorg, val_dqorg, ts_abs_dqorg, first_smpl, last_smpl) - - if ts_dq.size != 0: - if first_smpl != ts_abs_dq[0]: - setattr(self, first_name, ts_abs_dq[0]) - self.run_callbacks( - 'FrstSplTime'+lt_type+'-RB', getattr(self, first_name)) - intvl_smpl = last_smpl - first_smpl - setattr(self, intvl_name, intvl_smpl) - self.run_callbacks( - 'SplIntvl'+lt_type+'-Mon', getattr(self, intvl_name)) - - val_dq -= self._current_offset - - # check min number of points in buffer - if len(val_dq) > 100: - fit = 'lin' if self._mode == _Const.Fit.Linear else 'exp' - value = self._least_squares_fit(ts_dq, val_dq, fit=fit) - setattr(self, lt_name, value) - - # update pvs - self.run_callbacks('BufferValue'+lt_type+'-Mon', val_dq) - self.run_callbacks('BufferTimestamp'+lt_type+'-Mon', ts_dq) - self.run_callbacks('BuffSize'+lt_type+'-Mon', len(val_dq)) - self.run_callbacks('BuffSizeTot'+lt_type+'-Mon', len(val_dqorg)) return value def write(self, reason, value): @@ -193,10 +151,14 @@ def write(self, reason, value): def _callback_get_storedebeam(self, value, **kws): self._is_stored = value - def _callback_calclifetime(self, pvname, timestamp, **kws): - # check DCCT StoredEBeam PV + def _callback_calclifetime(self, pvname, **kws): + # if there is no beam stored, set lifetime to zero if not self._is_stored: - self._buffautorst_check() + if self._lifetime != 0: + self._lifetime, self._lifetime_bpm = 0, 0 + self.run_callbacks('Lifetime-Mon', self._lifetime) + self.run_callbacks('LifetimeBPM-Mon', self._lifetime_bpm) + self._reset_buff() return is_bpm = 'BPM' in pvname @@ -287,23 +249,18 @@ def _least_squares_fit(timestamp, value, fit='exp'): value = _np.log(value) except Warning: return 0.0 - _ns = len(timestamp) - _x1 = _np.sum(timestamp) - _y1 = _np.sum(value) - if timestamp.size > 10000: - _x2 = _np.sum(timestamp*timestamp) - _xy = _np.sum(timestamp*value) - else: - _x2 = _np.dot(timestamp, timestamp) - _xy = _np.dot(timestamp, value) - fit_a = (_x2*_y1 - _xy*_x1)/(_ns*_x2 - _x1*_x1) - fit_b = (_ns*_xy - _x1*_y1)/(_ns*_x2 - _x1*_x1) + # y = a + bx + (fit_b, fit_a), cov = _np.polyfit(timestamp, value, deg=1, cov=True) + da_sqr, db_sqr = _np.diag(cov) if fit == 'exp': lifetime = - 1/fit_b else: lifetime = - fit_a/fit_b - return lifetime + + dlt_sqr_rel = db_sqr/fit_b/fit_b + da_sqr/fit_a/fit_a + dlifetime = _np.sqrt(dlt_sqr_rel)*_np.abs(lifetime) + return lifetime, dlifetime/lifetime def _update_times(self, now, force_min_first=False): if self._last_ts_set == 'first': @@ -325,3 +282,55 @@ def _update_times(self, now, force_min_first=False): self.run_callbacks('FrstSplTimeBPM-RB', self._frst_smpl_ts_bpm) self.run_callbacks('LastSplTime-RB', self._last_smpl_ts_dcct) self.run_callbacks('LastSplTimeBPM-RB', self._last_smpl_ts_bpm) + + def _update_lifetime(self): + for reason in ['Lifetime-Mon', 'LifetimeBPM-Mon']: + is_bpm = 'BPM' in reason + lt_type = 'BPM' if is_bpm else '' + lt_name = '_lifetime'+('_bpm' if is_bpm else '') + buffer_dt = self._bpmsum_buffer if is_bpm else self._current_buffer + + # get first and last sample + now = _time.time() + self._update_times(now) + first_name = '_frst_smpl_ts'+('_bpm' if is_bpm else '_dcct') + first_smpl = getattr(self, first_name) + last_name = '_last_smpl_ts'+('_bpm' if is_bpm else '_dcct') + last_smpl = getattr(self, last_name) + last_smpl = now if last_smpl == -1 else min(last_smpl, now) + intvl_name = '_smpl_intvl_mon'+('_bpm' if is_bpm else '_dcct') + intvl_smpl = getattr(self, intvl_name) + + # calculate lifetime + ts_abs_dqorg, val_dqorg = buffer_dt.get_serie(time_absolute=True) + ts_dqorg = ts_abs_dqorg - now + ts_dq, val_dq, ts_abs_dq = self._filter_buffer( + ts_dqorg, val_dqorg, ts_abs_dqorg, first_smpl, last_smpl) + + if ts_dq.size != 0: + if first_smpl != ts_abs_dq[0]: + setattr(self, first_name, ts_abs_dq[0]) + self.run_callbacks( + 'FrstSplTime'+lt_type+'-RB', getattr(self, first_name)) + intvl_smpl = last_smpl - first_smpl + setattr(self, intvl_name, intvl_smpl) + self.run_callbacks( + 'SplIntvl'+lt_type+'-Mon', getattr(self, intvl_name)) + + val_dq -= self._current_offset + + # check min number of points in buffer + if len(val_dq) > 100: + fit = 'lin' if self._mode == _Const.Fit.Linear else 'exp' + value, fit_err = self._least_squares_fit( + ts_dq, val_dq, fit=fit) + if fit_err < _Const.ERR_THRES_LIFETIME: + setattr(self, lt_name, value) + else: + value = getattr(self, lt_name) + + # update pvs + self.run_callbacks('BufferValue'+lt_type+'-Mon', val_dq) + self.run_callbacks('BufferTimestamp'+lt_type+'-Mon', ts_dq) + self.run_callbacks('BuffSize'+lt_type+'-Mon', len(val_dq)) + self.run_callbacks('BuffSizeTot'+lt_type+'-Mon', len(val_dqorg)) diff --git a/siriuspy/siriuspy/cycle/fc_cycle_data.py b/siriuspy/siriuspy/cycle/fc_cycle_data.py index 28a5d469fc..ffbe357812 100644 --- a/siriuspy/siriuspy/cycle/fc_cycle_data.py +++ b/siriuspy/siriuspy/cycle/fc_cycle_data.py @@ -165,6 +165,24 @@ 'SI-20C2:PS-FCV': [0.5, 0.95, 24, 8, 48, False], 'SI-20C3:PS-FCH': [0.5, 0.95, 24, 8, 48, False], 'SI-20C3:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + + 'SI-22M1:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-22M1:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-22M2:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-22M2:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-22C2:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-22C2:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-22C3:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-22C3:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + + 'SI-99M1:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-99M1:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-99M2:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-99M2:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-99C2:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-99C2:PS-FCV': [0.5, 0.95, 24, 8, 48, False], + 'SI-99C3:PS-FCH': [0.5, 0.95, 24, 8, 48, False], + 'SI-99C3:PS-FCV': [0.5, 0.95, 24, 8, 48, False], } diff --git a/siriuspy/siriuspy/devices/device.py b/siriuspy/siriuspy/devices/device.py index ce26472265..9aa9bd69e4 100644 --- a/siriuspy/siriuspy/devices/device.py +++ b/siriuspy/siriuspy/devices/device.py @@ -18,6 +18,7 @@ _DEF_TIMEOUT = 10 # s _TINY_INTERVAL = 0.050 # s +_VACA_PREFIX = '' class Device: diff --git a/siriuspy/siriuspy/diagsys/psdiag/pvs.py b/siriuspy/siriuspy/diagsys/psdiag/pvs.py index d1323eda3b..eb21b28bcc 100644 --- a/siriuspy/siriuspy/diagsys/psdiag/pvs.py +++ b/siriuspy/siriuspy/diagsys/psdiag/pvs.py @@ -62,7 +62,7 @@ class PSStatusPV: OPMODE_SEL = 2 OPMODE_STS = 3 WAVFRM_MON = 4 - TRIGEN_STS = 4 + TRIGEN_STS = 5 DTOLWFM_DICT = dict() diff --git a/siriuspy/siriuspy/epics/__init__.py b/siriuspy/siriuspy/epics/__init__.py index a409cd495d..6cf5c794b1 100644 --- a/siriuspy/siriuspy/epics/__init__.py +++ b/siriuspy/siriuspy/epics/__init__.py @@ -2,7 +2,7 @@ # the following parameter is used to establish connections with IOC PVs. CONNECTION_TIMEOUT = 0.050 # [s] -GET_TIMEOUT = 5.0 # [s] +GET_TIMEOUT = 0.1 # [s] from .pv import PV from .pv_time_serie import * diff --git a/siriuspy/siriuspy/fofb/main.py b/siriuspy/siriuspy/fofb/main.py index cfd91cc138..3438572dda 100644 --- a/siriuspy/siriuspy/fofb/main.py +++ b/siriuspy/siriuspy/fofb/main.py @@ -173,12 +173,12 @@ def __init__(self, tests=False): # configuration scanning self.quit = False self.scanning = False - self.thread_check_corrs_configs = _epics.ca.CAThread( - target=self._check_corrs_configs, daemon=True) - self.thread_check_corrs_configs.start() - self.thread_check_ctrls_configs = _epics.ca.CAThread( - target=self._check_ctrls_configs, daemon=True) - self.thread_check_ctrls_configs.start() + # self.thread_check_corrs_configs = _epics.ca.CAThread( + # target=self._check_corrs_configs, daemon=True) + # self.thread_check_corrs_configs.start() + # self.thread_check_ctrls_configs = _epics.ca.CAThread( + # target=self._check_ctrls_configs, daemon=True) + # self.thread_check_ctrls_configs.start() def init_database(self): """Set initial PV values.""" @@ -1103,7 +1103,8 @@ def set_enbllist(self, device, value): def _handle_devices_enblconfig(self, device): if device in ['ch', 'cv']: if self._check_corr_connection(): - self._check_set_corrs_opmode() + pass + # self._check_set_corrs_opmode() elif device in ['bpmx', 'bpmy']: self._update_fofbctrl_sync_enbllist() if self._check_fofbctrl_connection(): @@ -1255,7 +1256,8 @@ def _calc_matrices(self): # convert matrix to hardware units str2curr = _np.r_[self._corrs_dev.strength_2_current_factor, 1.0] - currgain = _np.r_[self._corrs_dev.curr_gain, 1.0] + # currgain = _np.r_[self._corrs_dev.curr_gain, 1.0] + currgain = _np.r_[6.25e-5*_np.ones(160), 1.0] if _np.any(str2curr == 0) or _np.any(currgain == 0): self._update_log('ERR:Could not calculate hardware unit') self._update_log('ERR:matrix, CurrGain or "urad to A" ') @@ -1310,7 +1312,8 @@ def _calc_matrices(self): # send new matrix to low level FOFB self._calc_corrs_coeffs() if self._init: - self._set_corrs_coeffs() + # self._set_corrs_coeffs() + pass self._update_log('Ok!') return True diff --git a/siriuspy/siriuspy/fofb/test_svd.py b/siriuspy/siriuspy/fofb/test_svd.py new file mode 100644 index 0000000000..8b1b7b0b6b --- /dev/null +++ b/siriuspy/siriuspy/fofb/test_svd.py @@ -0,0 +1,117 @@ + +import numpy as np + +from siriuspy.clientconfigdb import ConfigDBClient +from siriuspy.devices import FamFastCorrs +from siriuspy.fofb.csdev import HLFOFBConst + +# create FOFB contants +fofbconst = HLFOFBConst() + +# get matrix from configdb +client = ConfigDBClient() +ref = client.get_config_value(name='ref_orb', config_type='si_orbit') +respm = client.get_config_value( + name='ref_respmat', config_type='si_fastorbcorr_respm') +respm = np.asarray(respm) + +# connect to correctors and get convertion constants +corrs_dev = FamFastCorrs() + +# determine enable lists +# select all +bpmxenbl = np.ones(fofbconst.nr_bpms, dtype=bool) +bpmyenbl = np.ones(fofbconst.nr_bpms, dtype=bool) +chenbl = np.ones(fofbconst.nr_ch, dtype=bool) +cvenbl = np.ones(fofbconst.nr_cv, dtype=bool) +rfenbl = np.ones(1, dtype=bool) +# select one straight section +bpmenbl = np.array( + ['C1' not in b and 'C3-2' not in b and 'C4' not in b + for b in fofbconst.bpm_nicknames]) +bpmxenbl = bpmenbl +bpmyenbl = bpmenbl +chenbl = np.array(['01M' not in c for c in fofbconst.ch_names]) +cvenbl = np.array(['01M' not in c for c in fofbconst.cv_names]) +rfenbl = np.zeros(1, dtype=bool) + +selbpm = np.hstack([bpmxenbl, bpmyenbl]) +selcorr = np.hstack([chenbl, cvenbl, rfenbl]) +selmat = selbpm[:, None] * selcorr[None, :] +if selmat.size != respm.size: + raise ValueError( + f'Incompatiple selection ({selmat.size}) and matrix size {respm.size}') +mat = respm.copy() +mat = mat[selmat] +mat = np.reshape(mat, [sum(selbpm), sum(selcorr)]) + +# calculate SVD +try: + _uo, _so, _vo = np.linalg.svd(mat, full_matrices=False) +except np.linalg.LinAlgError(): + raise ValueError('Could not calculate SVD') + +# handle singular values +# select singular values greater than minimum +idcs = _so > fofbconst.MIN_SING_VAL +_sr = _so[idcs] +nrs = np.sum(idcs) +if not nrs: + raise ValueError('All Singular Values below minimum.') +# apply Tikhonov regularization +regc = fofbconst.TIKHONOV_REG_CONST +regc *= regc +inv_s = np.zeros(_so.size, dtype=float) +inv_s[idcs] = _sr/(_sr*_sr + regc) +# calculate processed singular values +_sp = np.zeros(_so.size, dtype=float) +_sp[idcs] = 1/inv_s[idcs] + +# check if inverse matrix is valid +invmat = np.dot(_vo.T*inv_s, _uo.T) +if np.any(np.isnan(invmat)) or np.any(np.isinf(invmat)): + raise ValueError('Inverse contains nan or inf.') + +# reconstruct filtered and regularized matrix in physical units +matr = np.dot(_uo*_sp, _vo) + +# convert matrix to hardware units +str2curr = np.r_[corrs_dev.strength_2_current_factor, 1.0] +currgain = np.r_[corrs_dev.curr_gain, 1.0] +# unit convertion: um/urad (1)-> nm/urad (2)-> nm/A (3)-> nm/counts +matc = matr * fofbconst.CONV_UM_2_NM +matc = matc / str2curr[selcorr] +matc = matc * currgain[selcorr] + +# obtain pseudoinverse +# calculate SVD for converted matrix +_uc, _sc, _vc = np.linalg.svd(matc, full_matrices=False) +# handle singular value selection +idcsc = _sc/_sc.max() >= 1e-14 +inv_sc = np.zeros(_so.size, dtype=float) +inv_sc[idcsc] = 1/_sc[idcsc] +# calculate pseudoinverse of converted matrix from SVD +invmatc = np.dot(_vc.T*inv_sc, _uc.T) + +# get final matrices and singular values +sing_vals_raw = np.zeros(fofbconst.nr_svals, dtype=float) +sing_vals_raw[:_so.size] = _so + +sing_vals_phy = np.zeros(fofbconst.nr_svals, dtype=float) +sing_vals_phy[:_sp.size] = _sp +nr_sing_vals = nrs + +invrespmat = np.zeros(respm.shape, dtype=float).T +invrespmat[selmat.T] = invmat.ravel() + +respmat_proc = np.zeros(respm.shape, dtype=float) +respmat_proc[selmat] = matr.ravel() + +sing_vals_hw = np.zeros(fofbconst.nr_svals, dtype=float) +sing_vals_hw[:_sc.size] = _sc + +invrespmatconv = np.zeros(respm.shape, dtype=float).T +invrespmatconv[selmat.T] = invmatc.ravel() + +respmatconv_proc = np.zeros(respm.shape, dtype=float) +respmatconv_proc[selmat] = matc.ravel() diff --git a/siriuspy/siriuspy/injctrl/main.py b/siriuspy/siriuspy/injctrl/main.py index 1be7570979..4b4cf26002 100644 --- a/siriuspy/siriuspy/injctrl/main.py +++ b/siriuspy/siriuspy/injctrl/main.py @@ -107,46 +107,46 @@ def __init__(self): # auxiliary diagnosis pvs self._pvs_diag = dict() - for sec in secs: - self._pvs_diag[sec] = dict() - self._pvs_diag[sec]['TI'] = { - n: _PV(n+':Status-Mon', connection_timeout=0.05) - for n in _HLTimeSearch.get_hl_triggers(filters={'sec': sec})} - - if sec == 'AS': - continue - - self._pvs_diag[sec]['PS'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in _PSSearch.get_psnames( - {'sec': sec, 'dis': 'PS', 'dev': '(B|Q.*|S.*|CH|CV)'})} - - if sec != 'LI': - punames = _PSSearch.get_psnames({ - 'sec': sec, 'dis': 'PU', 'dev': '.*(Kckr|Sept)', - 'propty_name': '(?!:CCoil).*'}) - if sec == 'SI': - punames.remove('SI-01SA:PU-InjDpKckr') - self._pvs_diag[sec]['PU'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in punames} - else: - self._pvs_diag[sec]['PU'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in ['LI-01:PU-Modltr-1', 'LI-01:PU-Modltr-2']} - - self._pvs_diag[sec]['RF'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in _LIDiagConst.DEV_2_LINAME if 'RF' in n} - - self._pvs_diag[sec]['Egun'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in _LIDiagConst.DEV_2_LINAME if 'EG' in n} - - if sec in ['BO', 'SI']: - self._pvs_diag[sec]['RF'] = { - n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) - for n in _RFDiagConst.ALL_DEVICES if n.startswith(sec)} + # for sec in secs: + # self._pvs_diag[sec] = dict() + # self._pvs_diag[sec]['TI'] = { + # n: _PV(n+':Status-Mon', connection_timeout=0.05) + # for n in _HLTimeSearch.get_hl_triggers(filters={'sec': sec})} + + # if sec == 'AS': + # continue + + # self._pvs_diag[sec]['PS'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in _PSSearch.get_psnames( + # {'sec': sec, 'dis': 'PS', 'dev': '(B|Q.*|S.*|CH|CV)'})} + + # if sec != 'LI': + # punames = _PSSearch.get_psnames({ + # 'sec': sec, 'dis': 'PU', 'dev': '.*(Kckr|Sept)', + # 'propty_name': '(?!:CCoil).*'}) + # if sec == 'SI': + # punames.remove('SI-01SA:PU-InjDpKckr') + # self._pvs_diag[sec]['PU'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in punames} + # else: + # self._pvs_diag[sec]['PU'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in ['LI-01:PU-Modltr-1', 'LI-01:PU-Modltr-2']} + + # self._pvs_diag[sec]['RF'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in _LIDiagConst.DEV_2_LINAME if 'RF' in n} + + # self._pvs_diag[sec]['Egun'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in _LIDiagConst.DEV_2_LINAME if 'EG' in n} + + # if sec in ['BO', 'SI']: + # self._pvs_diag[sec]['RF'] = { + # n: _PV(n+':DiagStatus-Mon', connection_timeout=0.05) + # for n in _RFDiagConst.ALL_DEVICES if n.startswith(sec)} # Timing device self._hlti_dev = HLTiming() @@ -169,8 +169,8 @@ def __init__(self): self.egun_dev = EGun( print_log=False, callback=self._update_dev_status) self._init_egun = False - self.egun_dev.trigps.pv_object('enable').add_callback( - self._callback_watch_eguntrig) + # self.egun_dev.trigps.pv_object('enable').add_callback( + # self._callback_watch_eguntrig) self._pumode_dev = InjSysPUModeHandler( print_log=False, callback=self._update_dev_status, @@ -252,12 +252,12 @@ def __init__(self): # status scanning self.quit = False self.scanning = False - self.thread_check_diagstatus = _epics.ca.CAThread( - target=self._update_diagstatus, daemon=True) - self.thread_check_diagstatus.start() - self.thread_check_injstatus = _epics.ca.CAThread( - target=self._update_injstatus, daemon=True) - self.thread_check_injstatus.start() + # self.thread_check_diagstatus = _epics.ca.CAThread( + # target=self._update_diagstatus, daemon=True) + # self.thread_check_diagstatus.start() + # self.thread_check_injstatus = _epics.ca.CAThread( + # target=self._update_injstatus, daemon=True) + # self.thread_check_injstatus.start() # initialize default operation values with implemented values self.egun_dev.wait_for_connection() @@ -307,12 +307,12 @@ def init_database(self): 'HVOpVolt-SP': self._hvopvolt, 'HVOpVolt-RB': self._hvopvolt, 'HVOpVoltCmdSts-Mon': self._p2w['HVOpVolt']['status'], - 'PUModeDeltaPosAng-SP': self._pumode_dev.delta_posang, - 'PUModeDeltaPosAng-RB': self._pumode_dev.delta_posang, - 'PUModeDpKckrDlyRef-SP': self._pumode_dev.dpkckr_dlyref, - 'PUModeDpKckrDlyRef-RB': self._pumode_dev.dpkckr_dlyref, - 'PUModeDpKckrKick-SP': self._pumode_dev.dpkckr_kick, - 'PUModeDpKckrKick-RB': self._pumode_dev.dpkckr_kick, + # 'PUModeDeltaPosAng-SP': self._pumode_dev.delta_posang, + # 'PUModeDeltaPosAng-RB': self._pumode_dev.delta_posang, + # 'PUModeDpKckrDlyRef-SP': self._pumode_dev.dpkckr_dlyref, + # 'PUModeDpKckrDlyRef-RB': self._pumode_dev.dpkckr_dlyref, + # 'PUModeDpKckrKick-SP': self._pumode_dev.dpkckr_kick, + # 'PUModeDpKckrKick-RB': self._pumode_dev.dpkckr_kick, 'PUModeCmdSts-Mon': self._p2w['PUMode']['status'], 'TargetCurrent-SP': self._target_current, 'TargetCurrent-RB': self._target_current, @@ -356,7 +356,7 @@ def init_database(self): 'TopUpNextInj-Mon': self._topup_next, 'TopUpNrPulses-SP': self._topup_nrpulses, 'TopUpNrPulses-RB': self._topup_nrpulses, - 'InjSysCmdDone-Mon': ','.join(self._injsys_dev.done), + # 'InjSysCmdDone-Mon': ','.join(self._injsys_dev.done), 'InjSysCmdSts-Mon': _Const.InjSysCmdSts.Idle, 'RFKillBeam-Mon': _Const.RFKillBeamMon.Idle, 'DiagStatusLI-Mon': self._status['LI'], @@ -371,35 +371,35 @@ def init_database(self): self.run_callbacks(pvn, val) self._callback_update_type(init=True) - self.egun_dev.pulse.pv_object('multiselstatus').add_callback( - self._callback_update_type) - self.egun_dev.pulse.pv_object('multiswstatus').add_callback( - self._callback_update_type) - self.egun_dev.pulse.pv_object('singleselstatus').add_callback( - self._callback_update_type) - self.egun_dev.pulse.pv_object('singleswstatus').add_callback( - self._callback_update_type) - self.egun_dev.trigmultipre.pv_object('State-Sts').add_callback( - self._callback_update_type) - self.egun_dev.trigmulti.pv_object('State-Sts').add_callback( - self._callback_update_type) - self.egun_dev.trigsingle.pv_object('State-Sts').add_callback( - self._callback_update_type) - self._callback_update_pumode(init=True) - self._pumode_dev.trigdpk.pv_object('Src-Sts').add_callback( - self._callback_update_pumode) - self._pumode_dev.trigdpk.pv_object('DelayRaw-RB').add_callback( - self._callback_update_pumode) - self._pumode_dev.pudpk.pv_object('Kick-RB').add_callback( - self._callback_update_pumode) - self._pumode_dev.pudpk.pv_object('PwrState-Sts').add_callback( - self._callback_update_pumode) - self._pumode_dev.pudpk.pv_object('Pulse-Sts').add_callback( - self._callback_update_pumode) - self._pumode_dev.punlk.pv_object('PwrState-Sts').add_callback( - self._callback_update_pumode) - self._pumode_dev.punlk.pv_object('Pulse-Sts').add_callback( - self._callback_update_pumode) + # self.egun_dev.pulse.pv_object('multiselstatus').add_callback( + # self._callback_update_type) + # self.egun_dev.pulse.pv_object('multiswstatus').add_callback( + # self._callback_update_type) + # self.egun_dev.pulse.pv_object('singleselstatus').add_callback( + # self._callback_update_type) + # self.egun_dev.pulse.pv_object('singleswstatus').add_callback( + # self._callback_update_type) + # self.egun_dev.trigmultipre.pv_object('State-Sts').add_callback( + # self._callback_update_type) + # self.egun_dev.trigmulti.pv_object('State-Sts').add_callback( + # self._callback_update_type) + # self.egun_dev.trigsingle.pv_object('State-Sts').add_callback( + # self._callback_update_type) + # self._callback_update_pumode(init=True) + # self._pumode_dev.trigdpk.pv_object('Src-Sts').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.trigdpk.pv_object('DelayRaw-RB').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.pudpk.pv_object('Kick-RB').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.pudpk.pv_object('PwrState-Sts').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.pudpk.pv_object('Pulse-Sts').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.punlk.pv_object('PwrState-Sts').add_callback( + # self._callback_update_pumode) + # self._pumode_dev.punlk.pv_object('Pulse-Sts').add_callback( + # self._callback_update_pumode) self._bias_feedback.init_database() self.run_callbacks('Log-Mon', 'Started.') diff --git a/siriuspy/siriuspy/ramp/conn.py b/siriuspy/siriuspy/ramp/conn.py index 92654c292a..3263a23109 100644 --- a/siriuspy/siriuspy/ramp/conn.py +++ b/siriuspy/siriuspy/ramp/conn.py @@ -84,7 +84,9 @@ class Const(_csdev.Const): 'TrgEGunSglBun': 'LI-01:TI-EGun-SglBun', 'TrgEGunMultBun': 'LI-01:TI-EGun-MultBun', 'TrgEjeKckr': 'BO-48D:TI-EjeKckr', - 'TrgInjKckr': 'SI-01SA:TI-InjDpKckr'} + 'TrgInjDpKckr': 'SI-01SA:TI-InjDpKckr', + 'TrgInjNLKckr': 'SI-01SA:TI-InjNLKckr', + } trg_propties = ('State-Sel', 'Polarity-Sel', 'Src-Sel', 'NrPulses-SP', 'Duration-SP', 'Delay-SP', 'Status-Mon') @@ -137,7 +139,8 @@ def cmd_config_ramp(self, events_inj, events_eje, timeout=_TIMEOUT_DFLT): sp_dic[c.EvtLinac_Delay] = delays['Linac'] sp_dic[c.EvtInjBO_Delay] = delays['InjBO'] sp_dic[c.EvtInjSI_Delay] = delays['InjSI'] - sp_dic[c.TrgInjKckr_Delay] = delays[c.TrgInjKckr_Delay] + sp_dic[c.TrgInjDpKckr_Delay] = delays[c.TrgInjDpKckr_Delay] + sp_dic[c.TrgInjNLKckr_Delay] = delays[c.TrgInjNLKckr_Delay] for event in events_inj: attr = getattr(c, 'Evt'+event+'_Delay') sp_dic[attr] = delays[event] @@ -258,8 +261,11 @@ def calc_evts_delay(self, events_inj=list(), events_eje=list()): curr = self.get_readback(attr) delays[event] = curr + dlt_eje_dly - injkckr_dly = self.get_readback(c.TrgInjKckr_Delay) - delays[c.TrgInjKckr_Delay] = injkckr_dly + dlt_eje_dly + injdpkckr_dly = self.get_readback(c.TrgInjDpKckr_Delay) + delays[c.TrgInjDpKckr_Delay] = injdpkckr_dly + dlt_eje_dly + + injnlkckr_dly = self.get_readback(c.TrgInjNLKckr_Delay) + delays[c.TrgInjNLKckr_Delay] = injnlkckr_dly + dlt_eje_dly return delays def update_ramp_configsetup(self, events_inj, events_eje, delays): @@ -330,12 +336,13 @@ def _define_properties(self, prefix, connection_callback, callback): self.ramp_configsetup = { # Event delays - c.EvtLinac_Delay: None, # [us] - c.EvtInjBO_Delay: None, # [us] - c.EvtRmpBO_Delay: None, # [us] - c.EvtInjSI_Delay: None, # [us] - c.EvtStudy_Delay: None, # [us] - c.TrgInjKckr_Delay: None, # [us] + c.EvtLinac_Delay: None, # [us] + c.EvtInjBO_Delay: None, # [us] + c.EvtRmpBO_Delay: None, # [us] + c.EvtInjSI_Delay: None, # [us] + c.EvtStudy_Delay: None, # [us] + c.TrgInjDpKckr_Delay: None, # [us] + c.TrgInjNLKckr_Delay: None, # [us] # Mags trigger c.TrgMags_Delay: 0.0, # [us] # Corrs trigger diff --git a/siriuspy/siriuspy/search/get_iocs_ref.py b/siriuspy/siriuspy/search/get_iocs_ref.py new file mode 100644 index 0000000000..35ca589ceb --- /dev/null +++ b/siriuspy/siriuspy/search/get_iocs_ref.py @@ -0,0 +1,30 @@ + +from siriuspy.epics import PV +from siriuspy.search import IOCSearch + +pvs = dict() +for ioc in IOCSearch.get_iocs(): + prefs = IOCSearch.conv_ioc_2_prefixes(ioc) + if '-ap-' in ioc: + parts = ioc.split('-') + for pref in prefs: + if parts[2] in pref.lower() and parts[0] == pref[:2].lower(): + prefix = pref + break + else: + prefix = prefs[0] + + if prefix.endswith('Kick') or prefix.endswith('KL') or \ + prefix.endswith('Kx'): + prefix += '-SP' + elif ':TI-' in prefix: + prefix += ':Status-Mon' + else: + if not prefix.endswith(':Diag'): + prefix += ':' + prefix += 'Version-Cte' + pvs[ioc] = PV(prefix) + +for pv in pvs.values(): + if not pv.connected: + print(pv.pvname) diff --git a/siriuspy/siriuspy/timesys/static_table.py b/siriuspy/siriuspy/timesys/static_table.py index b602795f60..2b6673541c 100644 --- a/siriuspy/siriuspy/timesys/static_table.py +++ b/siriuspy/siriuspy/timesys/static_table.py @@ -65,11 +65,11 @@ def read_data_from_google(): # The file token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. - store = file.Storage('/home/fernando/token.json') + store = file.Storage('/home/ana/token.json') creds = store.get() if not creds or creds.invalid: flow = client.flow_from_clientsecrets( - '/home/fernando/credentials.json', + '/home/ana/credentials.json', 'https://www.googleapis.com/auth/spreadsheets.readonly') creds = tools.run_flow(flow, store) service = build('sheets', 'v4', http=creds.authorize(Http()))