Coverage for src/km3dq_common/common_library.py: 0%

402 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-25 11:58 +0000

1#! /usr/bin/env python 

2############################################################################### 

3# Various functions used in km3dq_lw_dq and km3dq_core scripts 

4# 

5# Developer: Benjamin Trocme (benjamin.trocme at apc.in2p3.fr) - 2023 

6 

7import os 

8import sys 

9import time 

10import array 

11import re 

12import urllib.request 

13import km3db 

14 

15from km3dq_common.config_library import configure_var_bit 

16from km3dq_common.config_library import configure_var_thresholds 

17from km3dq_common.config_library import get_detx_caract 

18from km3dq_common.config_library import configure_dataquality_tag 

19from km3dq_common.config_library import WEEKLY_DETECTORS 

20from km3dq_common.lw_db_defect_library import read_defect_file 

21from km3dq_common.lw_db_library import decode_defect_diag_byte 

22 

23 

24############################################################################### 

25def get_file_paths(dataset, qaqc_proc_version, runnumber=0, jra_proc=""): 

26 """ 

27 For a given dataset / detector, returns the path for various files: 

28 JMonitor, JDataQuality, JRunAnalyzer 

29 The run number and jra_proc are relevant only for the JRunAnalyzer files 

30 jra_proc: 

31 - priv: analysis level = 1, patches to TH2D coding to recompute the 

32 mean/RMS of PMT rates 

33 """ 

34 

35 filename = {} 

36 

37 det_id = get_det_id(dataset) 

38 if det_id < 100: 

39 det_id_str = f"000000{det_id}" 

40 else: 

41 det_id_str = f"00000{det_id}" 

42 

43 # JDataMonitor file 

44 if dataset == "D0ARCA021": 

45 filename['JDataMonitorName'] = ("/sps/km3net/repo/data_processing/tag/" 

46 "v8.1/data_processing/DataQuality/" 

47 "KM3NeT_00000133_Monitor_Jpp_v17.3.2" 

48 ".root") 

49 

50 # QAQC file stored on sps 

51 qaqc_sps = "/sps/km3net/repo/data/raw/quality/" 

52 filename['JQAQC_sps'] = qaqc_sps \ 

53 + f"KM3NeT_{det_id_str}_QAQC_Jpp_{qaqc_proc_version}.txt" 

54 

55 # QAQC file stored on sftp 

56 qaqc_htpps = "https://sftp.km3net.de/data/quality/" 

57 filename['JQAQC_sftp'] = qaqc_htpps \ 

58 + f"KM3NeT_{det_id_str}_QAQC_Jpp_{qaqc_proc_version}.txt" 

59 

60 # JDataQuality file - Processing by BT, available on sps 

61 dq_file_path_bt = "/sps/km3net/users/trocme/Output-JDataQuality/" 

62 dataquality_suffix = qaqc_proc_version.split(".", 1)[0] 

63 filename['JDataQualityName'] = dq_file_path_bt\ 

64 + f"{dataset}/KM3NeT_{det_id_str}_QAQC_Jpp_{dataquality_suffix}.root" 

65 

66 # JRA file - Private BT processing - One file per run 

67 jra_file_path_bt = "/sps/km3net/users/trocme/Output-JRunAnalyzer/" 

68 if runnumber != 0: 

69 if jra_proc == "priv": 

70 filename['JRAName'] = \ 

71 f"{jra_file_path_bt}{dataset}/JRA-{runnumber:.0f}.root" 

72 else: # Official production 

73 filename['JRAName'] = ("/sps/km3net/repo/data/raw/quality/" 

74 f"KM3NeT_{det_id_str}/" 

75 f"JRA_KM3NeT_{det_id_str}_000{runnumber}" 

76 ".root") 

77 if os.path.exists(filename['JRAName']) is False: 

78 filename['JRAName'] = "Not found" 

79 

80 return filename 

81 

82 

83############################################################################### 

84def get_run_properties_from_db(det, filt="PHYS"): 

85 """ 

86 Retrieve run properties from the database with the km3db package 

87 """ 

88 sds = km3db.tools.StreamDS() 

89 runs = sds.runs(detid=get_det_id(det)) 

90 lines = runs.split("\n") 

91 

92 # Extract the available data 

93 variable_list = lines[0].split("\t") 

94 nb_variables = len(variable_list) 

95 

96 regex = r"([0-9]+)" 

97 for i_var in range(nb_variables-1): 

98 regex += r"\t(.*)" 

