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
« 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"""
5import math
6from datetime import datetime
7from typing import Literal, Union
9from pydantic import BaseModel, ConfigDict, Field, computed_field, model_validator
12class StrictModel(BaseModel):
13 model_config = ConfigDict(extra="ignore")
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")
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")
83class EventMLyBurst(StrictModel):
84 """MLy-specific burst fields."""
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")
128class EventSingleInspiral(StrictModel):
129 """Single Inspiral fields for one IFO."""
131 # Identifiers
132 single_search: Union[str, None] = Field(None, description="Search type")
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 )
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")
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")
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")
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")
191 # Beta
192 single_beta: Union[float, None] = Field(None, description="Beta")
194 single_f_final: Union[float, None] = Field(None, description="Final frequency (Hz)")
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 )
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 )
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")
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")
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")
333class EventLinks(StrictModel):
334 """URL links from GraceDB."""
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")
345class EventPastro(StrictModel):
346 """Astrophysical source-class probabilities from p_astro.json."""
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 )
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 )
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 )
393 return self
396class EventEMBright(StrictModel):
397 """Source-class probabilities from em_bright.json."""
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 )
413class NormalizedEvent(StrictModel):
414 """
415 Flat representation of a gravitational wave event.
416 """
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 )
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"
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)")
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")
461 # CBC-specific (from CoincInspiral)
462 coinc: Union[EventCoinc, None] = Field(
463 None, description="Fields from CoincInspiral tables"
464 )
466 # Burst-specific (MultiBurst)
467 burst: Union[EventBurst, None] = Field(
468 None, description="Fields from MultiBurst tables"
469 )
471 # MLy-specific (MLyBurst)
472 mly: Union[EventMLyBurst, None] = Field(
473 None, description="Fields from MLyBurst tables"
474 )
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 )
490 # Labels
491 labels: Union[EventLabels, None] = Field(None, description="Event labels")
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 )
501 # Links
502 links: Union[EventLinks, None] = Field(None, description="GraceDB URL links")
504 # METADATA
505 last_updated: datetime = Field(description="Last update time")