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