99 regex += "$" 

100 

101 reg_line = re.compile(regex) 

102 

103 results = {} 

104 for i_line in lines: 

105 r_p = reg_line.search(i_line) 

106 if r_p: 

107 if filt not in r_p.group(5): 

108 continue 

109 run_nb = int(r_p.group(1)) 

110 results[run_nb] = {} 

111 for i_var in range(2, nb_variables+1): 

112 results[run_nb][variable_list[i_var-1]] = r_p.group(i_var) 

113 

114 return results 

115 

116 

117############################################################################### 

118def get_run_properties_from_qaqc(dataset, dq_tag, origin="qaqc_sftp", 

119 startrun=0, endrun=1e9): 

120 """ 

121 For a given dataset/detector, returns the run numbers, their lengths, 

122 time counter... 

123 A dataset may be restricted to a user-defined run range 

124 When using the create_ttree_from_qaqc function, a check on the QAQC file 

125 is performed 

126 Source: QAQC file or JDataQualityFile 

127 """ 

128 

129 from ROOT import TFile 

130 

131 runprop = {"runNumber": [], 

132 "lvt": [], 

133 "lvt_counter": [], 

134 "kty": [], 

135 "kty_counter": [], 

136 "nbRuns": 0, "minRun": 1e10, "maxRun": 0} 

137 

138 dataset_time_counter = 0. 

139 scale_sec_days = 1/3600./24. 

140 kty_counter = 0 

141 

142 err_log = "" 

143 if origin == "jdq": # Source: JDataquality 

144 filename_jdq = get_file_paths(dataset, "")["JDataQualityName"] 

145 assert(filename_jdq != "Missing"), \ 

146 print("Missing DataQuality file - " 

147 "Can not retrieve the run properties") 

148 f_dq = TFile(filename_jdq) 

149 n1_tree = f_dq.Get("n1") 

150 elif "qaqc" in origin: # Source: QAQC file on sftp 

151 # QAQC default tag used. As priori OK for this purpose 

152 

153 (n1_tree, err_log) = create_ttree_from_qaqc(dataset, 

154 ["run", "livetime", "kton_year"], 

155 origin, dq_tag) 

156 

157 entries = n1_tree.GetEntriesFast() 

158 for jentry in range(entries): 

159 n1_tree.GetEntry(jentry) # Load event 

160 if (n1_tree.run < startrun) or (n1_tree.run > endrun): 

161 continue 

162 runprop["runNumber"].append(n1_tree.run) 

163 if origin == "JDataQuality": 

164 run_length = n1_tree.a 

165 else: 

166 run_length = n1_tree.livetime 

167 runprop["lvt"].append(run_length*scale_sec_days) 

168 runprop["lvt_counter"].append(dataset_time_counter) 

169 runprop["kty"].append(n1_tree.kton_year) 

170 runprop["kty_counter"].append(kty_counter) 

171 

172 dataset_time_counter = dataset_time_counter + run_length*scale_sec_days 

173 kty_counter = kty_counter + n1_tree.kton_year 

174 runprop["nbRuns"] = runprop["nbRuns"] + 1 

175 if n1_tree.run < runprop["minRun"]: 

176 runprop["minRun"] = n1_tree.run 

177 if n1_tree.run > runprop["maxRun"]: 

178 runprop["maxRun"] = n1_tree.run 

179 

180 # Add the last time counter to define TH1 with a vector 

181 if len(runprop["lvt_counter"]) > 0: 

182 runprop["lvt_counter"].append(runprop["lvt_counter"][-1] 

183 + runprop["lvt"][-1]) 

184 if len(runprop["kty_counter"]) > 0: 

185 runprop["kty_counter"].append(runprop["kty_counter"][-1] 

186 + runprop["kty"][-1]) 

187 

188 if origin == "JDataQuality": 

189 f_dq.Close() 

190 

191 return (runprop, err_log) 

192 

193 

194############################################################################### 

195def get_last_n_weeks_runs_qaqc(dataset, nweeks=2): 

196 """ Extract the all runs acquired in the last nweeks of running """ 

197 

198 # QAQC default tag used. As priori OK for this purpose 

199 dq_tag = configure_dataquality_tag("default") 

200 lines = read_qaqc_file(dataset, "qaqc_sftp", dq_tag['qaqc_vers'][dataset]) 

201 min_run = 1e9 

202 max_run = 0 

203 

204 first_line = True 

205 for i in lines: 

