diff --git a/siriuspy/siriuspy/clientconfigdb/types/__init__.py b/siriuspy/siriuspy/clientconfigdb/types/__init__.py index 02f9e9ca7..95812ebf2 100644 --- a/siriuspy/siriuspy/clientconfigdb/types/__init__.py +++ b/siriuspy/siriuspy/clientconfigdb/types/__init__.py @@ -6,5 +6,5 @@ for loader, module_name, is_pkg in _pkgutil.walk_packages(__path__): CONFIG_TYPES.append(module_name) - module = loader.find_module(module_name).load_module(module_name) - exec('%s = module' % module_name) + +__all__ = CONFIG_TYPES diff --git a/siriuspy/siriuspy/currinfo/csdev.py b/siriuspy/siriuspy/currinfo/csdev.py index 47c34ab5a..7d0579ec2 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 000000000..e9295e1c2 --- /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 9821d459f..52ee827f8 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 28a5d469f..ffbe35781 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 ce2647226..fe6faddc7 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: @@ -201,7 +202,8 @@ def __setitem__(self, propty, value): """Set value of property.""" pvobj = self.pv_object(propty) try: - pvobj.value = value + # pvobj.value = value + print(pvobj.pvname, value) except (_ChannelAccessGetFailure, _CASeverityException): print('Could not set value of {}'.format(pvobj.pvname)) diff --git a/siriuspy/siriuspy/epics/__init__.py b/siriuspy/siriuspy/epics/__init__.py index a409cd495..6cf5c794b 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/test_svd.py b/siriuspy/siriuspy/fofb/test_svd.py new file mode 100644 index 000000000..8b1b7b0b6 --- /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/machshift/gensumm_macreport.py b/siriuspy/siriuspy/machshift/gensumm_macreport.py index ca2db8c68..8757f5328 100644 --- a/siriuspy/siriuspy/machshift/gensumm_macreport.py +++ b/siriuspy/siriuspy/machshift/gensumm_macreport.py @@ -7,18 +7,21 @@ # failure statistics per month intervals = [ - [Time(2023, 1, 1, 0, 0), Time(2023, 1, 31, 23, 59, 59)], - [Time(2023, 2, 1, 0, 0), Time(2023, 2, 28, 23, 59, 59)], - [Time(2023, 3, 1, 0, 0), Time(2023, 3, 31, 23, 59, 59)], - [Time(2023, 4, 1, 0, 0), Time(2023, 4, 30, 23, 59, 59)], - [Time(2023, 5, 1, 0, 0), Time(2023, 5, 31, 23, 59, 59)], - [Time(2023, 6, 1, 0, 0), Time(2023, 6, 30, 23, 59, 59)], - [Time(2023, 7, 1, 0, 0), Time(2023, 7, 31, 23, 59, 59)], - [Time(2023, 8, 1, 0, 0), Time(2023, 8, 31, 23, 59, 59)], - [Time(2023, 9, 1, 0, 0), Time(2023, 9, 30, 23, 59, 59)], - [Time(2023, 10, 1, 0, 0), Time(2023, 10, 31, 23, 59, 59)], - [Time(2023, 11, 1, 0, 0), Time(2023, 11, 30, 23, 59, 59)], - [Time(2023, 12, 1, 0, 0), Time(2023, 12, 31, 23, 59, 59)], + # [Time(2023, 1, 1, 0, 0), Time(2023, 1, 31, 23, 59, 59)], + # [Time(2023, 2, 1, 0, 0), Time(2023, 2, 28, 23, 59, 59)], + # [Time(2023, 3, 1, 0, 0), Time(2023, 3, 31, 23, 59, 59)], + # [Time(2023, 4, 1, 0, 0), Time(2023, 4, 30, 23, 59, 59)], + # [Time(2023, 5, 1, 0, 0), Time(2023, 5, 31, 23, 59, 59)], + # [Time(2023, 6, 1, 0, 0), Time(2023, 6, 30, 23, 59, 59)], + # [Time(2023, 7, 1, 0, 0), Time(2023, 7, 31, 23, 59, 59)], + # [Time(2023, 8, 1, 0, 0), Time(2023, 8, 31, 23, 59, 59)], + # [Time(2023, 9, 1, 0, 0), Time(2023, 9, 30, 23, 59, 59)], + # [Time(2023, 10, 1, 0, 0), Time(2023, 10, 31, 23, 59, 59)], + # [Time(2023, 11, 1, 0, 0), Time(2023, 11, 30, 23, 59, 59)], + # [Time(2023, 12, 1, 0, 0), Time(2023, 12, 31, 23, 59, 59)], + [Time(2024, 1, 1, 0, 0), Time(2024, 1, 31, 23, 59, 59)], + [Time(2024, 2, 1, 0, 0), Time(2024, 2, 29, 23, 59, 59)], + [Time(2024, 3, 1, 0, 0), Time(2024, 3, 31, 23, 59, 59)], ] macreports = dict() diff --git a/siriuspy/siriuspy/machshift/macreport.py b/siriuspy/siriuspy/machshift/macreport.py index 67cf16bb9..e7d4c9dc4 100644 --- a/siriuspy/siriuspy/machshift/macreport.py +++ b/siriuspy/siriuspy/machshift/macreport.py @@ -7,6 +7,9 @@ import numpy as _np from matplotlib import pyplot as _plt +from mathphys.functions import save_pickle as _save_pickle, \ + load_pickle as _load_pickle + from ..search import PSSearch as _PSSearch from ..clientarch import ClientArchiver as _CltArch, Time as _Time, \ PVData as _PVData, PVDataSet as _PVDataSet @@ -258,6 +261,7 @@ class MacReport: [_Time(2023, 3, 3, 22, 56, 0, 0), _Time(2023, 3, 3, 23, 0, 0, 0)], # power grid failure, archiver was down [_Time(2023, 5, 18, 5, 55, 0, 0), _Time(2023, 5, 18, 9, 8, 0, 0)], + [_Time(2024, 1, 18, 14, 0, 0, 0), _Time(2024, 1, 18, 19, 45, 0, 0)], ] def __init__(self, connector=None, logger=None): @@ -1962,3 +1966,60 @@ def __str__(self): rst += '{0:>46s}: {1:>8.3f}\n'.format( ppty_text, getattr(self, ppty)) return rst + + # ----- auxiliary methods to save/restore data ----- + + def to_dict(self): + """Return dictionary with PV properties. + + Returns: + dict: dictionary with fields: + server_url (str): url of the Archiver server. + pvnames (list): the names of the PVs. + timestamp_start (time.time): start of acquisition time. + timestamp_stop (time.time): end of acquisition time. + pvdata_info (list): with dictionaries containing the data for + all PVs. Compatible input for PVData.from_dict. + + """ + return {'raw_data': self._raw_data} + + @staticmethod + def from_dict(info): + """Return PVDataSet object with information in data. + + Args: + info (dict): must have the same fields as the dictionary returned + by PVDataSet.to_dict method. + + Returns: + PVDataSet: with values compatible with data. + + """ + return info['raw_data'] + + def to_pickle(self, fname, overwrite=False): + """Create pickle file with complete PVDataSet. + + The data saved is the dictionary returned by PVDataSet.to_dict method. + + Args: + fname (str): name of the file to save. + overwrite (bool, optional): Whether to overwrite existing file + with same name or not. Defaults to False. + + """ + _save_pickle(self.to_dict(), fname, overwrite=overwrite) + + @staticmethod + def from_pickle(fname): + """Load data from pickle file and return PVDataSet object. + + Args: + fname (str): name of the file to load. + + Returns: + PVDataSet: with values from the pickle file. + + """ + return MacReport.from_dict(_load_pickle(fname)) diff --git a/siriuspy/siriuspy/ramp/conn.py b/siriuspy/siriuspy/ramp/conn.py index 92654c292..3263a2310 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 000000000..35ca589ce --- /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 b602795f6..2b6673541 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())) diff --git a/siriuspy/siriuspy/util.py b/siriuspy/siriuspy/util.py index 8d576c9cb..e00e44578 100644 --- a/siriuspy/siriuspy/util.py +++ b/siriuspy/siriuspy/util.py @@ -14,6 +14,9 @@ from mathphys import beam_optics as _beam from siriuspy import envars as _envars +DEFAULT_TIMEOUT = 10 # [s] +DEFAULT_TINYSLEEP = 0.1 # [s] + def conv_splims_labels(label): """Convert setpoint limit labels from pcaspy to epics and vice-versa.""" @@ -258,6 +261,47 @@ def check_public_interface_namespace(namespace, valid_interface, return True +def wait_timeout(func, args=None, kwargs=None, timeout=None, tiny_sleep=None): + """Wait timeout utility. + + Check func(*args, **kwargs) return value periodically (with period + `tiny_sleep`) until timeout interval ends. If func returns True, return + True, else, return False. + + Parameters + ---------- + func: callable + Function to be periocally called. + args: tuple, list (optional) + func arguments. Defaults to empty list. + kwargs: dict (optional) + func key arguments. Defaults to empty dict. + timeout: int (optional) + Maximum time interval [s] that we will wait until + `func` returns True. Defaults to DEFAULT_TIMEOUT. + tiny_sleep: int (optional) + Tiny time interval [s] to sleep between func checks. + Defaults to DEFAULT_TINYSLEEP. + + Returns + ------- + success: bool + Whether func returned True within timeout time interval. + + """ + args = list() if args is None else args + kwargs = dict() if kwargs is None else kwargs + timeout = timeout if timeout is not None else DEFAULT_TIMEOUT + tiny_sleep = tiny_sleep if tiny_sleep is not None else DEFAULT_TINYSLEEP + + _t0 = _time.time() + while _time.time() - _t0 < timeout: + if func(*args, **kwargs): + return True + _time.sleep(tiny_sleep) + return False + + # This solution was copied from: # https://stackoverflow.com/questions/5189699/how-to-make-a-class-property class ClassProperty: