Then I found it's better to improve ALIGNMENT_RECORDER.
-----
Prior knowledge:
Good alignment values are recorded to K1:VIS-$(OPTIC)_GOOD_$(DOF) by ALIGNMENT_RECORDER guardian during IFO is in locked without excitation. These values are computed from witness channels such as K1:VIS-$(OPTIC)_TM_WIT_$(DOF), K1:VIS-$(OPTIC)_TM_SUMOUT_$(DOF)_OUT16, etc.
These good values are copied to setpoint such as K1:VIS-$(OPTIC)_TM_SET_$(DOF)_OFFSET or K1:VIS-$(OPTIC)_TM_OPTICALIGN_$(DOF)_OFFSET by LSC_LOCK guardian in RESET_ASC_FEEDBACK. As a result, output from ASC models are offloaded when lockloss occurrs.
Quick check:
At first, I check the related channels about OMMT1 PIT. Figure. 1 is a time-series plot of K1:VIS-OMMT1_TM_SUMOUT_P_OUT16 as a witness, K1:VIS-OMMT1_GOOD_OUTPUT_PIT as a good alignment value, and K1:VIS-OMMT1_TM_OPTICALIGN_P_OUT16 as a setpoint (though showing *_OFFSET was better) around problematic time. We can notice soon that the good value (green curve) changed before changes in the witness (blue curve) and the setpoint (orange curve). So this means something occurred on ALIGNMENT_RECORDER not a rounding in RESET_ASC_FEEDBACK and/or SDF.
Code check of ALIGNMENT_RECORDER:
ALIGNMENT_RECORDER gets witness channel values by cdsutils.getdata() and computes the good value of CDSData.data.mean() which is derived from ndarray. Time epoch of taken data is from 60s ago to 50s ago in order to avoid contamination of good values due to data that contains values after the lockloss by using recent data. To simulate this situation, I executed a same process with K1:VIS-OMMT1_TM_SUMOUT_P_OUT16 around 50-60s ahead of the change in K1:VIS-OMMT1_GOOD_OUTPUT_PIT. Figure. 2 shows the zoom up of Fig.1. t=0 represents a timing of the update in the good value and two t-cursors represent a time span for computing the average of witness. My offline simulation reproduces a same value as one computed by ALIGNMENT_RECORDER. So the average value of a time series which is around -1290.0139... is computed as -1290.01379.....
Looking at the cdsutils results in a more detail, the reason of this issue seemed that unexpected large error due to the channel data retrieved in numpy.float32. The 1st and last elements of 10-second long witness data are as follows.
1) as raw value (np.float32) = (-1290.0139, -1290.0139)2) and 3) were explicitly cast after reading data (Of course, the middle values were also completely same value as the first and last elements).
2) as casted value as float = (-1290.013916015625, -1290.013916015625)
3) as casted value as np.float64 = (-1290.013916015625, -1290.013916015625)
By using these data arrays, I computed average of elements in the array as follows.
A) average with sum by sum() method of ndarray = -1290.01376953125In the way of C) or D) in which a explicit cast is used for each element can produce accurate average of array elements. But E) is used way in current ALIGNMENT_RECORDER and it's affected by an accuracy of 32bit floating number. (I guess that it was enhanced by the constant value of OMMT1 witnesses because a direction of the numerical error becomes same for all elements. When the witness contains a noise such as OpLev channels, a direction of the numerical error for all elements are random.)
B) average with manual sum of raw elements (np.float32) = -1290.01533203125
C) average with manual sum of casted elements as float = -1290.013916015625
D) average with manual sum of casted elements as np.float64 = -1290.013916015625
E) averaged by mean() method of ndarray = -1290.0137939453125
Anyway, we know now reading channels as float32 is not sufficient for this purpose. We should insert an explicit cast from float32 to float64. And also it might be better to apply round function for final results of good and setpoint values in ALIGNMENT_RECORDER instead of RESET_ASC_FEEDBACK to mitigate the SDF issue.
Following modification of ALIGNMENT_RECORDER should be enough
--- /opt/rtcds/userapps/release/sys/k1/guardian/ALIGNMENT_RECORDER.py 2025-06-17 15:41:49.000000000 +0900
+++ a.py 2025-07-23 20:28:53.378990691 +0900
@@ -192,7 +192,7 @@ class ALIGNMENT_RECORD(ASYNC_GETDATA):
if dd.name in self.grd_chans:
self.grd_state[dd.name][self.index] = all((dd.data == 2000) | (dd.data >= 8000))
else:
- self.alignment[dd.name][self.index] = dd.data.mean()
+ self.alignment[dd.name][self.index] = dd.data.mean(dtype=np.float64)
self.index = (self.index + 1) % self.narray
def _get_oldest_value(self):