206 i_split = i.split(" ") 

207 if first_line: 

208 run_number_index = i_split.index("run") 

209 utc_min_s_index = i_split.index("UTCMin_s") 

210 first_line = False 

211 else: 

212 if len(i_split) > utc_min_s_index: 

213 ten_days = (nweeks * 7 + 1) * 3600. * 24. 

214 if (time.time() - float(i_split[utc_min_s_index])) < ten_days: 

215 run_number = int(i_split[run_number_index]) 

216 if run_number < min_run: 

217 min_run = run_number 

218 if run_number > max_run: 

219 max_run = run_number 

220 

221 return (min_run, max_run) 

222 

223 

224############################################################################### 

225def get_full_detector_name(options, key="detector"): 

226 """ 

227 Get the full detector name with the prefix (D0, D_...)  

228 when there is no ambiguity 

229 

230 The input is the options dict. 

231 NB: options['detector'] can be either a single list either a list of 

232 detector/strings. 

233 """ 

234 

235 names = {"D0ARCA009": ["ARCA009", "ARCA9"], 

236 "D0ARCA020": ["ARCA020", "ARCA20"], 

237 "D0ARCA021": ["ARCA021", "ARCA21"], 

238 "D0ARCA028": ["ARCA028", "ARCA28"], 

239 "D_ORCA006": ["ORCA006", "ORCA6"], 

240 "D0ORCA007": ["ORCA007", "ORCA7"], 

241 "D0ORCA010": ["ORCA010", "ORCA10"], 

242 "D0ORCA011": [], 

243 "D1ORCA011": [], 

244 "D1ORCA013": ["ORCA013", "ORCA13"], 

245 "D0ORCA015": [], 

246 "D1ORCA015": [], 

247 "D0ORCA018": ["ORCA018"], 

248 "D1ORCA019": ["ORCA019"], 

249 "D0ORCA023": ["ORCA023"]} 

250 

251 # A and O are used to refer to the current detector 

252 names[WEEKLY_DETECTORS["ARCA"]].append("A") 

253 names[WEEKLY_DETECTORS["ORCA"]].append("O") 

254 

255 # Single detector 

256 if isinstance(options[key], str): 

257 if options[key] not in names: 

258 full_name_found = False 

259 for i, name in names.items(): 

260 for j in name: 

261 if options[key] == j: 

262 options[key] = i 

263 print(f"I am redefining the detector name as {i}") 

264 full_name_found = True 

265 if not full_name_found: 

266 print("Ambiguous %s detector. Please use the full " 

267 + "name D*ORCA*** or D*ARCA***") 

268 sys.exit() 

269 # List of detectors 

270 elif isinstance(options[key], list): 

271 for k in range(0, len(options[key])): 

272 if options[key][k] not in names: 

273 full_name_found = False 

274 for i, name in names.items(): 

275 for j in name: 

276 if options[key][k] == j: 

277 options[key][k] = i 

278 print(f"I am redefining the detector name as {i}") 

279 full_name_found = True 

280 if not full_name_found: 

281 print(f"Ambiguous {options['detector'][k]} detector." 

282 f"Please use the full" 

283 f"name D*ORCA*** or D*ARCA***") 

284 sys.exit() 

285 

286 

287############################################################################### 

288def get_det_id(dataset): 

289 """ Return the detector id """ 

290 

291 det_id = {"D0ARCA009": 94, 

292 "D0ARCA020": 116, 

293 "D0ARCA021": 133, 

294 "D0ARCA028": 160, 

295 "D_ORCA006": 49, 

296 "D0ORCA007": 110, 

297 "D0ORCA010": 100, 

298 "D1ORCA013": 117, 

299 "D0ORCA011": 123, 

300 "D1ORCA011": 132, 

301 "D0ORCA015": 138, 

302 "D1ORCA015": 146, 

303 "D0ORCA018": 148, 

304 "D1ORCA019": 172, 

305 "D0ORCA023": 196} 

306 

307 return det_id[dataset] 

308 

309 

310############################################################################### 

311def get_site(det): 

312 """ Returns the site (ORCA or ARCA) """ 

313 

314 if "ORCA" in det: 

315 out = "ORCA" 

316 else: 

317 out = "ARCA" 

318 return out 

319 

320 

321############################################################################### 

322def get_active_dus_range(det): 

323 """ Retrieve the range of DU active """ 

324 lower_du = 0 

