Coverage for event_normalizer/models.py: 100%

242 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-01 10:03 +0000

1""" 

2Unified NormalizedEvent model for gravitational wave events. 

3""" 

4 

5import math 

6from datetime import datetime 

7from typing import Literal, Union 

8 

9from pydantic import BaseModel, ConfigDict, Field, computed_field, model_validator 

10 

11 

12class StrictModel(BaseModel): 

13 model_config = ConfigDict(extra="ignore") 

14 

15 

16class EventCoinc(StrictModel): 

17 coinc_mass: Union[float, None] = Field( 

18 None, description="Total mass (solar masses)" 

19 ) 

20 coinc_mchirp: Union[float, None] = Field( 

21 None, description="Chirp mass (solar masses)" 

22 ) 

23 coinc_minimum_duration: Union[float, None] = Field( 

24 None, description="Minimum duration (seconds)" 

25 ) 

26 coinc_snr: Union[float, None] = Field(None, description="Signal-to-Noise Ratio") 

27 coinc_ifos: Union[str, None] = Field(None, description="Interferometers (H1,L1,V1)") 

28 coinc_end_time: Union[int, None] = Field(None, description="End time (GPS seconds)") 

29 coinc_end_time_ns: Union[int, None] = Field( 

30 None, description="End time nanoseconds" 

31 ) 

32 coinc_false_alarm_rate: Union[float, None] = Field( 

33 None, description="False alarm rate" 

34 ) 

35 coinc_combined_far: Union[float, None] = Field(None, description="Combined FAR") 

36 

37 

38class EventBurst(StrictModel): 

39 burst_duration: Union[float, None] = Field(None, description="Duration (seconds)") 

40 burst_mchirp: Union[float, None] = Field( 

41 None, description="Chirp mass (solar masses)" 

42 ) 

43 burst_snr: Union[float, None] = Field(None, description="Signal-to-Noise Ratio") 

44 burst_ifos: Union[str, None] = Field(None, description="Interferometers") 

45 burst_start_time: Union[int, None] = Field( 

46 None, description="Start time (GPS seconds)" 

47 ) 

48 burst_start_time_ns: Union[int, None] = Field( 

49 None, description="Start time nanoseconds" 

50 ) 

51 burst_strain: Union[float, None] = Field(None, description="Strain amplitude") 

52 burst_peak_time: Union[int, None] = Field( 

53 None, description="Peak time (GPS seconds)" 

54 ) 

55 burst_peak_time_ns: Union[int, None] = Field( 

56 None, description="Peak time nanoseconds" 

57 ) 

58 burst_central_freq: Union[float, None] = Field( 

59 None, description="Central frequency (Hz)" 

60 ) 

61 burst_bandwidth: Union[float, None] = Field(None, description="Bandwidth (Hz)") 

62 burst_amplitude: Union[float, None] = Field(None, description="Amplitude") 

63 burst_confidence: Union[float, None] = Field(None, description="Confidence") 

64 burst_false_alarm_rate: Union[float, None] = Field( 

65 None, description="False alarm rate" 

66 ) 

67 burst_ligo_axis_ra: Union[float, None] = Field( 

68 None, description="LIGO axis RA (deg)" 

69 ) 

70 burst_ligo_axis_dec: Union[float, None] = Field( 

71 None, description="LIGO axis Dec (deg)" 

72 ) 

73 burst_ligo_angle: Union[float, None] = Field(None, description="LIGO angle") 

74 burst_ligo_angle_sig: Union[float, None] = Field( 

75 None, description="LIGO angle sigma" 

76 ) 

77 burst_single_ifo_times: Union[str, None] = Field( 

78 None, description="Single IFO times" 

79 ) 

80 burst_code: Union[str, None] = Field(None, description="Pipeline code") 

81 

82 

83class EventMLyBurst(StrictModel): 

84 """MLy-specific burst fields.""" 

85 

86 mly_central_freq: Union[float, None] = Field( 

87 None, description="Central frequency (Hz)" 

88 ) 

89 mly_bandwidth: Union[float, None] = Field(None, description="Bandwidth (Hz)") 

90 mly_central_time: Union[float, None] = Field(None, description="Central time (GPS)") 

