Skip to content
Snippets Groups Projects
Commit 22dccb84 authored by Camilla Compton's avatar Camilla Compton
Browse files

Merge branch '212_213_saturation_thresholds' into 'master'

Updated suspension saturation thresholds, added saturation labels, make lpy plot for ETMX L3 if no saturating suspensions

Closes #213 and #164

See merge request jameson.rollins/locklost!140
parents f0bbb50a 73e22965
No related branches found
No related tags found
No related merge requests found
Pipeline #690453 passed
......@@ -159,71 +159,66 @@ def get_saturation_threshold(chan, gps, IFO):
SATURATION_THRESHOLD_16 = (2**16) / 2
SATURATION_THRESHOLD_18 = (2**18) / 2
SATURATION_THRESHOLD_20 = (2**20) / 2
SATURATION_THRESHOLD_28 = (2**28) / 2
# Initializing bit channel lists so we don't run into issues when returning
TWENTYEIGHT_BIT_CHANNELS = []
TWENTY_BIT_CHANNELS = []
EIGHTEEN_BIT_CHANNELS = []
SIXTEEN_BIT_CHANNELS = [
'SUS-RM',
'SUS-ZM',
'SUS-OM1',
'SUS-OM2',
'SUS-OM3'
]
if IFO == 'L1':
# This corresponds to a change from 18 to 20-bit DAC for ETMX L3 channels.
# ETMX L3 channels after this date have counts that are four times higher
CHANGE_DAC_DATE = 1188518418
# This corresponds to a change from 18 to 20-bit DAC for ETMY L3 channels.
# ETMY L3 channels after this date have counts that are four times higher
CHANGE_DAC_DATE = 1188518418 # 2017/09/04
# This gets the DAC configuration in line with https://alog.ligo-la.caltech.edu/aLOG/index.php?callRep=63133
# Exact DAC times should be added later to better analyze old events
CHANGE_DAC_DATE2 = 1358033599
CHANGE_DAC_DATE2 = 1358033599 # 2023/01/17
# https://alog.ligo-la.caltech.edu/aLOG/index.php?callRep=63808
CHANGE_DAC_DATE3 = 1361911279
CHANGE_DAC_DATE3 = 1361911279 # 2023/03/03
elif IFO == 'H1':
# This corresponds to a change from 18 to 20-bit DAC for ETMX L3 channels.
# ETMX L3 channels after this date have counts that are four times higher
CHANGE_DAC_DATE = 1224961218
CHANGE_DAC_DATE = 1224961218 # 2018/10/30
# This gets the DAC configuration in line with https://alog.ligo-wa.caltech.edu/aLOG/index.php?callRep=66836
# Exact DAC times should be added later to better analyze old events
CHANGE_DAC_DATE2 = 1358024479
CHANGE_DAC_DATE2 = 1358024479 # 2023/01/17
# Change from 20-bit to 28-bit DACs on H1 EX L1, L2, L3 only
# See https://alog.ligo-wa.caltech.edu/aLOG/index.php?callRep=80167
CHANGE_DAC_DATE3 = 1410642901 # 2024/09/18
else:
# Assuming missed channel is 18-bit
return SATURATION_THRESHOLD_18
# Before CHANGE_DAC_DATE (L1 2017/09/04, H1 2018/10/30)
if gps < CHANGE_DAC_DATE:
SIXTEEN_BIT_CHANNELS = [
'SUS-RM1_M1',
'SUS-RM2_M1',
'SUS-ZM1_M1',
'SUS-ZM2_M1',
'SUS-OM1_M1',
'SUS-OM2_M1',
'SUS-OM3_M1'
]
if any(x in chan for x in SIXTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_16
else:
return SATURATION_THRESHOLD_18
# Between CHANGE_DAC_DATE and CHANGE_DAC_DATE2
# (L1 2017/09/04-2023/01/17, H1 2018/10/30-2023/01/17)
elif gps >= CHANGE_DAC_DATE and gps < CHANGE_DAC_DATE2:
# ETM's are 20-bit and were changed at a date within our wanted
# lockloss time range
SIXTEEN_BIT_CHANNELS = [
'SUS-RM1_M1',
'SUS-RM2_M1',
'SUS-ZM1_M1',
'SUS-ZM2_M1',
'SUS-OM1_M1',
'SUS-OM2_M1',
'SUS-OM3_M1'
]
if IFO == 'H1':
ETM_L3_CHANNELS = 'SUS-ETMX_L3'
elif IFO == 'L1':
if IFO == 'L1':
ETM_L3_CHANNELS = 'SUS-ETMY_L3'
else:
# Assuming missed channel is 18-bit
return SATURATION_THRESHOLD_18
elif IFO == 'H1':
ETM_L3_CHANNELS = 'SUS-ETMX_L3'
if ETM_L3_CHANNELS in chan:
return SATURATION_THRESHOLD_20
elif any(x in chan for x in SIXTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_16
else:
return SATURATION_THRESHOLD_18
else:
# Between CHANGE_DAC_DATE2 and CHANGE_DAC_DATE3
# (L1 2023/01/17-2023/03/03, H1 2023/01/17-2024/09/18)
elif gps >= CHANGE_DAC_DATE2:
if IFO == 'L1':
TWENTY_BIT_CHANNELS = [
'SUS-ETM',
......@@ -245,26 +240,17 @@ def get_saturation_threshold(chan, gps, IFO):
'SUS-OMC',
'SUS-IM'
]
SIXTEEN_BIT_CHANNELS = [
'SUS-ZM',
'SUS-OM1',
'SUS-OM2',
'SUS-OM3',
'SUS-RM'
]
if gps >= CHANGE_DAC_DATE3:
TWENTY_BIT_CHANNELS = TWENTY_BIT_CHANNELS + EIGHTEEN_BIT_CHANNELS
elif IFO == 'H1':
TWENTY_BIT_CHANNELS = [
'SUS-ETMX_L1',
'SUS-ETMY_L1',
'SUS-ETMX_L2',
'SUS-ETMY_L2',
'SUS-ETMX_L3',
'SUS-ETMY_L1',
'SUS-ETMY_L2',
'SUS-ETMY_L3',
'SUS-ITMX_L2',
'SUS-ITMY_L2',
'SUS-ITMX_L3',
'SUS-ITMY_L2',
'SUS-ITMY_L3',
'SUS-BS_M2',
'SUS-SRM_M2',
......@@ -291,25 +277,33 @@ def get_saturation_threshold(chan, gps, IFO):
'SUS-BS_M1',
'SUS-OMC',
]
SIXTEEN_BIT_CHANNELS = [
'SUS-ZM',
'SUS-OM1',
'SUS-OM2'
'SUS-OM3',
'SUS-RM'
]
else:
# Assuming missed channel is 18-bit
return SATURATION_THRESHOLD_18
if any(x in chan for x in TWENTY_BIT_CHANNELS):
return SATURATION_THRESHOLD_20
elif any(x in chan for x in EIGHTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_18
elif any(x in chan for x in SIXTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_16
else:
# Assuming missed channel is 18-bit
return SATURATION_THRESHOLD_18
# After CHANGE_DAC_DATE3 (L1 2023/03/03, H1 2024/09/18)
if gps >= CHANGE_DAC_DATE3:
if IFO == 'L1':
TWENTY_BIT_CHANNELS = TWENTY_BIT_CHANNELS + EIGHTEEN_BIT_CHANNELS
elif IFO == 'H1':
TWENTYEIGHT_BIT_CHANNELS = [
'SUS-ETMX_L1',
'SUS-ETMX_L2',
'SUS-ETMX_L3'
]
for new_susstage in TWENTYEIGHT_BIT_CHANNELS:
for susstage in TWENTY_BIT_CHANNELS:
if new_susstage == susstage:
TWENTY_BIT_CHANNELS.remove(susstage)
# Return saturation thresholds
if any(x in chan for x in TWENTYEIGHT_BIT_CHANNELS):
return SATURATION_THRESHOLD_28
elif any(x in chan for x in TWENTY_BIT_CHANNELS):
return SATURATION_THRESHOLD_20
elif any(x in chan for x in EIGHTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_18
elif any(x in chan for x in SIXTEEN_BIT_CHANNELS):
return SATURATION_THRESHOLD_16
else:
# Assuming missed channel is 18-bit
return SATURATION_THRESHOLD_18
ANALOG_BOARD_CHANNELS = [
......
......@@ -31,13 +31,13 @@ def find_lpy(event):
sat_channel = ''
if not os.path.exists(inpath_csv):
logger.info("LPY plot bypassed (no saturating suspensions).")
return
with open(inpath_csv, 'r') as f:
first_sat = f.readline()
if first_sat:
sat_channel, sat_time = first_sat.split(' ', 1)
logger.info('No saturating suspensions, plotting ETMX L3 LPY')
sat_channel = f'{config.IFO}:SUS-ETMX_L3_MASTER_OUT_UR_DQ'
else:
with open(inpath_csv, 'r') as f:
first_sat = f.readline()
if first_sat:
sat_channel, sat_time = first_sat.split(' ', 1)
# generate the LPY mapping and channels to query from base channel
lpy_map = channel2lpy_coeffs(sat_channel)
......@@ -59,8 +59,13 @@ def find_lpy(event):
for title in titles:
for corner, coeff in lpy_map[title].items():
idx = channels.index(get_sus_channel(base_channel, corner))
saturation_threshold = config.get_saturation_threshold(get_sus_channel(base_channel, corner), gps, config.IFO)
lpy[title] += (bufs[idx].data * coeff) / (4 * saturation_threshold)
# If ETMX, make threshold < saturation value so we can see better
if base_channel[0:11] == 'H1:SUS-ETMX':
threshold = 524288
lpy[title] += (bufs[idx].data * coeff) / (4 * threshold)
else:
threshold = config.get_saturation_threshold(get_sus_channel(base_channel, corner), gps, config.IFO)
lpy[title] += (bufs[idx].data * coeff) / (4 * threshold)
colors = ['#2c7fb8', '#e66101', '#5e3c99']
fig, axs = plt.subplots(3, sharex=True, figsize=(27, 16))
......@@ -73,12 +78,16 @@ def find_lpy(event):
alpha=0.8,
lw=2,
)
if base_channel[0:11] == 'H1:SUS-ETMX':
label = '524288 cts (sat threshold = 134217728)'
else:
label = 'saturation threshold'
ax.axhline(
1,
color='red',
linestyle='--',
lw=3,
label='saturation threshold',
label=label,
)
ax.axhline(
-1,
......
......@@ -35,11 +35,13 @@ def find_saturations(event):
shift = -0.5*sat_search_window[0]
while expand is True:
relative_datasets = []
saturation_values = []
for idx, buf in enumerate(bufs):
channel_name = str(buf.channel)
saturation_threshold = config.get_saturation_threshold(channel_name, gps, config.IFO)
reduced_data = buf.data/(saturation_threshold)
relative_datasets.append(reduced_data)
saturation_values.append(saturation_threshold)
srate = buf.sample_rate
early_thresh = int(shift*srate)
if any(abs(reduced_data[:early_thresh]) >= 0.9):
......@@ -65,11 +67,18 @@ def find_saturations(event):
channel_name = str(buf.channel)
srate = buf.sample_rate
reduced_dataset = relative_datasets[idx]
saturation_value = saturation_values[idx]
t = np.arange(segment[0], segment[1], 1/srate)
newtime_idx = max(np.nonzero(t <= gps)[0])
sat_idxs = np.nonzero(abs(reduced_dataset[:newtime_idx]) >= 1)[0]
if len(sat_idxs) > 0:
saturations.append([channel_name, t[min(sat_idxs)], t, reduced_dataset])
saturations.append([
channel_name,
t[min(sat_idxs)],
t,
reduced_dataset,
saturation_value
])
if not saturations:
logger.info("No saturated suspension channels before lockloss.")
......@@ -104,7 +113,7 @@ def find_saturations(event):
val[2]-gps,
val[3],
color=config.SATURATION_CM[idx],
label=distill_channel_name(val[0]),
label=f'{distill_channel_name(val[0])}',
alpha=0.8,
lw=2,
)
......@@ -124,7 +133,7 @@ def find_saturations(event):
textcoords='offset points',
horizontalalignment='center',
verticalalignment='bottom',
bbox=dict(boxstyle="round", fc="w", ec="black", alpha=0.95),
bbox=dict(boxstyle="round", fc="w", ec="black", alpha=0.95)
)
# draw lines on plot for saturation thresholds, lock loss time
......@@ -147,6 +156,39 @@ def find_saturations(event):
lw=2
)
# Add labels listing saturation values above top red dashed line
chan_sats = []
for idx, val in enumerate(saturations[0:8]):
short_name = distill_channel_name(val[0])[:-3]
sat_val = '{:.2e}'.format(val[4])
name_sat = f'{short_name}: {sat_val}'
for j in range(0, len(chan_sats)):
if short_name in chan_sats[j]:
break
if sat_val in chan_sats[j]:
sat_split = chan_sats[j].split(':')
sat_chan_val = f'{sat_split[0]}, {short_name}:{sat_split[1]}'
chan_sats[j] = sat_chan_val
break
else:
chan_sats.append(name_sat)
saturation_thresh = 'Saturation thresholds:'
for sat_set in chan_sats:
saturation_thresh = f'{saturation_thresh} {sat_set};'
saturation_thresh = saturation_thresh[:-1]
# Find x and y coords for saturation text label that are set
# proportionally wrt to the y=1 horizontal line and the plot limits
xmin, xmax = ax.get_xlim()
sat_text_x = ((xmax - xmin)*0.03) + xmin
sat_text_y = ((ymax-ymin)*0.015) + 1
plt.text(
sat_text_x,
sat_text_y,
saturation_thresh,
color='red',
size=22
)
ax.legend(handles[::-1], labels[::-1], bbox_to_anchor=(1.1, 0.96), title='optic/stage/corner')
fig.tight_layout(pad=0.05)
ax.set_title('Saturating suspension channels (ordered by time of saturation)', y=1.04)
......@@ -166,8 +208,8 @@ def find_saturations(event):
def set_ylims(saturations, xmax, buffer):
"""Calculate ylims for the section of SUS channels occuring before lock loss time.
"""Calculate ylims for the section of SUS channels occuring before
lock loss time.
"""
ymaxs = []
ymins = []
......
......@@ -89,7 +89,15 @@
</div>
</div>
% else:
<p>LPY plots not created due to lack of saturating suspension channels.</p>
<hr />
<div class="container">
<div class="row">
<h3>Length-Pitch-Yaw Plots</h3>
<br />
<p>Length, pitch, and yaw drives reconstructed from osem DAC counts for ETMX L3 since no suspension stages saturated before lockloss time.</p>
% include('plots.tpl', plots=event_plot_urls(event, 'lpy'), size=6)
</div>
</div>
% end
<!-- PI Monitor -->
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment