1use super::clustering::ClusteringResult;
2use crate::momentum::{FourMomentum, Rotation};
3use crate::utils::{F, FloatLike, into_complex_ff64};
4use colored::Colorize;
5use serde::{Deserialize, Serialize};
6use smallvec::SmallVec;
7use spenso::algebra::complex::Complex;
8use std::collections::BTreeMap;
9use std::fmt;
10use std::ops::{Deref, DerefMut};
11use tabled::{
12 Table, Tabled,
13 settings::{Style, style::HorizontalLine},
14};
15
16#[derive(Default, Debug, Clone, Serialize, Deserialize)]
17pub struct CutInfo {
18 pub particle_pdgs: (SmallVec<[isize; 2]>, SmallVec<[isize; 4]>),
19 pub cut_id: usize,
20 pub graph_id: usize,
21 pub graph_group_id: Option<usize>,
22 pub orientation_id: Option<usize>,
23 pub lmb_channel_id: Option<usize>,
24 pub lmb_channel_edge_ids: Option<SmallVec<[usize; 4]>>,
25}
26
27pub type Event = GenericEvent<f64>;
28pub type EventGroup = GenericEventGroup<f64>;
29pub type EventGroupList = GenericEventGroupList<f64>;
30
31#[derive(Default, Debug, Clone)]
32pub struct GenericDerivedEventData<T: FloatLike> {
33 pub clustered_jets: Vec<Option<ClusteringResult<T>>>,
34}
35
36impl<T: FloatLike> GenericDerivedEventData<T> {
37 pub fn to_f64(&self) -> GenericDerivedEventData<f64> {
38 GenericDerivedEventData {
39 clustered_jets: self
40 .clustered_jets
41 .iter()
42 .map(|result| result.as_ref().map(ClusteringResult::to_f64))
43 .collect(),
44 }
45 }
46
47 pub fn from_f64(data: &GenericDerivedEventData<f64>) -> Self {
48 GenericDerivedEventData {
49 clustered_jets: data
50 .clustered_jets
51 .iter()
52 .map(|result| result.as_ref().map(ClusteringResult::from_f64))
53 .collect(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
59pub enum AdditionalWeightKey {
60 FullMultiplicativeFactor,
61 Original,
62 ThresholdCounterterm {
63 subset_index: usize,
64 },
65 AmplitudeThresholdCounterterm {
66 esurface_id: usize,
67 overlap_group: usize,
68 },
69}
70
71#[derive(Default, Debug, Clone, Serialize, Deserialize)]
72pub struct GenericEventGroup<T: FloatLike>(pub Vec<GenericEvent<T>>);
73
74impl<T: FloatLike> Deref for GenericEventGroup<T> {
75 type Target = Vec<GenericEvent<T>>;
76
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl<T: FloatLike> DerefMut for GenericEventGroup<T> {
83 fn deref_mut(&mut self) -> &mut Self::Target {
84 &mut self.0
85 }
86}
87
88impl<T: FloatLike> GenericEventGroup<T> {
89 pub fn to_f64(&self) -> EventGroup {
90 GenericEventGroup(self.0.iter().map(GenericEvent::to_f64).collect())
91 }
92
93 pub fn from_f64(event_group: &EventGroup) -> Self {
94 GenericEventGroup(event_group.0.iter().map(GenericEvent::from_f64).collect())
95 }
96}
97
98#[derive(Default, Debug, Clone, Serialize, Deserialize)]
99pub struct GenericEventGroupList<T: FloatLike>(pub Vec<GenericEventGroup<T>>);
100
101impl<T: FloatLike> Deref for GenericEventGroupList<T> {
102 type Target = Vec<GenericEventGroup<T>>;
103
104 fn deref(&self) -> &Self::Target {
105 &self.0
106 }
107}
108
109impl<T: FloatLike> DerefMut for GenericEventGroupList<T> {
110 fn deref_mut(&mut self) -> &mut Self::Target {
111 &mut self.0
112 }
113}
114
115impl<T: FloatLike> GenericEventGroupList<T> {
116 pub fn to_f64(&self) -> EventGroupList {
117 GenericEventGroupList(self.0.iter().map(GenericEventGroup::to_f64).collect())
118 }
119
120 pub fn from_f64(event_group_list: &EventGroupList) -> Self {
121 GenericEventGroupList(
122 event_group_list
123 .0
124 .iter()
125 .map(GenericEventGroup::from_f64)
126 .collect(),
127 )
128 }
129
130 pub fn push_singleton(&mut self, event: GenericEvent<T>) {
131 self.0.push(GenericEventGroup(vec![event]));
132 }
133}
134
135#[derive(Default, Debug, Clone, Serialize, Deserialize)]
136pub struct GenericAdditionalWeightInfo<T: FloatLike> {
137 pub weights: BTreeMap<AdditionalWeightKey, Complex<F<T>>>,
138}
139
140impl<T: FloatLike> GenericAdditionalWeightInfo<T> {
141 pub fn to_f64(&self) -> GenericAdditionalWeightInfo<f64> {
142 GenericAdditionalWeightInfo {
143 weights: self
144 .weights
145 .iter()
146 .map(|(key, weight)| (*key, into_complex_ff64(weight)))
147 .collect(),
148 }
149 }
150
151 pub fn from_f64(additional_weights: &GenericAdditionalWeightInfo<f64>) -> Self {
152 GenericAdditionalWeightInfo {
153 weights: additional_weights
154 .weights
155 .iter()
156 .map(|(key, weight)| {
157 (
158 *key,
159 Complex::new(F::from_ff64(weight.re), F::from_ff64(weight.im)),
160 )
161 })
162 .collect(),
163 }
164 }
165}
166
167#[derive(Default, Debug, Clone, Serialize, Deserialize)]
168pub struct GenericEvent<T: FloatLike> {
169 #[allow(clippy::type_complexity)]
170 pub kinematic_configuration: (
171 SmallVec<[FourMomentum<F<T>>; 2]>,
172 SmallVec<[FourMomentum<F<T>>; 4]>,
173 ),
174 pub cut_info: CutInfo,
175 pub weight: Complex<F<T>>,
177 pub additional_weights: GenericAdditionalWeightInfo<T>,
179 #[serde(skip)]
180 pub derived_observable_data: GenericDerivedEventData<T>,
181}
182
183impl<T: FloatLike> GenericEvent<T> {
184 pub(crate) fn inverse_rotate(&mut self, rotation: &Rotation) {
185 if rotation.is_identity() {
186 return;
187 }
188
189 for momentum in self.kinematic_configuration.0.iter_mut() {
190 *momentum = rotation.inverse_rotate_four(momentum);
191 }
192 for momentum in self.kinematic_configuration.1.iter_mut() {
193 *momentum = rotation.inverse_rotate_four(momentum);
194 }
195
196 self.derived_observable_data = GenericDerivedEventData::default();
198 }
199
200 pub fn ensure_clustering_slots(&mut self, n_clusterings: usize) {
201 if self.derived_observable_data.clustered_jets.len() < n_clusterings {
202 self.derived_observable_data
203 .clustered_jets
204 .resize_with(n_clusterings, || None);
205 }
206 }
207
208 pub fn cached_clustering(&self, handle: usize) -> Option<&ClusteringResult<T>> {
209 self.derived_observable_data
210 .clustered_jets
211 .get(handle)
212 .and_then(|result| result.as_ref())
213 }
214
215 pub fn to_f64(&self) -> Event {
216 GenericEvent {
217 kinematic_configuration: (
218 self.kinematic_configuration
219 .0
220 .iter()
221 .map(FourMomentum::to_f64)
222 .collect(),
223 self.kinematic_configuration
224 .1
225 .iter()
226 .map(FourMomentum::to_f64)
227 .collect(),
228 ),
229 cut_info: self.cut_info.clone(),
230 weight: into_complex_ff64(&self.weight),
231 additional_weights: self.additional_weights.to_f64(),
232 derived_observable_data: self.derived_observable_data.to_f64(),
233 }
234 }
235
236 pub fn from_f64(event: &Event) -> Self {
237 GenericEvent {
238 kinematic_configuration: (
239 event
240 .kinematic_configuration
241 .0
242 .iter()
243 .map(FourMomentum::from_ff64)
244 .collect(),
245 event
246 .kinematic_configuration
247 .1
248 .iter()
249 .map(FourMomentum::from_ff64)
250 .collect(),
251 ),
252 cut_info: event.cut_info.clone(),
253 weight: Complex::new(F::from_ff64(event.weight.re), F::from_ff64(event.weight.im)),
254 additional_weights: GenericAdditionalWeightInfo::from_f64(&event.additional_weights),
255 derived_observable_data: GenericDerivedEventData::from_f64(
256 &event.derived_observable_data,
257 ),
258 }
259 }
260}
261
262#[derive(Tabled)]
263struct EventSummaryRow {
264 field: String,
265 value: String,
266}
267
268#[derive(Tabled)]
269struct MomentumRow {
270 #[tabled(rename = "state")]
271 state: String,
272 #[tabled(rename = "PDG")]
273 pdg: String,
274 #[tabled(rename = "E")]
275 e: String,
276 px: String,
277 py: String,
278 pz: String,
279 #[tabled(rename = "sqrt(p^2)")]
280 p2: String,
281}
282
283#[derive(Tabled)]
284struct AdditionalWeightRow {
285 key: String,
286 value: String,
287}
288
289fn display_decimal_precision<T: FloatLike>(value: &F<T>) -> usize {
290 let precision_bits = value.0.get_precision().max(1) as f64;
291 (precision_bits * std::f64::consts::LOG10_2).ceil().max(1.0) as usize
292}
293
294pub(crate) fn format_real_generic<T: FloatLike>(value: &F<T>) -> String {
295 format!("{:+.*e}", display_decimal_precision(value), value)
296}
297
298fn format_count(value: usize) -> String {
299 if value < 1_000 {
300 return value.to_string();
301 }
302
303 let value = value as f64;
304 for (scale, suffix) in [
305 (1_000_000_000_f64, "B"),
306 (1_000_000_f64, "M"),
307 (1_000_f64, "K"),
308 ] {
309 if value >= scale {
310 let scaled = value / scale;
311 let precision = if scaled >= 100.0 {
312 0
313 } else if scaled >= 10.0 {
314 1
315 } else {
316 2
317 };
318 return format!("{scaled:.precision$}{suffix}");
319 }
320 }
321
322 value.round().to_string()
323}
324
325pub(crate) fn format_optional_real_generic<T: FloatLike>(value: Option<&F<T>>) -> String {
326 value
327 .map(format_real_generic)
328 .unwrap_or_else(|| "None".red().to_string())
329}
330
331pub(crate) fn format_complex_generic<T: FloatLike>(value: &Complex<F<T>>) -> String {
332 let precision = display_decimal_precision(&value.re).max(display_decimal_precision(&value.im));
333 format!("{:+.*e} {:+.*e}i", precision, value.re, precision, value.im)
334}
335
336fn format_pdg(pdg: Option<isize>) -> String {
337 pdg.map(|value| value.to_string())
338 .unwrap_or_else(|| "N/A".red().to_string())
339}
340
341fn format_pdg_with_state_color(pdg: Option<isize>, incoming: bool) -> String {
342 let pdg = format_pdg(pdg);
343 if incoming {
344 pdg.bright_blue().to_string()
345 } else {
346 pdg.bright_green().to_string()
347 }
348}
349
350fn format_lmb_channel_edge_ids(edge_ids: Option<&[usize]>) -> String {
351 edge_ids
352 .map(|edge_ids| {
353 format!(
354 "({})",
355 edge_ids
356 .iter()
357 .map(|edge_id| edge_id.to_string())
358 .collect::<Vec<_>>()
359 .join(",")
360 )
361 })
362 .unwrap_or_else(|| "None".to_string())
363}
364
365fn momentum_mass_squared<T: FloatLike>(momentum: &FourMomentum<F<T>>) -> F<T> {
366 momentum.temporal.value.clone() * momentum.temporal.value.clone()
367 - momentum.spatial.px.clone() * momentum.spatial.px.clone()
368 - momentum.spatial.py.clone() * momentum.spatial.py.clone()
369 - momentum.spatial.pz.clone() * momentum.spatial.pz.clone()
370}
371
372fn momentum_mass<T: FloatLike>(momentum: &FourMomentum<F<T>>) -> F<T> {
373 momentum_mass_squared(momentum).abs().sqrt()
374}
375
376fn format_momentum_rows<T: FloatLike>(
377 state: String,
378 momenta: &[FourMomentum<F<T>>],
379 pdgs: &[isize],
380 incoming: bool,
381) -> Vec<MomentumRow> {
382 momenta
383 .iter()
384 .enumerate()
385 .map(|(index, momentum)| MomentumRow {
386 state: state.clone(),
387 pdg: format_pdg_with_state_color(pdgs.get(index).copied(), incoming),
388 e: format_real_generic(&momentum.temporal.value),
389 px: format_real_generic(&momentum.spatial.px),
390 py: format_real_generic(&momentum.spatial.py),
391 pz: format_real_generic(&momentum.spatial.pz),
392 p2: format_real_generic(&momentum_mass(momentum))
393 .bright_yellow()
394 .to_string(),
395 })
396 .collect()
397}
398
399fn event_zero<T: FloatLike>(event: &GenericEvent<T>) -> F<T> {
400 event
401 .kinematic_configuration
402 .0
403 .first()
404 .map(|momentum| momentum.temporal.value.zero())
405 .or_else(|| {
406 event
407 .kinematic_configuration
408 .1
409 .first()
410 .map(|momentum| momentum.temporal.value.zero())
411 })
412 .unwrap_or_else(|| F(T::new_zero()))
413}
414
415fn conservation_row<T: FloatLike>(event: &GenericEvent<T>) -> MomentumRow {
416 let zero = event_zero(event);
417 let incoming = event.kinematic_configuration.0.iter().fold(
418 (zero.clone(), zero.clone(), zero.clone(), zero.clone()),
419 |acc, momentum| {
420 (
421 acc.0 + momentum.temporal.value.clone(),
422 acc.1 + momentum.spatial.px.clone(),
423 acc.2 + momentum.spatial.py.clone(),
424 acc.3 + momentum.spatial.pz.clone(),
425 )
426 },
427 );
428 let outgoing = event.kinematic_configuration.1.iter().fold(
429 (zero.clone(), zero.clone(), zero.clone(), zero),
430 |acc, momentum| {
431 (
432 acc.0 + momentum.temporal.value.clone(),
433 acc.1 + momentum.spatial.px.clone(),
434 acc.2 + momentum.spatial.py.clone(),
435 acc.3 + momentum.spatial.pz.clone(),
436 )
437 },
438 );
439 let delta = FourMomentum {
440 temporal: crate::momentum::Energy::new(incoming.0 - outgoing.0),
441 spatial: crate::momentum::ThreeMomentum::new(
442 incoming.1 - outgoing.1,
443 incoming.2 - outgoing.2,
444 incoming.3 - outgoing.3,
445 ),
446 };
447
448 MomentumRow {
449 state: "CHECK".bold().bright_yellow().to_string(),
450 pdg: "N/A".bright_yellow().to_string(),
451 e: format_real_generic(&delta.temporal.value)
452 .bright_yellow()
453 .to_string(),
454 px: format_real_generic(&delta.spatial.px)
455 .bright_yellow()
456 .to_string(),
457 py: format_real_generic(&delta.spatial.py)
458 .bright_yellow()
459 .to_string(),
460 pz: format_real_generic(&delta.spatial.pz)
461 .bright_yellow()
462 .to_string(),
463 p2: String::new().bright_yellow().to_string(),
464 }
465}
466
467fn indent_block(block: &str, prefix: &str) -> String {
468 let mut result = String::new();
469 for (index, line) in block.lines().enumerate() {
470 if index > 0 {
471 result.push('\n');
472 }
473 result.push_str(prefix);
474 result.push_str(line);
475 }
476 result
477}
478
479impl fmt::Display for AdditionalWeightKey {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 match self {
482 AdditionalWeightKey::FullMultiplicativeFactor => {
483 write!(f, "full multiplicative factor")
484 }
485 AdditionalWeightKey::Original => write!(f, "original"),
486 AdditionalWeightKey::ThresholdCounterterm { subset_index } => {
487 write!(f, "threshold_counterterm:{subset_index}")
488 }
489 AdditionalWeightKey::AmplitudeThresholdCounterterm {
490 esurface_id,
491 overlap_group,
492 } => {
493 write!(f, "threshold_counterterm:{esurface_id}:{overlap_group}")
494 }
495 }
496 }
497}
498
499impl fmt::Display for CutInfo {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 let rows = vec![
502 EventSummaryRow {
503 field: "graph".to_string(),
504 value: self.graph_id.to_string(),
505 },
506 EventSummaryRow {
507 field: "graph group".to_string(),
508 value: self
509 .graph_group_id
510 .map(|value| value.to_string())
511 .unwrap_or_else(|| "None".to_string()),
512 },
513 EventSummaryRow {
514 field: "orientation".to_string(),
515 value: self
516 .orientation_id
517 .map(|value| value.to_string())
518 .unwrap_or_else(|| "None".to_string()),
519 },
520 EventSummaryRow {
521 field: "cut".to_string(),
522 value: self.cut_id.to_string(),
523 },
524 EventSummaryRow {
525 field: "lmb channel id".to_string(),
526 value: self
527 .lmb_channel_id
528 .map(|value| value.to_string())
529 .unwrap_or_else(|| "None".to_string()),
530 },
531 EventSummaryRow {
532 field: "lmb channel".to_string(),
533 value: format_lmb_channel_edge_ids(self.lmb_channel_edge_ids.as_deref()),
534 },
535 ];
536 write!(f, "{}", Table::new(rows).with(Style::rounded()))
537 }
538}
539
540impl<T: FloatLike> fmt::Display for GenericEvent<T> {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 let summary = vec![
543 EventSummaryRow {
544 field: "graph".to_string(),
545 value: self.cut_info.graph_id.to_string(),
546 },
547 EventSummaryRow {
548 field: "graph group".to_string(),
549 value: self
550 .cut_info
551 .graph_group_id
552 .map(|value| value.to_string())
553 .unwrap_or_else(|| "None".to_string()),
554 },
555 EventSummaryRow {
556 field: "orientation".to_string(),
557 value: self
558 .cut_info
559 .orientation_id
560 .map(|value| value.to_string())
561 .unwrap_or_else(|| "None".to_string()),
562 },
563 EventSummaryRow {
564 field: "cut".to_string(),
565 value: self.cut_info.cut_id.to_string(),
566 },
567 EventSummaryRow {
568 field: "lmb channel id".to_string(),
569 value: self
570 .cut_info
571 .lmb_channel_id
572 .map(|value| value.to_string())
573 .unwrap_or_else(|| "None".to_string()),
574 },
575 EventSummaryRow {
576 field: "lmb channel".to_string(),
577 value: format_lmb_channel_edge_ids(self.cut_info.lmb_channel_edge_ids.as_deref()),
578 },
579 EventSummaryRow {
580 field: "weight".to_string(),
581 value: format_complex_generic(&self.weight),
582 },
583 ];
584
585 writeln!(f, "{}", "Event".bold().bright_cyan())?;
586 writeln!(f, "{}", Table::new(summary).with(Style::rounded()))?;
587
588 let mut momentum_rows = format_momentum_rows(
589 "IN".bold().bright_blue().to_string(),
590 &self.kinematic_configuration.0,
591 &self.cut_info.particle_pdgs.0,
592 true,
593 );
594 let incoming_row_count = momentum_rows.len();
595 momentum_rows.extend(format_momentum_rows(
596 "OUT".bold().bright_green().to_string(),
597 &self.kinematic_configuration.1,
598 &self.cut_info.particle_pdgs.1,
599 false,
600 ));
601 if !momentum_rows.is_empty() {
602 momentum_rows.push(conservation_row(self));
603 let check_separator_index = momentum_rows.len();
604 let header_separator = (
605 1,
606 HorizontalLine::new('─')
607 .intersection('┼')
608 .left('├')
609 .right('┤'),
610 );
611 let check_separator = (
612 check_separator_index,
613 HorizontalLine::new('─')
614 .intersection('┼')
615 .left('├')
616 .right('┤'),
617 );
618 writeln!(f)?;
619 writeln!(f, "{}", "Kinematics".bold().bright_blue())?;
620 if incoming_row_count > 0 && incoming_row_count < check_separator_index - 1 {
621 let in_out_separator = (
622 incoming_row_count + 1,
623 HorizontalLine::new('─')
624 .intersection('┼')
625 .left('├')
626 .right('┤'),
627 );
628 writeln!(
629 f,
630 "{}",
631 Table::new(momentum_rows).with(Style::rounded().horizontals([
632 header_separator,
633 in_out_separator,
634 check_separator,
635 ]))
636 )?;
637 } else {
638 writeln!(
639 f,
640 "{}",
641 Table::new(momentum_rows)
642 .with(Style::rounded().horizontals([header_separator, check_separator]))
643 )?;
644 }
645 }
646
647 if !self.additional_weights.weights.is_empty() {
648 let additional_weights = self
649 .additional_weights
650 .weights
651 .iter()
652 .map(|(key, value)| AdditionalWeightRow {
653 key: key.to_string(),
654 value: format_complex_generic(value),
655 })
656 .collect::<Vec<_>>();
657
658 writeln!(f)?;
659 writeln!(f, "{}", "Additional weights".bold().bright_magenta())?;
660 write!(
661 f,
662 "{}",
663 Table::new(additional_weights).with(Style::rounded())
664 )?;
665 }
666
667 Ok(())
668 }
669}
670
671impl<T: FloatLike> fmt::Display for GenericEventGroup<T> {
672 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
673 writeln!(
674 f,
675 "{}",
676 format!("Event group ({} event(s))", format_count(self.len()))
677 .bold()
678 .bright_green()
679 )?;
680 for (index, event) in self.iter().enumerate() {
681 writeln!(f, " {}", format!("Event {index}:").bold().bright_yellow())?;
682 let event_str = event.to_string();
683 writeln!(f, "{}", indent_block(&event_str, " "))?;
684 }
685 Ok(())
686 }
687}
688
689impl<T: FloatLike> fmt::Display for GenericEventGroupList<T> {
690 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
691 for (index, group) in self.iter().enumerate() {
692 if index > 0 {
693 writeln!(f)?;
694 }
695 writeln!(
696 f,
697 "{}",
698 format!("Event group {index}:").bold().bright_green()
699 )?;
700 writeln!(f, "{}", indent_block(&group.to_string(), " "))?;
701 }
702 Ok(())
703 }
704}