91 mly_detection_statistic: Union[float, None] = Field( 

92 None, description="Detection statistic" 

93 ) 

94 mly_duration: Union[float, None] = Field(None, description="Duration") 

95 mly_bbh: Union[float, None] = Field(None, description="BBH probability") 

96 mly_sglf: Union[float, None] = Field(None, description="SG low frequency") 

97 mly_sghf: Union[float, None] = Field(None, description="SG high frequency") 

98 mly_background: Union[float, None] = Field(None, description="Background") 

99 mly_glitch: Union[float, None] = Field(None, description="Glitch probability") 

100 mly_freq_correlation: Union[float, None] = Field( 

101 None, description="Frequency correlation" 

102 ) 

103 mly_mass1: Union[float, None] = Field(None, description="Mass 1 (solar masses)") 

104 mly_mass2: Union[float, None] = Field(None, description="Mass 2 (solar masses)") 

105 mly_mtotal: Union[float, None] = Field( 

106 None, description="Total mass (solar masses)" 

107 ) 

108 mly_mchirp: Union[float, None] = Field( 

109 None, description="Chirp mass (solar masses)" 

110 ) 

111 mly_spin1z: Union[float, None] = Field(None, description="Spin 1 z-component") 

112 mly_spin2z: Union[float, None] = Field(None, description="Spin 2 z-component") 

113 mly_end_time: Union[int, None] = Field(None, description="End time (GPS seconds)") 

114 mly_end_time_ns: Union[int, None] = Field(None, description="End time nanoseconds") 

115 mly_template_duration: Union[float, None] = Field( 

116 None, description="Template duration (s)" 

117 ) 

118 mly_SNR: Union[float, None] = Field(None, description="Signal-to-Noise Ratio") 

119 mly_scores_coherency: Union[float, None] = Field( 

120 None, description="Coherency score" 

121 ) 

122 mly_scores_coincidence: Union[float, None] = Field( 

123 None, description="Coincidence score" 

124 ) 

125 mly_scores_combined: Union[float, None] = Field(None, description="Combined score") 

126 

127 

128class EventSingleInspiral(StrictModel): 

129 """Single Inspiral fields for one IFO.""" 

130 

131 # Identifiers 

132 single_search: Union[str, None] = Field(None, description="Search type") 

133 

134 # Time 

135 single_end_time: Union[int, None] = Field( 

136 None, description="End time (GPS seconds)" 

137 ) 

138 single_end_time_ns: Union[int, None] = Field( 

139 None, description="End time nanoseconds" 

140 ) 

141 single_end_time_gmst: Union[float, None] = Field(None, description="End time GMST") 

142 single_impulse_time: Union[int, None] = Field(None, description="Impulse time") 

143 single_impulse_time_ns: Union[int, None] = Field( 

144 None, description="Impulse time ns" 

145 ) 

146 single_template_duration: Union[float, None] = Field( 

147 None, description="Template duration (s)" 

148 ) 

149 single_event_duration: Union[float, None] = Field( 

150 None, description="Event duration (s)" 

151 ) 

152 

153 single_amplitude: Union[float, None] = Field(None, description="Amplitude") 

154 single_eff_distance: Union[float, None] = Field( 

155 None, description="Effective distance (Mpc)" 

156 ) 

157 single_coa_phase: Union[float, None] = Field(None, description="Coalescence phase") 

158 single_mass1: Union[float, None] = Field(None, description="Mass 1 (solar masses)") 

159 single_mass2: Union[float, None] = Field(None, description="Mass 2 (solar masses)") 

160 single_mchirp: Union[float, None] = Field( 

161 None, description="Chirp mass (solar masses)" 

162 ) 

163 single_mtotal: Union[float, None] = Field( 

164 None, description="Total mass (solar masses)" 

165 ) 

166 single_eta: Union[float, None] = Field(None, description="Symmetric mass ratio") 

167 single_kappa: Union[float, None] = Field(None, description="Kappa parameter") 

168 single_chi: Union[float, None] = Field(None, description="Chi parameter") 

169 

170 # Tau parameters 

171 single_tau0: Union[float, None] = Field(None, description="Tau 0") 

172 single_tau2: Union[float, None] = Field(None, description="Tau 2") 