325 upper_du = {"D0ARCA009": 32, 

326 "D0ARCA020": 32, 

327 "D0ARCA021": 32, 

328 "D0ARCA028": 32, 

329 "D_ORCA006": 32, 

330 "D0ORCA007": 32, 

331 "D0ORCA010": 32, 

332 "D1ORCA013": 32, 

333 "D0ORCA011": 32, 

334 "D1ORCA011": 32, 

335 "D0ORCA015": 32, 

336 "D1ORCA015": 32, 

337 "D0ORCA018": 32, 

338 "D1ORCA019": 42, 

339 "D0ORCA023": 42} 

340 

341 return (lower_du, upper_du[det]) 

342 

343 

344############################################################################### 

345def get_nb_qaqc_variables(qaqc_vers): 

346 """ 

347 Retrieve the number of variables in the QAQC file 

348 NB: in a near future, the det argument should be replaced by a Jpp version 

349 === Arguments === 

350 - det : detector name - [string] - Ex: "D0ARCA021", "D0ORCA018"... 

351 

352 === Output === 

353 - Number of QAQC variables 

354 

355 """ 

356 if "v16.0.3" in qaqc_vers: 

357 return 39 

358 if "v17.3.2" in qaqc_vers: 

359 return 42 

360 

361 return 45 

362 

363 

364############################################################################### 

365def create_ttree_from_qaqc(det, var_to_fill, source, tag, append_veto_qsco=False): 

366 """ 

367 Create a ttree from qaqc sftp file and defect variables stored on git 

368 It includes some advanced check about the QAQC file integrity. 

369 === Arguments === 

370 - det : detector name - [string] - Ex: "D0ARCA021", "D0ORCA018"... 

371 - var_to_fill : QAQC variables or defect to fill - [array of string] - 

372 Ex: ['run', 'timestampdiff', 'def_operation', 'def_oos'] 

373 - source : QAQC source, a priori "qaqc_sftp" - [string] 

374 - tag : data-quality tag (not its name) as created by 

375 configure_dataquality_tag 

376 - append_veto_qsco: append the veto and Qscore - [boolean] 

377 

378 === Output === 

379 - TTree 

380 - Error log 

381 

382 """ 

383 

384 from ROOT import TTree 

385 

386 error_log = "" 

387 

388 # Correspondance between the name in the QAQC file (first line) and the 

389 # variable name in the script 

390 # In the case of empty variable, this corresponds to "composite" variable 

391 # derived from the primary variables with an explicit computation in the 

392 # function 

393 

394 variable_name_corresp = {"git": "GIT", 

395 "jpp": "JPP", 

396 "nb_of_meta": "nb_of_meta", 

397 "uuid": "UUID", 

398 "detectorid": "detector", 

399 "run": "run", 

400 "livetime": "livetime_s", 

401 "utcmin": "UTCMin_s", 

402 "utcmax": "UTCMax_s", 

403 "trigger3dmuon": "trigger3DMuon", 

404 "trigger3dshower": "trigger3DShower", 

405 "triggermxshower": "triggerMXShower", 

406 "triggernb": "triggerNB", 

407 "writel0": "writeL0", 

408 "writel1": "writeL1", 

409 "writel2": "writeL2", 

410 "writesn": "writeSN", 

411 "jdaqtimeslice": "JDAQTimeslice", 

412 "jdaqtimeslicel0": "JDAQTimesliceL0", 

413 "jdaqtimeslicel1": "JDAQTimesliceL1", 

414 "jdaqtimeslicel2": "JDAQTimesliceL2", 

415 "jdaqtimeslicesn": "JDAQTimesliceSN", 

416 "jdaqsummaryslice": "JDAQSummaryslice", 

417 "jdaqevent": "JDAQEvent", 

418 "jtriggerreprocessor": "JTriggerReprocessor", 

419 "jtrigger3dshower": "JTrigger3DShower", 

420 "jtriggermxshower": "JTriggerMXShower", 

421 "jtrigger3dmuon": "JTrigger3DMuon", 

422 "jtriggernb": "JTriggerNB", 

423 "in_sync": "in_sync", 

424 "oos": "out_sync", 

425 "daq": "DAQ", 

426 "whiterabbitstatus": "WR", 

427 "hrv": "HRV", 

428 "fifo": "FIFO", 

429 "pmts": "PMTs", 

430 "meanrate": "MEAN_Rate_Hz", 

431 "rmsrate": "RMS_Rate_Hz", 

432 "hrv_fifo_failures": "hrv_fifo_failures", 

433 "duplic_timeslices": "duplic_timeslices", 

434 "Acoustics": "Acoustics", 

435 "AHRS": "AHRS", 

436 "in_usync": "in_usync", 

437 "out_usync": "out_usync", 

438 "event_duration": "event_duration", 

439 "timestampdiff": "", 

440 "timestampdiff_r": "", 

441 "triggerrate": "", 

442 "pmts_norm": "", 

443 "acoustics": "", 

444 "ahrs_per_mn": "", 

445 "JDAQJTProc": "", 

446 "kton_year": ""} 

