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
7
8dirname = "simplesim_lat885_lat88_lat87_snr5ish_200evt"
9#dirname = "tmp"
10#dirname = "simplesim_lat88_5_snr5ish"
11if __name__ == "__main__":
12 import os
13 from scipy.optimize import minimize
14 import matplotlib.pyplot as plt
15 import matplotlib
16 import calibration
17 from calibration import generate_antenna_phase_center_pairs_with_placeholders, load_dataset_and_measure_time_delays
18 from calibration import retrieve_relevant_antennas_from_all_pairs, compute_time_delay_residuals, objective_function, _plot_calibration_result
19 matplotlib.use("Agg")
20 ant_type='LF'
21 MC=True
22
23 RemoveOutlier=True
24 RemoveOutlierTimes=0
25 calibration.dirname = dirname
26 calibration.UPSAMPLE_FACTOR=50
27
28 # use nominal phase center as the initial guesss of the minimzation
29 if ant_type=='MI':
30 j25: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/jun25/qrh.dat")
31 a25: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/aug25/qrh.dat")
32 offset_inSIM: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/jun25/phase_center_offset_MI.dat")
33 elif ant_type=='LF':
34 j25: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/jun25/lf_cal.dat")#lf_cal.dat
35 a25: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/aug25/lf.dat")
36 offset_inSIM: Path = os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/jun25/phase_center_offset_LF.dat")
37 else:
38 print("Ant type doesn't exist!")
39
40 nominal_pairs = generate_antenna_phase_center_pairs_with_placeholders(qrh_dot_dat=j25, offset_inSIM=offset_inSIM, ant_type=ant_type)
41
42 # start with some run number to initialize the Dataset instance.
43 ds: ROOT.pueo.Dataset = load_pueoEvent_Dataset(pueo_mc_data=Path(f"/users/PAS0654/yuchieh/pueo_2025/components/pueoAnalysisTools/SharedUtils/{dirname}/"), run_number=20)
44 if ant_type=='LF':
45 Icefinalpath=f'./{dirname}/'
46 else:
47 Icefinalpath=''
48
49 pulser_directions_and_pairwise_time_delays = (
50 load_dataset_and_measure_time_delays(dataset=ds, phase_center_pairs=nominal_pairs, MC=True, AntType=ant_type, IcefinalFileDir=Icefinalpath, corr_fcn='ZNCC') # option corr_fcn='ZNCC'
51 )
52
53 corr = pulser_directions_and_pairwise_time_delays["max correlation"].to_numpy()
54 N_tot_pairs = len(corr)
55 print("N_tot_pairs=", N_tot_pairs)
56 percentage=99.7 # at least 99.5 for snr=5
57 pXX = np.percentile(corr, percentage)
58 print(f"{percentage}th percentile =", pXX)
59
60 # histogram
61 plt.figure(figsize=(8,5))
62 plt.hist(corr, bins=100)
63 plt.axvline(pXX, linestyle="--", linewidth=2, label=f"{percentage}% = {pXX:.3f}")
64 plt.xlabel("max correlation")
65 plt.ylabel("count")
66 plt.legend()
67 plt.tight_layout()
68 os.makedirs(f"plot/{dirname}", exist_ok=True)
69 plt.savefig(f"plot/{dirname}/max_correlation.png")
70
71 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.filter((pl.col("max correlation")> pXX) | (pl.col("max correlation")> 0.95))
72 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.filter(pl.col("measured time delays (ns)").is_between(-16, 16))
73
74
75
76 # add index to pair in order to track them later
77 pulser_directions_and_pairwise_time_delays = pulser_directions_and_pairwise_time_delays.with_row_index("row_id")
78
79
80
81 N_pairs = pulser_directions_and_pairwise_time_delays.height
82
83 #pl.Config.set_tbl_cols(30) # show up to 1000 columns
84 #pl.Config.set_tbl_rows(30) # optional: show more rows too
85 #pl.Config.set_tbl_width_chars(200) # optional: wider display
86 print("N pairs=", N_pairs)
87
88 relevant_antennas = (
89 retrieve_relevant_antennas_from_all_pairs(
90 phase_center_pairs=pulser_directions_and_pairwise_time_delays
91 )
92 )
93 print("relevant_antennas=", relevant_antennas)
94 axis_list_initial = ["Rho","Phi","Z", "CableDelay"] # adding random offst the each axis
95 plot_range=[0.1,0.1,10/180*3.1415926] # cm, cm, deg
96 unit_scale=[100,100,180/3.1415926] # m to cm, m to cm, rad to deg
97
98 x0_randomguess = {}
99 fixed_antenna={}
100
101 best_result_rho = None
102 best_result_z = None
103 best_result_phi = None
104 best_result_cabledelay = None
105 result_rho = None
106 result_z = None
107 result_phi = None
108 result_cabledelay = None
109 best_value = np.inf
110 best_trace = []
111 tmp_trace = []
112
113 # repeat the minimization n times and pick the minimal loss function. This is to reduce dependency on initial value, and stuck in local minimum
114 for trials in range(150): #50
115 for index, axis_label in enumerate(axis_list_initial):
116 if MC:
117
118 # For simulation data, apply some random variation to the initial guess, or we won't initially start from the correct answer
119
120 x0_randomguess[axis_label] = relevant_antennas[axis_label].to_numpy().copy()
121 fixed_antenna[axis_label] = x0_randomguess[axis_label][0]
122 if axis_label=='Z':
123 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1)
124
125 elif axis_label=='Phi':
126 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.05, 0.05, size=len(relevant_antennas)-1)
127
128 elif axis_label=='Rho':
129 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.1, 0.1, size=len(relevant_antennas)-1)
130
131 elif axis_label=='CableDelay':
132 x0_randomguess[axis_label][1:] = x0_randomguess[axis_label][1:] + np.random.uniform(-0.066, 0.066, size=len(relevant_antennas)-1) #ns
133
134
135 pulser_directions_and_pairwise_time_delays = (
136 pulser_directions_and_pairwise_time_delays
137 .with_columns(
138 pl.col(f"A1_{axis_label} (placeholder)")
139 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
140 .alias(f"A1_{axis_label}"),
141
142 pl.col(f"A2_{axis_label} (placeholder)")
143 .replace_strict(relevant_antennas[f"{axis_label} (placeholder)"], x0_randomguess[axis_label])
144 .alias(f"A2_{axis_label}")
145 )
146 )
147 else:
148 x0_randomguess[axis_label] = (
149 relevant_antennas[axis_label].to_numpy()
150 )
151
152 axis_list=[]
153 for _ in range(3): # repeat 3 times
154 axis_list.extend([["CableDelay"],["Rho"], ["Phi"],["Z"]])
155 for _ in range(6):
156 axis_list.extend([["CableDelay"], ["Z"]])
157 axis_list.extend([["Rho"], ["Phi"]])
158
159 # figure to plot delta T histograms
160 fig1, ax1 = plt.subplots(figsize=(8, 5))
161
162 for index, axis_label in enumerate(axis_list):
163
164 AXIS = axis_label[0] # "Rho", "Phi", or "Z"
165
166 #print("AXIS=",axis_label ," true:", relevant_antennas[AXIS])
167 x0 = x0_randomguess[AXIS]
168 #print("starting points=", x0)
169
170 if ant_type=='LF':
171 x0=x0[1:]
172 placeholder = relevant_antennas[f"{AXIS} (placeholder)"][1:]
173 else:
174 x0=x0[1:]
175 placeholder = relevant_antennas[f"{AXIS} (placeholder)"][1:]
176
177 if AXIS== 'Rho':
178 bounds = [(1.0, 2.5)] * (len(x0))
179
180 if AXIS=='Phi':
181 bounds = [(-3.14, 3.14)] * (len(x0))
182 if AXIS == 'Z':
183 bounds =[(-12, -4)] * (len(x0))
184 if AXIS=='CableDelay':
185 bounds =[(-0.1, 0.1)] * (len(x0))
186
187 result = minimize(fun=objective_function,
188 x0=x0,
189 args=(placeholder,
190 pulser_directions_and_pairwise_time_delays, AXIS),
191 method='Nelder-Mead',
192 bounds=bounds,
193 options={'maxiter': 500000000})
194
195 print("result=",result.fun)
196 tmp_trace.append(result.fun)
197
198
199 pulser_directions_and_pairwise_time_delays = (
200 pulser_directions_and_pairwise_time_delays
201 .with_columns(
202 pl.col(f"A1_{AXIS} (placeholder)")
203 .replace_strict(placeholder, result.x, default=pl.col(f"A1_{AXIS}"))
204 .alias(f"A1_{AXIS}"),
205
206 pl.col(f"A2_{AXIS} (placeholder)")
207 .replace_strict(placeholder, result.x, default = pl.col(f"A1_{AXIS}"))
208 .alias(f"A2_{AXIS}")
209 )
210 )
211
212 resultx = np.insert(result.x, 0, x0_randomguess[AXIS][0])
213 x0_randomguess[AXIS] = resultx
214
215
216 df_resid = compute_time_delay_residuals(
217 phase_center_guess=resultx,
218 phase_center_placeholders=relevant_antennas[f"{AXIS} (placeholder)"],
219 pulser_directions_and_measured_time_delays=pulser_directions_and_pairwise_time_delays,
220 axis=AXIS,
221 )
222
223 residuals = df_resid["residual (ns)"].to_numpy()
224 cmap = plt.cm.copper
225 color = cmap(index / (len(axis_list) - 1)) if len(axis_list) > 1 else cmap(1.0)
226 ax1.hist(residuals, bins=80, alpha=0.3, label=f'{index}:[{AXIS}]', color=color)
227
228 if (index>=8 and RemoveOutlier==True and RemoveOutlierTimes<5):
229 RemoveOutlierTimes=RemoveOutlierTimes+1
230 row_before = df_resid.height
231
232 resid_col = "residual (ns)"
233
234 mu = df_resid.select(pl.col(resid_col).mean()).item()
235 sigma = df_resid.select(pl.col(resid_col).std()).item()
236
237 print("You're removing outlier over 3 sigma, mu=", mu, "sigma=", sigma)
238 good_rows = (
239 df_resid
240 .filter((pl.col(resid_col) - mu).abs() <= 3 * sigma)
241 .select("row_id")
242 )
243
244 row_after = good_rows.height
245
246 print("Removing", row_before-row_after, "rows")
247 pulser_directions_and_pairwise_time_delays = (
248 pulser_directions_and_pairwise_time_delays
249 .join(good_rows, on="row_id", how="inner")
250 )
251
252 #relevant_antennas = (
253 # retrieve_relevant_antennas_from_all_pairs(
254 # phase_center_pairs=pulser_directions_and_pairwise_time_delays
255 # )
256 #)
257
258
259 if AXIS == 'Rho':
260 result_rho = result
261 elif AXIS == 'Phi':
262 result_phi = result
263 elif AXIS == 'Z':
264 result_z = result
265 elif AXIS == 'CableDelay':
266 result_cabledelay = result
267
268 #_plot_calibration_result(axis=AXIS, before=relevant_antennas, after=resultx, anttype=ant_type)
269
270
271 print("result.fun=", result.fun, "result.success=", result.success)
272 if result.fun < best_value and result.success:
273 best_trace = tmp_trace.copy()
274 best_value = result.fun
275 best_result_rho = result_rho
276 best_result_phi = result_phi
277 best_result_z = result_z
278 best_result_cabledelay = result_cabledelay
279 print("temp best_value=", best_value)
280 print("temp best result=", best_result_rho.x)
281
282 ax1.set_xlabel("measured - expected (ns)")
283 ax1.set_ylabel("Counts")
284 ax1.set_title("Residual time delays")
285 ax1.grid(True, alpha=0.3)
286 ax1.legend()
287 fig1.tight_layout()
288 fig1.savefig(f"plot/{dirname}/{ant_type}step_timediff.png")
289
290 tmp_trace.clear()
291
292 resultList = [best_result_rho, best_result_phi, best_result_z, best_result_cabledelay]
293 for i, AXIS in enumerate(["Rho", "Phi", "Z", "CableDelay"]): #["Rho", "Phi", "Z", "CableDelay"]
294
295 resultx_full_axis = np.insert(resultList[i].x, 0, fixed_antenna[AXIS])
296 print(f"{AXIS} resultx_full_axis=", resultx_full_axis)
297 _plot_calibration_result(axis=AXIS, before=relevant_antennas, after=resultx_full_axis, anttype=ant_type)
298
299 print("best_value=", best_value)
300
301 print("best_trace=", best_trace)
302 plt.figure(figsize=(8,5))
303 step = len(best_trace)
304 if step>1:
305 plt.plot(np.arange(step), np.sqrt(np.asarray(best_trace))/N_pairs)
306 plt.yscale('log')
307 plt.grid()
308 plt.xlabel("Minimization iteration")
309 plt.ylabel("Averging $\Delta$ T per pair (ns)")
310 print(f"plot/{dirname}/{ant_type}best_trace.png")
311 plt.savefig(f"plot/{dirname}/{ant_type}best_trace.png")
312
313