173 single_tau3: Union[float, None] = Field(None, description="Tau 3") 

174 single_tau4: Union[float, None] = Field(None, description="Tau 4") 

175 single_tau5: Union[float, None] = Field(None, description="Tau 5") 

176 single_ttotal: Union[float, None] = Field(None, description="Total tau") 

177 

178 # Psi parameters 

179 single_psi0: Union[float, None] = Field(None, description="Psi 0") 

180 single_psi3: Union[float, None] = Field(None, description="Psi 3") 

181 

182 # Alpha parameters 

183 single_alpha: Union[float, None] = Field(None, description="Alpha") 

184 single_alpha1: Union[float, None] = Field(None, description="Alpha 1") 

185 single_alpha2: Union[float, None] = Field(None, description="Alpha 2") 

186 single_alpha3: Union[float, None] = Field(None, description="Alpha 3") 

187 single_alpha4: Union[float, None] = Field(None, description="Alpha 4") 

188 single_alpha5: Union[float, None] = Field(None, description="Alpha 5") 

189 single_alpha6: Union[float, None] = Field(None, description="Alpha 6") 

190 

191 # Beta 

192 single_beta: Union[float, None] = Field(None, description="Beta") 

193 

194 single_f_final: Union[float, None] = Field(None, description="Final frequency (Hz)") 

195 

196 # SNR e chi-square 

197 single_snr: Union[float, None] = Field(None, description="Signal-to-Noise Ratio") 

198 single_chisq: Union[float, None] = Field(None, description="Chi-square") 

199 single_chisq_dof: Union[int, None] = Field(None, description="Chi-square DOF") 

200 single_bank_chisq: Union[float, None] = Field(None, description="Bank chi-square") 

201 single_bank_chisq_dof: Union[int, None] = Field( 

202 None, description="Bank chi-square DOF" 

203 ) 

204 single_cont_chisq: Union[float, None] = Field( 

205 None, description="Continuous chi-square" 

206 ) 

207 single_cont_chisq_dof: Union[int, None] = Field( 

208 None, description="Continuous chi-square DOF" 

209 ) 

210 

211 single_sigmasq: Union[float, None] = Field(None, description="Sigma squared") 

212 single_rsqveto_duration: Union[float, None] = Field( 

213 None, description="RSQ veto duration" 

214 ) 

215 

216 # Gamma parameters 

217 single_Gamma0: Union[float, None] = Field(None, description="Gamma 0") 

218 single_Gamma1: Union[float, None] = Field(None, description="Gamma 1") 

219 single_Gamma2: Union[float, None] = Field(None, description="Gamma 2") 

220 single_Gamma3: Union[float, None] = Field(None, description="Gamma 3") 

221 single_Gamma4: Union[float, None] = Field(None, description="Gamma 4") 

222 single_Gamma5: Union[float, None] = Field(None, description="Gamma 5") 

223 single_Gamma6: Union[float, None] = Field(None, description="Gamma 6") 

224 single_Gamma7: Union[float, None] = Field(None, description="Gamma 7") 

225 single_Gamma8: Union[float, None] = Field(None, description="Gamma 8") 

226 single_Gamma9: Union[float, None] = Field(None, description="Gamma 9") 

227 

228 # Spin 

229 single_spin1x: Union[float, None] = Field(None, description="Spin 1 x-component") 

230 single_spin1y: Union[float, None] = Field(None, description="Spin 1 y-component") 

231 single_spin1z: Union[float, None] = Field(None, description="Spin 1 z-component") 

232 single_spin2x: Union[float, None] = Field(None, description="Spin 2 x-component") 

233 single_spin2y: Union[float, None] = Field(None, description="Spin 2 y-component") 

234 single_spin2z: Union[float, None] = Field(None, description="Spin 2 z-component") 

235 

236 

237class EventLabels(StrictModel): 

238 label_2022_LENSING_MDC: Union[bool, None] = Field( 

239 None, description="2022_LENSING_MDC label" 

240 ) 

241 label_ADVNO: Union[bool, None] = Field(None, description="ADVNO label") 

242 label_ADVOK: Union[bool, None] = Field(None, description="ADVOK label") 

243 label_ADVREQ: Union[bool, None] = Field(None, description="ADVREQ label") 