447 

448 if len(var_to_fill) == 0: 

449 var_to_fill = list(variable_name_corresp.keys()) 

450 

451 if tag["qaqc_vers"][det] == "v16.0.3": 

452 # Processing version 16: name change 

453 # 3 missing variables 

454 variable_name_corresp['oos'] = "out-sync" 

455 variable_name_corresp['in_sync'] = "in-sync" 

456 for i_missing in ["in_usync", "out_usync", "event_duration"]: 

457 variable_name_corresp[i_missing] = "MISSING" 

458 if i_missing in var_to_fill: 

459 var_to_fill.remove(i_missing) 

460 

461 if any((tag["qaqc_vers"][det] == "v16.0.3", 

462 tag["qaqc_vers"][det] == "v17.3.2")): 

463 # Processing version 16/17: 3 other missing variables 

464 for i_missing in ["nb_of_meta", "hrv_fifo_failures", "duplic_timeslices"]: 

465 variable_name_corresp[i_missing] = "MISSING" 

466 if i_missing in var_to_fill: 

467 var_to_fill.remove(i_missing) 

468 

469 variable_float = ["livetime", "daq", "whiterabbitstatus", "fifo", "hrv", 

470 "pmts", "pmts_norm", "timestampdiff", "timestampdiff_r", 

471 "triggerrate", "acoustics", 

472 "ahrs_per_mn", "JDAQJTProc", "kton_year"] 

473 

474 var_properties = {} 

475 configure_var_thresholds(var_properties, det, tag) 

476 configure_var_bit(var_properties) 

477 

478 new_tree = TTree("t1", "t1") 

479 

480 lines = read_qaqc_file(det, source, tag['qaqc_vers'][det]) 

481 defect_results = read_defect_file(det, tag['def_tag']) 

482 

483 # Define the regular expression for the QAQC file 

484 regex0 = r"" 

485 nb_qaqc_variables = get_nb_qaqc_variables(tag['qaqc_vers'][det]) 

486 for i in range(nb_qaqc_variables - 1): 

487 regex0 += r"\s*(\S+)\s+" 

488 regex0 += r"(\S+)\n*\Z" 

489 qaqc_pattern = re.compile(regex0) 

490 #################################################################### 

491 # Check the regular expression of the first line of the QAQC file to 

492 # retrieve the list of available variables 

493 qaqc_first_line = qaqc_pattern.match(lines[0]) 

494 if not qaqc_first_line: 

495 error_log += "Wrong regex for the list of variable of the QAQC file\n" 

496 error_log += "Exiting..." 

497 return (new_tree, error_log) 

498 

499 var_list_qaqc = [] 

500 for i_var in range(1, nb_qaqc_variables + 1): 

501 var_list_qaqc.append(qaqc_first_line.group(i_var)) 

502 # var_list_qaqc = (lines[0].replace("\n", "")).split(" ") 

503 

504 qaqc_index = {} 

505 var_value = {} 

506 ###################################################################### 

507 # Loop on all QAQC variables to fill, create all relevant branches and 

508 # associate them to the array used for tree filling. 

509 for i_var in var_to_fill: 

510 if i_var.startswith("def_") is True: 

511 continue # Defect variable are treated hereafter 

512 if variable_name_corresp[i_var] == "MISSING": 

513 # Missing variable (in v16 and v17 compared to v19) 

514 continue 

515 if any((variable_name_corresp[i_var] in var_list_qaqc, 

516 variable_name_corresp[i_var] == "")): 

517 if variable_name_corresp[i_var] in var_list_qaqc: 

518 qaqc_index[i_var] = var_list_qaqc\ 

519 .index(variable_name_corresp[i_var]) 

520 else: 

521 qaqc_index[i_var] = 1000 # Composite variable 

522 if i_var in variable_float: 

523 var_value[i_var] = array.array('f', [0.]) 

524 else: 

