pueoAnalysisTools
Loading...
Searching...
No Matches
run_LF_calibration.py
1import ROOT
2import re
3import numpy as np
4import polars as pl
5from pathlib import Path
6from initialise import load_pueoEvent_Dataset
7import calibration
8from calibration import generate_antenna_phase_center_pairs_with_placeholders, load_dataset_and_measure_time_delays
9from calibration import retrieve_relevant_antennas_from_all_pairs, compute_time_delay_residuals, objective_function, _plot_calibration_result
10import os
11from scipy.optimize import minimize
12import matplotlib.pyplot as plt
13import matplotlib
14matplotlib.use("Agg")
15
16import sys
17import glob
18
19
20if __name__ == "__main__":
21 ant_type='LF'
22 MC=False
23 run = 797
24
25 RemoveOutlier=True
26
27 # read pulser_directions_and_pairwise_time_delays from preprocessed feather files
28 files = glob.glob(f"feather_data/pulser_directions_and_pairwise_time_delays_run*.feather")
29 #files = glob.glob(f"feather_data/pulser_directions_and_pairwise_time_delays_run797_2518_7533.feather")
30 pulser_directions_and_pairwise_time_delays = pl.concat([pl.read_ipc(f) for f in files])
31
32 # make a correlation plot
33 corr = pulser_directions_and_pairwise_time_delays["max correlation"].to_numpy()
34 N_tot_pairs = len(corr)
35 print("N pairs before selections = ", N_tot_pairs)
36 percentage=90.0#99.8 # at least 99.5 for snr=5
37 pXX = np.percentile(corr, percentage)
38 print(f"{percentage}th percentile =", pXX)
39
40 # histogram
41 plt.figure(figsize=(8,5))
42 plt.hist(corr, bins=100)
43 plt.axvline(pXX, linestyle="--", linewidth=2, label=f"{percentage}% = {pXX:.3f}")
44 plt.axvline(0.9, linestyle="--", linewidth=2)
45 plt.xlabel("max correlation")
46 plt.ylabel("count")
47 plt.legend()
48 plt.tight_layout()
49 os.makedirs("plot", exist_ok=True)
50 plt.savefig(f"plot/{ant_type}max_correlation.png")
51
52 # filter out events that doesn't have a successfully caculated incoming pulser direction
53 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.filter(
54 ~pl.col("incoming pulse direction (Phi Theta)")
55 .arr.eval(pl.element().is_nan())
56 .arr.any()
57 )
58
59 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.filter(pl.col("max correlation")> 0.90)
60 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.filter(pl.col("measured time delays (ns)").is_between(-16, 16))
61
62 pl.Config.set_tbl_rows(-1) # show all rows
63 '''
64 print(
65 pulser_directions_and_pairwise_time_delays
66 .select([
67 "measured time delays (ns)",
68 "max correlation",
69 pl.col("incoming pulse direction (Phi Theta)")
70 .map_elements(
71 lambda x: np.rad2deg(np.array(x.to_list())),
72 return_dtype=pl.Array(pl.Float64, 2)
73 )
74 .alias("incoming pulse direction (deg)")
75 ])
76 )
77 '''
78 # add index to pair in order to track them later
79 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.with_row_index("row_id")
80 N_pairs = pulser_directions_and_pairwise_time_delays.height
81
82 print("N total pairs after correlation seletion = ", N_pairs)
83
84 relevant_antennas = (
85 retrieve_relevant_antennas_from_all_pairs(
86 phase_center_pairs=pulser_directions_and_pairwise_time_delays
87 )
88 )
89
90 relevant_antennas = relevant_antennas.with_columns(
91 pl.col("AntNum").cast(pl.Utf8).cast(pl.Int64).alias("AntNum")
92 )
93
94 print("relevant_antennas=", relevant_antennas)
95
96 axis_list_initial = ["Rho","Phi","Z", "CableDelay"] # adding random offst the each axis
97
98 x0_randomguess = {}
99 fixed_antenna={}
100
101 # Now make an axis list which is the 1d optimization order
102 # For the prerun minimization to remove the outlier
103 axis_list0 = []
104 for _ in range(4): # repeat 4 times
105 axis_list0.extend([["CableDelay"],["Rho"], ["Phi"], ["Z"]])
106 for _ in range(4):
107 axis_list0.extend([["CableDelay"],["Z"]])
108 axis_list0.extend([["Rho"]])
109
110 # This is for the real minimization run
111 axis_list=[]
112 for _ in range(4): # repeat 4 times
113 axis_list.extend([["CableDelay"],["Rho"], ["Phi"],["Z"]])
114 for _ in range(4):
115 axis_list.extend([["CableDelay"],["Z"]])
116 axis_list.extend([["Rho"]])
117
118 # Do minimization once to throw away outlier. Step1: setup the initial value
119 for index, axis_label in enumerate(axis_list_initial):
120 if MC:
121 # For simulation data, apply some random variation to the initial guess, or we won't initially start from the correct answer
122 x0_randomguess[axis_label] = relevant_antennas[axis_label].to_numpy().copy()
123 fixed_antenna[axis_label] = x0_randomguess[axis_label][0]
124 if axis_label=='Z':
125 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1) # -0.1, 0.1
126
127 elif axis_label=='Phi':
128 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1) # -0.05, 0.05
129
130 elif axis_label=='Rho':
131 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1) #-0.1, 0.1
132
133 elif axis_label=='CableDelay':
134 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.066, 0.066, size=len(relevant_antennas)-1) #ns -0.066, 0.066
135
136 pulser_directions_and_pairwise_time_delays = (
137 pulser_directions_and_pairwise_time_delays
138 .with_columns(
139 pl.col(f"A1_{axis_label} (placeholder)")
140 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
141 .alias(f"A1_{axis_label}"),
142
143 pl.col(f"A2_{axis_label} (placeholder)")
144 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
145 .alias(f"A2_{axis_label}")
146 )
147 )
148 else:
149 x0_randomguess[axis_label] = relevant_antennas[axis_label].to_numpy().copy()
150 fixed_antenna[axis_label] = x0_randomguess[axis_label][0]
151
152 print("x0_randomguess=", x0_randomguess)
153 # figure to plot delta T histograms
154 fig1, ax1 = plt.subplots(figsize=(8, 5))
155
156 #Do minimization once to throw away outlier. Step2: minimize along the axes in axis_list0
157 for index, axis_label in enumerate(axis_list0):
158 AXIS = axis_label[0] # "Rho", "Phi", "Z", or "CableDelay"
159
160 x0 = x0_randomguess[AXIS]
161
162 if ant_type=='LF':
163 x0=x0[1:]
164 placeholder = relevant_antennas[f"{AXIS} (placeholder)"][1:]
165 else:
166 x0=x0[1:]
167 placeholder = relevant_antennas[f"{AXIS} (placeholder)"][1:]
168
169 if AXIS== 'Rho':
170 bounds = [(1.0, 2.0)] * (len(x0))
171 if AXIS=='Phi':
172 bounds = [(-3.14, 3.14)] * (len(x0))
173 if AXIS == 'Z':
174 bounds =[(-12, -4)] * (len(x0))
175 if AXIS=='CableDelay':
176 bounds =[(-1, 1)] * (len(x0)) # -0.066, 0.066
177
178 result = minimize(fun=objective_function,
179 x0=x0,
180 args=(placeholder,
181 pulser_directions_and_pairwise_time_delays, AXIS, 'l2'),
182 method='Nelder-Mead',
183 bounds=bounds,
184 options={'maxiter': 50000})
185
186 #print("result=",result.fun)
187 #print("result.x=", result.x)
188
189
190 pulser_directions_and_pairwise_time_delays = (
191 pulser_directions_and_pairwise_time_delays
192 .with_columns(
193 pl.col(f"A1_{AXIS} (placeholder)")
194 .replace_strict(placeholder, result.x, default=pl.col(f"A1_{AXIS}"))
195 .alias(f"A1_{AXIS}"),
196
197 pl.col(f"A2_{AXIS} (placeholder)")
198 .replace_strict(placeholder, result.x, default = pl.col(f"A1_{AXIS}"))
199 .alias(f"A2_{AXIS}")
200 )
201 )
202
203 resultx = np.insert(result.x, 0, x0_randomguess[AXIS][0])
204 x0_randomguess[AXIS] = resultx
205
206
207
208 # calculate residuls distribution
209 df_resid = compute_time_delay_residuals(
210 phase_center_guess=resultx,
211 phase_center_placeholders=relevant_antennas[f"{AXIS} (placeholder)"],
212 pulser_directions_and_measured_time_delays=pulser_directions_and_pairwise_time_delays,
213 axis=AXIS,
214 )
215 #print('df_resid=', df_resid)
216 residuals = df_resid["residual (ns)"].to_numpy()
217 cmap = plt.cm.copper
218 color = cmap(index / (len(axis_list0) - 1)) if len(axis_list0) > 1 else cmap(1.0)
219 if index>=0:
220 bin_width = 0.005 #0.005
221 bins = np.arange(residuals.min(), residuals.max() + bin_width, bin_width)
222 ax1.hist(residuals, bins=bins, alpha=1, label=f'{index}:[{AXIS}]', color=color)
223 #ax1.set_xlim(-50,50)
224
225 if (index>=12 and RemoveOutlier==True):
226
227 row_before = df_resid.height
228 resid_col = "residual (ns)"
229
230 mu = df_resid.select(pl.col(resid_col).mean()).item()
231 sigma = df_resid.select(pl.col(resid_col).std()).item()
232
233 Nsigma=100
234
235 if index>=12:
236 Nsigma = 3.0
237 if index>=20:
238 Nsigma=2.0
239
240 if Nsigma!=100:
241 print(f"You're removing outlier over {Nsigma} sigma, mu=", mu, "sigma=", sigma)
242
243 good_rows = (
244 df_resid
245 .filter((pl.col(resid_col) - mu).abs() <= Nsigma * sigma)
246 .select("row_id")
247 )
248 row_after = good_rows.height
249
250 print("Removing", row_before-row_after, "rows")
251 pulser_directions_and_pairwise_time_delays = (
252 pulser_directions_and_pairwise_time_delays
253 .join(good_rows, on="row_id", how="inner")
254 )
255
256
257 ax1.set_xlabel("measured - expected (ns)")
258 ax1.set_ylabel("Counts")
259 ax1.set_title("Residual time delays")
260 ax1.grid(True, alpha=0.3)
261 ax1.legend()
262 fig1.tight_layout()
263 fig1.savefig(f"plot/{ant_type}step_timediff_RemoveOutliers.png")
264
265 plt.close()
266
267 # Now we've done selecting samples, we start the real optimization
268
269 N_pairs = pulser_directions_and_pairwise_time_delays.height
270 print("N pairs after removing outliers = ", N_pairs)
271
272 x0_randomguess = {} # the initial value
273 fixed_antenna={}
274
275 result_rho = None
276 result_z = None
277 result_phi = None
278 result_cabledelay = None
279 tmp_trace = []
280
281 for index, axis_label in enumerate(axis_list_initial):
282 # apply some random variations to the initial guess, or we won't initially start from the correct answer
283
284 x0_randomguess[axis_label] = relevant_antennas[axis_label].to_numpy().copy() # fill in initial value
285 fixed_antenna[axis_label] = x0_randomguess[axis_label][0] # fix one antenna
286
287 if axis_label=='Z':
288 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1)
289
290 elif axis_label=='Phi':
291 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1) #-0.01, 0.01
292
293 elif axis_label=='Rho':
294 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1) #-0.01, 0.01
295
296 elif axis_label=='CableDelay':
297 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.066, 0.066, size=len(relevant_antennas)-1) #ns
298
299 pulser_directions_and_pairwise_time_delays = (
300 pulser_directions_and_pairwise_time_delays
301 .with_columns(
302 pl.col(f"A1_{axis_label} (placeholder)")
303 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
304 .alias(f"A1_{axis_label}"),
305
306 pl.col(f"A2_{axis_label} (placeholder)")
307 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
308 .alias(f"A2_{axis_label}")
309 )
310 )
311
312 # figure to plot delta T histograms
313 fig1, ax1 = plt.subplots(figsize=(8, 5))
314
315 # minimize along axis_list elements
316 for index, axis_label in enumerate(axis_list):
317
318 AXIS = axis_label[0] # "Rho", "Phi", "CableDelay" or "Z"
319
320 x0 = x0_randomguess[AXIS] # initial value for the AXIS
321
322 x0=x0[1:] # position of the first antenna is fixed, so we start from 1
323 placeholder = relevant_antennas[f"{AXIS} (placeholder)"][1:]
324
325 if AXIS== 'Rho':
326 bounds = [(1.0, 2.0)] * (len(x0))
327 if AXIS=='Phi':
328 bounds = [(-3.14, 3.14)] * (len(x0))
329 if AXIS == 'Z':
330 bounds =[(-12, -4)] * (len(x0))
331 if AXIS=='CableDelay':
332 bounds =[(-1, 1)] * (len(x0))
333
334 result = minimize(fun=objective_function,
335 x0=x0,
336 args=(placeholder,
337 pulser_directions_and_pairwise_time_delays, AXIS),
338 method='Nelder-Mead',
339 bounds=bounds,
340 options={'maxiter': 500_000})
341
342 # save the loss function value to keep track
343 tmp_trace.append(result.fun)
344
345
346 pulser_directions_and_pairwise_time_delays = (
347 pulser_directions_and_pairwise_time_delays
348 .with_columns(
349 pl.col(f"A1_{AXIS} (placeholder)")
350 .replace_strict(placeholder, result.x, default=pl.col(f"A1_{AXIS}"))
351 .alias(f"A1_{AXIS}"),
352
353 pl.col(f"A2_{AXIS} (placeholder)")
354 .replace_strict(placeholder, result.x, default = pl.col(f"A1_{AXIS}"))
355 .alias(f"A2_{AXIS}")
356 )
357 )
358
359 resultx = np.insert(result.x, 0, x0_randomguess[AXIS][0]) # place the first (the fixed position one) antenna back
360 x0_randomguess[AXIS] = resultx
361
362 df_resid = compute_time_delay_residuals(
363 phase_center_guess=resultx,
364 phase_center_placeholders=relevant_antennas[f"{AXIS} (placeholder)"],
365 pulser_directions_and_measured_time_delays=pulser_directions_and_pairwise_time_delays,
366 axis=AXIS,
367 )
368 residuals = df_resid["residual (ns)"].to_numpy()
369 cmap = plt.cm.copper
370 color = cmap(index / (len(axis_list) - 1)) if len(axis_list) > 1 else cmap(1.0)
371 if index%3==0:
372 bin_width = 0.005
373 bins = np.arange(residuals.min(), residuals.max() + bin_width, bin_width)
374 ax1.hist(residuals, bins=bins, alpha=0.8, label=f'{index}:[{AXIS}]', color=color)
375
376
377 if AXIS == 'Rho':
378 result_rho = np.insert(result.x, 0, x0_randomguess[AXIS][0])
379 elif AXIS == 'Phi':
380 result_phi = np.insert(result.x, 0, x0_randomguess[AXIS][0])
381 elif AXIS == 'Z':
382 result_z = np.insert(result.x, 0, x0_randomguess[AXIS][0])
383 elif AXIS == 'CableDelay':
384 result_cabledelay = np.insert(result.x, 0, x0_randomguess[AXIS][0])
385
386 ax1.legend()
387 fig1.savefig(f"plot/Minimization_plot.png")
388 plt.close(fig1)
389 print("Rho:", result_rho)
390 print("Phi:", np.rad2deg(result_phi))
391 print("Z:", result_z)
392 print("CableDelay:", result_cabledelay)
393
394