244 label_COMBINEDSKYMAP_READY: Union[bool, None] = Field( 

245 None, description="COMBINEDSKYMAP_READY label" 

246 ) 

247 label_DQOK: Union[bool, None] = Field(None, description="DQOK label") 

248 label_DQR_REQUEST: Union[bool, None] = Field(None, description="DQR_REQUEST label") 

249 label_DQV: Union[bool, None] = Field(None, description="DQV label") 

250 label_EARLY_WARNING: Union[bool, None] = Field( 

251 None, description="EARLY_WARNING label" 

252 ) 

253 label_EMBRIGHT_READY: Union[bool, None] = Field( 

254 None, description="EMBRIGHT_READY label" 

255 ) 

256 label_EM_COINC: Union[bool, None] = Field(None, description="EM_COINC label") 

257 label_EM_READY: Union[bool, None] = Field(None, description="EM_READY label") 

258 label_EM_SENT: Union[bool, None] = Field(None, description="EM_SENT label") 

259 label_EM_Selected: Union[bool, None] = Field(None, description="EM_Selected label") 

260 label_EM_SelectedConfident: Union[bool, None] = Field( 

261 None, description="EM_SelectedConfident label" 

262 ) 

263 label_EM_Superseded: Union[bool, None] = Field( 

264 None, description="EM_Superseded label" 

265 ) 

266 label_EM_Throttled: Union[bool, None] = Field( 

267 None, description="EM_Throttled label" 

268 ) 

269 label_EXT_SKYMAP_READY: Union[bool, None] = Field( 

270 None, description="EXT_SKYMAP_READY label" 

271 ) 

272 label_GCN_PRELIM_SENT: Union[bool, None] = Field( 

273 None, description="GCN_PRELIM_SENT label" 

274 ) 

275 label_GRB_OFFLINE: Union[bool, None] = Field(None, description="GRB_OFFLINE label") 

276 label_GRB_ONLINE: Union[bool, None] = Field(None, description="GRB_ONLINE label") 

277 label_H1NO: Union[bool, None] = Field(None, description="H1NO label") 

278 label_H1OK: Union[bool, None] = Field(None, description="H1OK label") 

279 label_H1OPS: Union[bool, None] = Field(None, description="H1OPS label") 

280 label_HIGH_PROFILE: Union[bool, None] = Field( 

281 None, description="HIGH_PROFILE label" 

282 ) 

283 label_HWINJNO: Union[bool, None] = Field(None, description="HWINJNO label") 

284 label_HWINJOK: Union[bool, None] = Field(None, description="HWINJOK label") 

285 label_HWINJREQ: Union[bool, None] = Field(None, description="HWINJREQ label") 

286 label_INJ: Union[bool, None] = Field(None, description="INJ label") 

287 label_L1NO: Union[bool, None] = Field(None, description="L1NO label") 

288 label_L1OK: Union[bool, None] = Field(None, description="L1OK label") 

289 label_L1OPS: Union[bool, None] = Field(None, description="L1OPS label") 

290 label_LENSED_CANDIDATE: Union[bool, None] = Field( 

291 None, description="LENSED_CANDIDATE label" 

292 ) 

293 label_LLAMA_COMPLETE: Union[bool, None] = Field( 

294 None, description="LLAMA_COMPLETE label" 

295 ) 

296 label_LOW_SIGNIF_LOCKED: Union[bool, None] = Field( 

297 None, description="LOW_SIGNIF_LOCKED label" 

298 ) 

299 label_LOW_SIGNIF_PRELIM_SENT: Union[bool, None] = Field( 

300 None, description="LOW_SIGNIF_PRELIM_SENT label" 

301 ) 

302 label_LUMIN_GO: Union[bool, None] = Field(None, description="LUMIN_GO label") 

303 label_LUMIN_NO: Union[bool, None] = Field(None, description="LUMIN_NO label") 

304 label_MOCK: Union[bool, None] = Field(None, description="MOCK label") 

305 label_NOT_GRB: Union[bool, None] = Field(None, description="NOT_GRB label") 

306 label_PASTRO_READY: Union[bool, None] = Field( 

307 None, description="PASTRO_READY label" 

308 ) 

309 label_PE_READY: Union[bool, None] = Field(None, description="PE_READY label") 