525 var_value[i_var] = array.array('i', [0]) 

526 if i_var in variable_float: 

527 new_tree.Branch(f"{i_var}", var_value[i_var], f"{i_var}/F") 

528 else: 

529 new_tree.Branch(f"{i_var}", var_value[i_var], f"{i_var}/i") 

530 else: 

531 err = (f"ERROR: {variable_name_corresp[i_var]}" 

532 f" not in QAQC fill -> Exiting") 

533 error_log = error_log + err 

534 if error_log != "": 

535 print(error_log) 

536 return (new_tree, error_log) 

537 

538 ######################################################################## 

539 # Loop on all defect variables to fill, create all relevant branches and 

540 # associate them to the array used for tree filling. 

541 for i_var in var_to_fill: 

542 if i_var.startswith("def_") is False: 

543 continue # QAQC variable are treated herebefore 

544 var_value[i_var] = array.array('i', [0]) 

545 new_tree.Branch(f"{i_var}", var_value[i_var], f"{i_var}/i") 

546 

547 ########################################### 

548 # Create the branch to fill the veto/Qscore 

549 if append_veto_qsco: 

550 for i_var in ("veto", "veto_source", "qsco_source"): 

551 var_value[i_var] = array.array('i', [0]) 

552 new_tree.Branch(f"{i_var}", var_value[i_var], f"{i_var}/i") 

553 var_value['qsco'] = array.array('f', [0]) 

554 new_tree.Branch("qsco", var_value['qsco'], "qsco/f") 

555 

556 ####################################################################### 

557 # Extract the QAQC index of the QAQC variables used for the computation 

558 # of composite variables 

559 for i_var in ["UTCMax_s", "UTCMin_s", "livetime_s", "JDAQEvent", "PMTs", 

560 "Acoustics", "AHRS", "JTriggerReprocessor", "HRV"]: 

561 if i_var in var_list_qaqc: 

562 qaqc_index[i_var] = var_list_qaqc.index(i_var) 

563 else: 

564 err = f"Missing {i_var} in QAQC file" 

565 error_log = error_log + err 

566 qaqc_index[i_var] = -1 

567 

568 ##################################### 

569 # Loop on all runs to fill the arrays 

570 run_stored = [] 

571 for i_line in range(1, len(lines)): 

572 if lines[i_line] == "": 

573 continue 

574 qaqc_regex = qaqc_pattern.match(lines[i_line]) 

575 # Bad line format -> Line skipped 

576 if not qaqc_regex: 

577 error_log += ("QAQC line with a bad format:" 

578 f"{lines[i_line]}") 

579 error_log += "->Line skipped\n" 

580 if error_log != "": 

581 print(error_log) 

582 continue 

583 

584 var_value_re = [] 

585 for i in range(1, nb_qaqc_variables + 1): 

586 var_value_re.append(qaqc_regex.group(i)) 

587 

588 # Run duplicated in QAQC file -> Line skipped 

589 run_number = var_value_re[qaqc_index["run"]] 

590 if run_number in run_stored: 

591 if f"Duplicated run {run_number}" not in error_log: 

592 error_log += f"Duplicated run {run_number} in QAQC file" 

593 error_log += "(1st occurence kept)\n" 

594 continue 

595 

596 # Run with a zero livetime -> Line skipped 

597 livetime = float(var_value_re[qaqc_index["livetime_s"]]) 

598 if livetime == 0.: 

599 if f"Run {run_number} has a livetime = 0" not in error_log: 

600 error_log += f"Run {run_number} has a livetime = 0" 

601 continue 

602 

603 # Filling the QAQC variable values 

604 for i_var in var_to_fill: 

605 if i_var.startswith("def_") is True: 

606 continue # Defect variable are treated hereafter 

607 

608 if qaqc_index[i_var] < len(var_value_re): 

609 str_fl = var_value_re[qaqc_index[i_var]] 

610 if i_var in variable_float: 

611 try: 

612 var_value[i_var][0] = float(str_fl) 

613 except ValueError: 

614 err = (f"Corrupted variable in QAQC file: {i_var}\n " 

615 f"- {lines[i_line]}\n") 

616 error_log = error_log + (f"{err}\n") 

617 continue 

618 else: 

619 try: 

620 var_value[i_var][0] = int(str_fl.split(".")[0]) 

621 except ValueError: 

622 err = (f"Corrupted variable {i_var} found in qaqc file" 

623 f"- {lines[i_line]} -" 

624 "Variable skipped for this run") 