310 label_RAVEN_ALERT: Union[bool, None] = Field(None, description="RAVEN_ALERT label") 

311 label_SIGNIF_LOCKED: Union[bool, None] = Field( 

312 None, description="SIGNIF_LOCKED label" 

313 ) 

314 label_SKYMAP_READY: Union[bool, None] = Field( 

315 None, description="SKYMAP_READY label" 

316 ) 

317 label_SNR_OPTIMIZED: Union[bool, None] = Field( 

318 None, description="SNR_OPTIMIZED label" 

319 ) 

320 label_SOG_READY: Union[bool, None] = Field(None, description="SOG_READY label") 

321 label_SUBSOLAR_MASS: Union[bool, None] = Field( 

322 None, description="SUBSOLAR_MASS label" 

323 ) 

324 label_SWIFT_GO: Union[bool, None] = Field(None, description="SWIFT_GO label") 

325 label_SWIFT_NO: Union[bool, None] = Field(None, description="SWIFT_NO label") 

326 label_V1NO: Union[bool, None] = Field(None, description="V1NO label") 

327 label_V1OK: Union[bool, None] = Field(None, description="V1OK label") 

328 label_V1OPS: Union[bool, None] = Field(None, description="V1OPS label") 

329 label_cWB_r: Union[bool, None] = Field(None, description="cWB_r label") 

330 label_cWB_s: Union[bool, None] = Field(None, description="cWB_s label") 

331 

332 

333class EventLinks(StrictModel): 

334 """URL links from GraceDB.""" 

335 

336 neighbors: Union[str, None] = Field(None, description="Neighbors URL") 

337 log: Union[str, None] = Field(None, description="Log URL") 

338 emobservations: Union[str, None] = Field(None, description="EM observations URL") 

339 files: Union[str, None] = Field(None, description="Files URL") 

340 labels: Union[str, None] = Field(None, description="Labels URL") 

341 self: Union[str, None] = Field(None, description="Self URL") 

342 tags: Union[str, None] = Field(None, description="Tags URL") 

343 

344 

345class EventPastro(StrictModel): 

346 """Astrophysical source-class probabilities from p_astro.json.""" 

347 

348 p_astro_terrestrial: float = Field( 

349 None, 

350 ge=0.0, 

351 le=1.0, 

352 description="Probability of terrestrial origin", 

353 ) 

354 p_astro_bns: float = Field( 

355 None, 

356 ge=0.0, 

357 le=1.0, 

358 description="Probability of binary neutron star origin", 

359 ) 

360 p_astro_bbh: float = Field( 

361 None, 

362 ge=0.0, 

363 le=1.0, 

364 description="Probability of binary black hole origin", 

365 ) 

366 p_astro_nsbh: float = Field( 

367 None, 

368 ge=0.0, 

369 le=1.0, 

370 description="Probability of neutron star-black hole origin", 

371 ) 

372 

373 @model_validator(mode="after") 

374 def validate_probability_sum(self) -> "EventPastro": 

375 """Validate that p_astro probabilities sum to approximately one.""" 

376 total = ( 

377 self.p_astro_terrestrial 

378 + self.p_astro_bns 

379 + self.p_astro_bbh 

380 + self.p_astro_nsbh 

381 ) 

382 

383 if not math.isclose( 

384 total, 

385 1.0, 

386 rel_tol=0.0, 

387 abs_tol=1e-6, 

388 ): 

389 raise ValueError( 

390 f"p_astro probabilities must sum to 1.0 (received {total:.12g})" 

391 ) 

392 

393 return self 

394 

395 

396class EventEMBright(StrictModel): 

397 """Source-class probabilities from em_bright.json.""" 

398 

399 em_bright_has_ns: float = Field( 

400 ge=0.0, le=1.0, description="Probability has neutron star" 

401 ) 

402 em_bright_has_remnant: Union[float, None] = Field( 

403 None, ge=0.0, le=1.0, description="Probability has remnant" 

404 ) 

405 em_bright_has_mass_gap: Union[float, None] = Field( 

406 None, ge=0.0, le=1.0, description="Probability in mass gap" 

407 ) 

408 em_bright_has_ssm: Union[float, None] = Field( 

409 None, ge=0.0, le=1.0, description="Probability has ssm" 

410 ) 

411 

412 

413class NormalizedEvent(StrictModel): 

414 """ 

415 Flat representation of a gravitational wave event. 

416 """ 

417 

418 # Identifiers 

419 uid: str = Field( 

420 ..., 

421 pattern=r"^[GSM]\d+(?:[a-z]+)?$", 

422 description="Unique identifier for the event (e.g G123456 or S260813fk)", 

423 ) 

424 

425 @computed_field 

426 @property 

427 def event_type(self) -> Literal["G-event", "S-event"]: 

428 return "G-event" if self.uid.startswith("G") else "S-event" 

429 

430 # COMPULSORY FIELDS 

431 group: str = Field(description="Event group (CBC, Burst)") 

432 pipeline: str = Field(description="Pipeline (gstlal, CWB, MLy, etc.)") 

433 search: str = Field(description="Search type (AllSky, etc.)") 

434 far: float = Field(description="False Alarm Rate (Hz)") 

435 far_is_upper_limit: Union[bool, None] = Field( 

436 None, description="FAR is upper limit" 

437 ) 

438 instruments: list[str] = Field(description="Detector list (H1,L1,V1)") 

439 reporting_latency: float = Field(description="Reporting latency (seconds)") 

440 gpstime: float = Field(description="GPS time of event (s)") 

441 

442 # OPTIONAL FIELDS - Root level 

443 H1_channel: Union[str, None] = Field("None", description="H1 channel name") 

444 L1_channel: Union[str, None] = Field("None", description="L1 channel name") 

445 V1_channel: Union[str, None] = Field("None", description="V1 channel name") 

446 K1_channel: Union[str, None] = Field("None", description="K1 channel name") 

447 alert_type: Union[str, None] = Field( 

448 None, description="Alert type (e.g., file_uploaded)" 

449 ) 

450 submitter: Union[str, None] = Field(None, description="User who submitted") 

451 offline: Union[bool, None] = Field(None, description="Offline analysis flag") 

452 nevents: Union[int, None] = Field( 

453 None, description="Number of events in coincidence" 

454 ) 

455 likelihood: Union[float, None] = Field(None, description="Log-likelihood ratio") 

456 superevent: Union[str, None] = Field(None, description="Superevent ID") 

457 created: Union[str, None] = Field(None, description="Creation time") 

458 processing_status: Union[str, None] = Field(None, description="Processing status") 

459 content_id: Union[str, None] = Field(None, description="Content ID") 

460 

461 # CBC-specific (from CoincInspiral) 

462 coinc: Union[EventCoinc, None] = Field( 

463 None, description="Fields from CoincInspiral tables" 

464 ) 

465 

466 # Burst-specific (MultiBurst) 

467 burst: Union[EventBurst, None] = Field( 

468 None, description="Fields from MultiBurst tables" 

469 ) 

470 

471 # MLy-specific (MLyBurst) 

472 mly: Union[EventMLyBurst, None] = Field( 

473 None, description="Fields from MLyBurst tables" 

474 ) 

475 

476 # Single Inspiral per IFO 

477 single_H1: Union[EventSingleInspiral, None] = Field( 

478 None, description="Single Inspiral for H1" 

479 ) 

480 single_L1: Union[EventSingleInspiral, None] = Field( 

481 None, description="Single Inspiral for L1" 

482 ) 

483 single_V1: Union[EventSingleInspiral, None] = Field( 

484 None, description="Single Inspiral for V1" 

485 ) 

486 single_K1: Union[EventSingleInspiral, None] = Field( 

487 None, description="Single Inspiral for K1" 

488 ) 

489 

490 # Labels 

491 labels: Union[EventLabels, None] = Field(None, description="Event labels") 

492 

493 # File artifacts 

494 p_astro: Union[EventPastro, None] = Field( 

495 None, description="Astrophysical probability" 

496 ) 

497 em_bright: Union[EventEMBright, None] = Field( 

498 None, description="EMBright probability" 

499 ) 

500 

501 # Links 

502 links: Union[EventLinks, None] = Field(None, description="GraceDB URL links") 

503 

504 # METADATA 

505 last_updated: datetime = Field(description="Last update time")