625 error_log += f"{err}\n" 

626 continue 

627 

628 elif qaqc_index[i_var] == 1000: # Composite variable 

629 tmp_utcmax = float(var_value_re[qaqc_index["UTCMax_s"]]) 

630 tmp_utcmin = float(var_value_re[qaqc_index["UTCMin_s"]]) 

631 tmp_livetime = float(var_value_re[qaqc_index["livetime_s"]]) 

632 tmp_pmts = float(var_value_re[qaqc_index["PMTs"]]) 

633 tmp_JDAQEvent = float(var_value_re[qaqc_index["JDAQEvent"]]) 

634 tmp_Acoustics = float(var_value_re[qaqc_index["Acoustics"]]) 

635 tmp_AHRS = float(var_value_re[qaqc_index["AHRS"]]) 

636 tmp_JTriggerReprocessor = \ 

637 float(var_value_re[qaqc_index["JTriggerReprocessor"]]) 

638 tmp_kton_year = 6980 / (31 * 18 * 115) \ 

639 * float(var_value_re[qaqc_index["PMTs"]]) \ 

640 * (1 - float(var_value_re[qaqc_index["HRV"]])) \ 

641 * tmp_livetime / (365.2425 * 24 * 3600) 

642 if i_var == "timestampdiff": 

643 var_value[i_var][0] = (tmp_utcmax - tmp_utcmin)\ 

644 - tmp_livetime 

645 if i_var == "timestampdiff_r": 

646 var_value[i_var][0] = 1. - tmp_livetime / \ 

647 (tmp_utcmax - tmp_utcmin) 

648 if i_var == "pmts_norm": # NOT YET NORMALIZED :-( 

649 var_value[i_var][0] = tmp_pmts / \ 

650 get_detx_caract(det)['pmts'] 

651 elif i_var == "triggerrate": 

652 var_value[i_var][0] = tmp_JDAQEvent / tmp_livetime 

653 elif i_var == "acoustics": 

654 var_value[i_var][0] = tmp_Acoustics 

655 elif i_var == "ahrs_per_mn": 

656 var_value[i_var][0] = tmp_AHRS / tmp_livetime * 60 

657 elif i_var == "JDAQJTProc": 

658 var_value[i_var][0] = \ 

659 (tmp_JDAQEvent - tmp_JTriggerReprocessor) / \ 

660 (tmp_JDAQEvent + 1.0e-10) 

661 elif i_var == "kton_year": 

662 var_value[i_var][0] = tmp_kton_year 

663 else: 

664 err = (f"Corrupted lines found in qaqc file - {lines[i_line]}" 

665 " - Line skipped") 

666 error_log = error_log + (f"{err}\n") 

667 

668 # Filling the QAQC variable values 

669 for i_var in var_to_fill: 

670 if i_var.startswith("def_") is False: 

671 continue # QAQC variable are treated herebefore 

672 var_value[i_var][0] = 0 # No defect 

673 if int(run_number) in list(defect_results[i_var].keys()): 

674 var_value[i_var][0] = defect_results[i_var][int(run_number)] 

675 

676 if append_veto_qsco: 

677 (var_value['veto'][0], var_value['veto_source'][0], 

678 var_value['qsco'][0], var_value['qsco_source'][0]) = \ 

679 compute_veto_qscore(var_properties, var_value) 

680 

681 run_stored.append(run_number) 

682 

683 new_tree.Fill() 

684 

685 new_tree.ResetBranchAddresses() 

686 

687 if error_log != "": 

688 print(error_log) 

689 return (new_tree, error_log) 

690 

691 

692############################################################################### 

693def compute_veto_qscore(var_prop, var_val): 

694 """ 

695 Computes the veto/Q-score for a detector/tag using a QAQC source. 

696 """ 

697 

698 veto = False 

699 veto_source = 0b0 

700 

701 qsco = 1. 

702 qsco_source = 0b0 

703 

704 for i_var in var_prop['veto_thresholds']: 

705 # Defect variables 

706 if i_var.startswith("def_"): 

707 # first extract the set defects 

708 decoded_defects = decode_defect_diag_byte(var_val[i_var][0], i_var) 

709 # Then loop on the various defects used in veto 

710 # Treatement of NOT. defect added on February, 2. Not fully tested. 

711 for i_excluded_def in var_prop['veto_thresholds'][i_var]: 

712 defect_test = False 

713 if i_excluded_def in decoded_defects: 

714 defect_test = True 

715 if i_excluded_def.startswith("NOT."): 

716 if i_excluded_def.replace("NOT.", "") not in decoded_defects: 

717 defect_test = True 

718 

719 if defect_test: 

720 veto = True 

721 veto_source = veto_source | 0b1 << var_prop['bit'][i_var] 

722 continue 

723 

724 # QAQC variables 

725 if any((var_val[i_var][0] < var_prop['veto_thresholds'][i_var][0], 

726 var_val[i_var][0] > var_prop['veto_thresholds'][i_var][1])): 

727 veto = True 

728 veto_source += 0b1 << var_prop['bit'][i_var] 

729 

730 for i_var in var_prop['qsco_thresholds']: 

731 # Defect variables 

732 if i_var.startswith("def_"): 

733 # first extract the set defects 

734 decoded_defects = decode_defect_diag_byte(var_val[i_var][0], i_var) 

735 # Then loop on the various defects used in veto 

736 for i_excluded_def in var_prop['qsco_thresholds'][i_var]: 

737 if i_excluded_def in decoded_defects: 

738 qsco -= 1./float(len(var_prop['qsco_thresholds'])) 

739 qsco_source = qsco_source | 0b1 << var_prop['bit'][i_var] 

740 continue 

741 

742 if any((var_val[i_var][0] < var_prop['qsco_thresholds'][i_var][0], 

743 var_val[i_var][0] > var_prop['qsco_thresholds'][i_var][1])): 

744 qsco -= 1./float(len(var_prop['qsco_thresholds'])) 

745 qsco_source += 0b1 << var_prop['bit'][i_var] 

746 

747 return (int(veto), int(veto_source), qsco, int(qsco_source)) 

748 

749 

750############################################################################### 

751def log_run_range(det, run_0, run_1, log_file): 

752 """ Write the run range and run list on disk """ 

753 

754 os.environ['TZ'] = 'Europe/Paris' 

755 time.tzset() 

756 

757 run_prop = get_run_properties_from_db(det) 

758 

759 if run_0 in run_prop.keys(): 

760 tim_0_unix = int(run_prop[run_0]['UNIXJOBSTART']) 

761 tim_0 = time.strftime("%a, %d %b %Y %H:%M", 

762 time.localtime(tim_0_unix/1000)) 

763 else: 

764 tim_0 = "Unknown" 

765 

766 if run_1 in run_prop.keys(): 

767 tim_1_unix = int(run_prop[run_1]['UNIXJOBEND']) 

768 tim_1 = time.strftime("%a, %d %b %Y %H:%M", 

769 time.localtime(tim_1_unix/1000)) 

770 else: 

771 tim_1 = "Unknown" 

772 

773 run_range = (f"{int(run_0)} ({tim_0} CET/CEST) - " 

774 f"{int(run_1)} ({tim_1} CET/CEST)") 

775 

776 log_file.write(f"Run range: {run_range}\n") 

777 

778 

779############################################################################### 

780def read_qaqc_file(det, source="qaqc_sftp", qaqc_proc_version=""): 

781 """ 

782 sftp QAQC file reading 

783 """ 

784 

785 lines = [] 

786 if source == "qaqc_sftp": 

787 with urllib.request.urlopen( 

788 get_file_paths(det, qaqc_proc_version)["JQAQC_sftp"]) as qaqc: 

789 tmp = ((qaqc.read()).split(b"\n")) 

790 for i_line in tmp: 

791 if i_line != "": 

792 lines.append(i_line.decode("utf-8")) 

793 else: 

794 if source == "qaqc_sps": 

795 source_file = get_file_paths(det, qaqc_proc_version)["JQAQC_sps"] 

796 else: 

797 source_file = source 

798 

799 with open(source_file, "r", encoding="utf-8") as s_f: 

800 lines = s_f.readlines() 

801 

802 return lines 

803 

804 

805############################################################################### 

806def decode_source_byte(value, v_prop): 

807 """ 

808 Decode the source of degradation stored in the TTree 

809 NB: the source is either a QAQC variable either a defect type (("daq", 

810 "operation"...) 

811 """ 

812 

813 from ROOT import TTree 

814 

815 source_list = [] 

816 source_str = "" 

817 

818 for (i_var, i_bit) in v_prop['bit'].items(): 

819 if (int(value) >> i_bit) & 1: 

820 source_list.append(i_var) 

821 source_str += f"{i_var} " 

822 

823 return (source_list, source_str)