1use dyn_clone::DynClone;
2use serde::{Deserialize, Serialize};
3use std::fmt::{Debug, Display};
4
5#[cfg(feature = "rayon")]
6use rayon::prelude::*;
7
8#[cfg(feature = "mpi")]
9use crate::mpi::LadduMPI;
10use crate::{
11 data::{Dataset, DatasetMetadata, EventData},
12 utils::{
13 enums::{Channel, Frame},
14 vectors::{Vec3, Vec4},
15 },
16 LadduError, LadduResult,
17};
18
19use auto_ops::impl_op_ex;
20
21#[cfg(feature = "mpi")]
22use mpi::{datatype::PartitionMut, topology::SimpleCommunicator, traits::*};
23
24#[typetag::serde(tag = "type")]
26pub trait Variable: DynClone + Send + Sync + Debug + Display {
27 fn bind(&mut self, _metadata: &DatasetMetadata) -> LadduResult<()> {
31 Ok(())
32 }
33
34 fn value(&self, event: &EventData) -> f64;
36
37 fn value_on_local(&self, dataset: &Dataset) -> LadduResult<Vec<f64>> {
45 let mut variable = dyn_clone::clone_box(self);
46 variable.bind(dataset.metadata())?;
47 #[cfg(feature = "rayon")]
48 let local_values: Vec<f64> = dataset
49 .events
50 .par_iter()
51 .map(|e| variable.value(e))
52 .collect();
53 #[cfg(not(feature = "rayon"))]
54 let local_values: Vec<f64> = dataset.events.iter().map(|e| variable.value(e)).collect();
55 Ok(local_values)
56 }
57
58 #[cfg(feature = "mpi")]
66 fn value_on_mpi(&self, dataset: &Dataset, world: &SimpleCommunicator) -> LadduResult<Vec<f64>> {
67 let local_weights = self.value_on_local(dataset)?;
68 let n_events = dataset.n_events();
69 let mut buffer: Vec<f64> = vec![0.0; n_events];
70 let (counts, displs) = world.get_counts_displs(n_events);
71 {
72 let mut partitioned_buffer = PartitionMut::new(&mut buffer, counts, displs);
73 world.all_gather_varcount_into(&local_weights, &mut partitioned_buffer);
74 }
75 Ok(buffer)
76 }
77
78 fn value_on(&self, dataset: &Dataset) -> LadduResult<Vec<f64>> {
81 #[cfg(feature = "mpi")]
82 {
83 if let Some(world) = crate::mpi::get_world() {
84 return self.value_on_mpi(dataset, &world);
85 }
86 }
87 self.value_on_local(dataset)
88 }
89
90 fn eq(&self, val: f64) -> VariableExpression
92 where
93 Self: std::marker::Sized + 'static,
94 {
95 VariableExpression::Eq(dyn_clone::clone_box(self), val)
96 }
97
98 fn lt(&self, val: f64) -> VariableExpression
100 where
101 Self: std::marker::Sized + 'static,
102 {
103 VariableExpression::Lt(dyn_clone::clone_box(self), val)
104 }
105
106 fn gt(&self, val: f64) -> VariableExpression
108 where
109 Self: std::marker::Sized + 'static,
110 {
111 VariableExpression::Gt(dyn_clone::clone_box(self), val)
112 }
113
114 fn ge(&self, val: f64) -> VariableExpression
116 where
117 Self: std::marker::Sized + 'static,
118 {
119 self.gt(val).or(&self.eq(val))
120 }
121
122 fn le(&self, val: f64) -> VariableExpression
124 where
125 Self: std::marker::Sized + 'static,
126 {
127 self.lt(val).or(&self.eq(val))
128 }
129}
130dyn_clone::clone_trait_object!(Variable);
131
132#[derive(Clone, Debug)]
134pub enum VariableExpression {
135 Eq(Box<dyn Variable>, f64),
137 Lt(Box<dyn Variable>, f64),
139 Gt(Box<dyn Variable>, f64),
141 And(Box<VariableExpression>, Box<VariableExpression>),
143 Or(Box<VariableExpression>, Box<VariableExpression>),
145 Not(Box<VariableExpression>),
147}
148
149impl VariableExpression {
150 pub fn and(&self, rhs: &VariableExpression) -> VariableExpression {
152 VariableExpression::And(Box::new(self.clone()), Box::new(rhs.clone()))
153 }
154
155 pub fn or(&self, rhs: &VariableExpression) -> VariableExpression {
157 VariableExpression::Or(Box::new(self.clone()), Box::new(rhs.clone()))
158 }
159
160 pub(crate) fn compile(&self, metadata: &DatasetMetadata) -> LadduResult<CompiledExpression> {
163 let mut compiled = compile_expression(self.clone());
164 compiled.bind(metadata)?;
165 Ok(compiled)
166 }
167}
168impl Display for VariableExpression {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 match self {
171 VariableExpression::Eq(var, val) => {
172 write!(f, "({} == {})", var, val)
173 }
174 VariableExpression::Lt(var, val) => {
175 write!(f, "({} < {})", var, val)
176 }
177 VariableExpression::Gt(var, val) => {
178 write!(f, "({} > {})", var, val)
179 }
180 VariableExpression::And(lhs, rhs) => {
181 write!(f, "({} & {})", lhs, rhs)
182 }
183 VariableExpression::Or(lhs, rhs) => {
184 write!(f, "({} | {})", lhs, rhs)
185 }
186 VariableExpression::Not(inner) => {
187 write!(f, "!({})", inner)
188 }
189 }
190 }
191}
192
193pub fn not(expr: &VariableExpression) -> VariableExpression {
195 VariableExpression::Not(Box::new(expr.clone()))
196}
197
198#[rustfmt::skip]
199impl_op_ex!(& |lhs: &VariableExpression, rhs: &VariableExpression| -> VariableExpression{ lhs.and(rhs) });
200#[rustfmt::skip]
201impl_op_ex!(| |lhs: &VariableExpression, rhs: &VariableExpression| -> VariableExpression{ lhs.or(rhs) });
202#[rustfmt::skip]
203impl_op_ex!(! |exp: &VariableExpression| -> VariableExpression{ not(exp) });
204
205#[derive(Debug)]
206enum Opcode {
207 PushEq(usize, f64),
208 PushLt(usize, f64),
209 PushGt(usize, f64),
210 And,
211 Or,
212 Not,
213}
214
215pub(crate) struct CompiledExpression {
216 bytecode: Vec<Opcode>,
217 variables: Vec<Box<dyn Variable>>,
218}
219
220impl CompiledExpression {
221 pub fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
222 for variable in &mut self.variables {
223 variable.bind(metadata)?;
224 }
225 Ok(())
226 }
227
228 pub fn evaluate(&self, event: &EventData) -> bool {
230 let mut stack = Vec::with_capacity(self.bytecode.len());
231
232 for op in &self.bytecode {
233 match op {
234 Opcode::PushEq(i, val) => stack.push(self.variables[*i].value(event) == *val),
235 Opcode::PushLt(i, val) => stack.push(self.variables[*i].value(event) < *val),
236 Opcode::PushGt(i, val) => stack.push(self.variables[*i].value(event) > *val),
237 Opcode::Not => {
238 let a = stack.pop().unwrap();
239 stack.push(!a);
240 }
241 Opcode::And => {
242 let b = stack.pop().unwrap();
243 let a = stack.pop().unwrap();
244 stack.push(a && b);
245 }
246 Opcode::Or => {
247 let b = stack.pop().unwrap();
248 let a = stack.pop().unwrap();
249 stack.push(a || b);
250 }
251 }
252 }
253
254 stack.pop().unwrap()
255 }
256}
257
258pub(crate) fn compile_expression(expr: VariableExpression) -> CompiledExpression {
259 let mut bytecode = Vec::new();
260 let mut variables: Vec<Box<dyn Variable>> = Vec::new();
261
262 fn compile(
263 expr: VariableExpression,
264 bytecode: &mut Vec<Opcode>,
265 variables: &mut Vec<Box<dyn Variable>>,
266 ) {
267 match expr {
268 VariableExpression::Eq(var, val) => {
269 variables.push(var);
270 bytecode.push(Opcode::PushEq(variables.len() - 1, val));
271 }
272 VariableExpression::Lt(var, val) => {
273 variables.push(var);
274 bytecode.push(Opcode::PushLt(variables.len() - 1, val));
275 }
276 VariableExpression::Gt(var, val) => {
277 variables.push(var);
278 bytecode.push(Opcode::PushGt(variables.len() - 1, val));
279 }
280 VariableExpression::And(lhs, rhs) => {
281 compile(*lhs, bytecode, variables);
282 compile(*rhs, bytecode, variables);
283 bytecode.push(Opcode::And);
284 }
285 VariableExpression::Or(lhs, rhs) => {
286 compile(*lhs, bytecode, variables);
287 compile(*rhs, bytecode, variables);
288 bytecode.push(Opcode::Or);
289 }
290 VariableExpression::Not(inner) => {
291 compile(*inner, bytecode, variables);
292 bytecode.push(Opcode::Not);
293 }
294 }
295 }
296
297 compile(expr, &mut bytecode, &mut variables);
298
299 CompiledExpression {
300 bytecode,
301 variables,
302 }
303}
304
305fn names_to_string(names: &[String]) -> String {
306 names.join(", ")
307}
308
309#[derive(Clone, Debug, Serialize, Deserialize)]
315pub struct P4Selection {
316 names: Vec<String>,
317 #[serde(skip, default)]
318 indices: Vec<usize>,
319}
320
321impl P4Selection {
322 fn new_many<I, S>(names: I) -> Self
323 where
324 I: IntoIterator<Item = S>,
325 S: Into<String>,
326 {
327 Self {
328 names: names.into_iter().map(Into::into).collect(),
329 indices: Vec::new(),
330 }
331 }
332
333 pub(crate) fn with_indices<I, S>(names: I, indices: Vec<usize>) -> Self
334 where
335 I: IntoIterator<Item = S>,
336 S: Into<String>,
337 {
338 Self {
339 names: names.into_iter().map(Into::into).collect(),
340 indices,
341 }
342 }
343
344 pub fn names(&self) -> &[String] {
346 &self.names
347 }
348
349 pub(crate) fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
350 let mut resolved = Vec::with_capacity(self.names.len());
351 for name in &self.names {
352 metadata.append_indices_for_name(name, &mut resolved)?;
353 }
354 self.indices = resolved;
355 Ok(())
356 }
357
358 pub fn indices(&self) -> &[usize] {
360 &self.indices
361 }
362
363 pub(crate) fn momentum(&self, event: &EventData) -> Vec4 {
364 event.get_p4_sum(self.indices())
365 }
366}
367
368pub trait IntoP4Selection {
370 fn into_selection(self) -> P4Selection;
372}
373
374impl IntoP4Selection for P4Selection {
375 fn into_selection(self) -> P4Selection {
376 self
377 }
378}
379
380impl IntoP4Selection for &P4Selection {
381 fn into_selection(self) -> P4Selection {
382 self.clone()
383 }
384}
385
386impl IntoP4Selection for String {
387 fn into_selection(self) -> P4Selection {
388 P4Selection::new_many(vec![self])
389 }
390}
391
392impl IntoP4Selection for &String {
393 fn into_selection(self) -> P4Selection {
394 P4Selection::new_many(vec![self.clone()])
395 }
396}
397
398impl IntoP4Selection for &str {
399 fn into_selection(self) -> P4Selection {
400 P4Selection::new_many(vec![self.to_string()])
401 }
402}
403
404impl<S> IntoP4Selection for Vec<S>
405where
406 S: Into<String>,
407{
408 fn into_selection(self) -> P4Selection {
409 P4Selection::new_many(self.into_iter().map(Into::into).collect::<Vec<_>>())
410 }
411}
412
413impl<S> IntoP4Selection for &[S]
414where
415 S: Clone + Into<String>,
416{
417 fn into_selection(self) -> P4Selection {
418 P4Selection::new_many(self.iter().cloned().map(Into::into).collect::<Vec<_>>())
419 }
420}
421
422impl<S, const N: usize> IntoP4Selection for [S; N]
423where
424 S: Into<String>,
425{
426 fn into_selection(self) -> P4Selection {
427 P4Selection::new_many(self.into_iter().map(Into::into).collect::<Vec<_>>())
428 }
429}
430
431impl<S, const N: usize> IntoP4Selection for &[S; N]
432where
433 S: Clone + Into<String>,
434{
435 fn into_selection(self) -> P4Selection {
436 P4Selection::new_many(self.iter().cloned().map(Into::into).collect::<Vec<_>>())
437 }
438}
439
440#[derive(Clone, Debug, Serialize, Deserialize)]
460pub enum Topology {
461 Full {
463 k1: P4Selection,
465 k2: P4Selection,
467 k3: P4Selection,
469 k4: P4Selection,
471 },
472 MissingK1 {
474 k2: P4Selection,
476 k3: P4Selection,
478 k4: P4Selection,
480 },
481 MissingK2 {
483 k1: P4Selection,
485 k3: P4Selection,
487 k4: P4Selection,
489 },
490 MissingK3 {
492 k1: P4Selection,
494 k2: P4Selection,
496 k4: P4Selection,
498 },
499 MissingK4 {
501 k1: P4Selection,
503 k2: P4Selection,
505 k3: P4Selection,
507 },
508}
509
510impl Topology {
511 pub fn new<K1, K2, K3, K4>(k1: K1, k2: K2, k3: K3, k4: K4) -> Self
513 where
514 K1: IntoP4Selection,
515 K2: IntoP4Selection,
516 K3: IntoP4Selection,
517 K4: IntoP4Selection,
518 {
519 Self::Full {
520 k1: k1.into_selection(),
521 k2: k2.into_selection(),
522 k3: k3.into_selection(),
523 k4: k4.into_selection(),
524 }
525 }
526
527 pub fn missing_k1<K2, K3, K4>(k2: K2, k3: K3, k4: K4) -> Self
529 where
530 K2: IntoP4Selection,
531 K3: IntoP4Selection,
532 K4: IntoP4Selection,
533 {
534 Self::MissingK1 {
535 k2: k2.into_selection(),
536 k3: k3.into_selection(),
537 k4: k4.into_selection(),
538 }
539 }
540
541 pub fn missing_k2<K1, K3, K4>(k1: K1, k3: K3, k4: K4) -> Self
543 where
544 K1: IntoP4Selection,
545 K3: IntoP4Selection,
546 K4: IntoP4Selection,
547 {
548 Self::MissingK2 {
549 k1: k1.into_selection(),
550 k3: k3.into_selection(),
551 k4: k4.into_selection(),
552 }
553 }
554
555 pub fn missing_k3<K1, K2, K4>(k1: K1, k2: K2, k4: K4) -> Self
557 where
558 K1: IntoP4Selection,
559 K2: IntoP4Selection,
560 K4: IntoP4Selection,
561 {
562 Self::MissingK3 {
563 k1: k1.into_selection(),
564 k2: k2.into_selection(),
565 k4: k4.into_selection(),
566 }
567 }
568
569 pub fn missing_k4<K1, K2, K3>(k1: K1, k2: K2, k3: K3) -> Self
571 where
572 K1: IntoP4Selection,
573 K2: IntoP4Selection,
574 K3: IntoP4Selection,
575 {
576 Self::MissingK4 {
577 k1: k1.into_selection(),
578 k2: k2.into_selection(),
579 k3: k3.into_selection(),
580 }
581 }
582
583 pub fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
585 match self {
586 Topology::Full { k1, k2, k3, k4 } => {
587 k1.bind(metadata)?;
588 k2.bind(metadata)?;
589 k3.bind(metadata)?;
590 k4.bind(metadata)?;
591 }
592 Topology::MissingK1 { k2, k3, k4 } => {
593 k2.bind(metadata)?;
594 k3.bind(metadata)?;
595 k4.bind(metadata)?;
596 }
597 Topology::MissingK2 { k1, k3, k4 } => {
598 k1.bind(metadata)?;
599 k3.bind(metadata)?;
600 k4.bind(metadata)?;
601 }
602 Topology::MissingK3 { k1, k2, k4 } => {
603 k1.bind(metadata)?;
604 k2.bind(metadata)?;
605 k4.bind(metadata)?;
606 }
607 Topology::MissingK4 { k1, k2, k3 } => {
608 k1.bind(metadata)?;
609 k2.bind(metadata)?;
610 k3.bind(metadata)?;
611 }
612 }
613 Ok(())
614 }
615
616 pub fn com_boost_vector(&self, event: &EventData) -> Vec3 {
619 match self {
620 Topology::Full { k3, k4, .. }
621 | Topology::MissingK1 { k3, k4, .. }
622 | Topology::MissingK2 { k3, k4, .. } => {
623 -(k3.momentum(event) + k4.momentum(event)).beta()
624 }
625 Topology::MissingK3 { k1, k2, .. } | Topology::MissingK4 { k1, k2, .. } => {
626 -(k1.momentum(event) + k2.momentum(event)).beta()
627 }
628 }
629 }
630
631 pub fn k1(&self, event: &EventData) -> Vec4 {
633 match self {
634 Topology::Full { k1, .. }
635 | Topology::MissingK2 { k1, .. }
636 | Topology::MissingK3 { k1, .. }
637 | Topology::MissingK4 { k1, .. } => k1.momentum(event),
638 Topology::MissingK1 { k2, k3, k4 } => {
639 k3.momentum(event) + k4.momentum(event) - k2.momentum(event)
640 }
641 }
642 }
643
644 pub fn k2(&self, event: &EventData) -> Vec4 {
646 match self {
647 Topology::Full { k2, .. }
648 | Topology::MissingK1 { k2, .. }
649 | Topology::MissingK3 { k2, .. }
650 | Topology::MissingK4 { k2, .. } => k2.momentum(event),
651 Topology::MissingK2 { k1, k3, k4 } => {
652 k3.momentum(event) + k4.momentum(event) - k1.momentum(event)
653 }
654 }
655 }
656
657 pub fn k3(&self, event: &EventData) -> Vec4 {
659 match self {
660 Topology::Full { k3, .. }
661 | Topology::MissingK1 { k3, .. }
662 | Topology::MissingK2 { k3, .. }
663 | Topology::MissingK4 { k3, .. } => k3.momentum(event),
664 Topology::MissingK3 { k1, k2, k4 } => {
665 k1.momentum(event) + k2.momentum(event) - k4.momentum(event)
666 }
667 }
668 }
669
670 pub fn k4(&self, event: &EventData) -> Vec4 {
672 match self {
673 Topology::Full { k4, .. }
674 | Topology::MissingK1 { k4, .. }
675 | Topology::MissingK2 { k4, .. }
676 | Topology::MissingK3 { k4, .. } => k4.momentum(event),
677 Topology::MissingK4 { k1, k2, k3 } => {
678 k1.momentum(event) + k2.momentum(event) - k3.momentum(event)
679 }
680 }
681 }
682
683 pub fn k1_com(&self, event: &EventData) -> Vec4 {
685 self.k1(event).boost(&self.com_boost_vector(event))
686 }
687
688 pub fn k2_com(&self, event: &EventData) -> Vec4 {
690 self.k2(event).boost(&self.com_boost_vector(event))
691 }
692
693 pub fn k3_com(&self, event: &EventData) -> Vec4 {
695 self.k3(event).boost(&self.com_boost_vector(event))
696 }
697
698 pub fn k4_com(&self, event: &EventData) -> Vec4 {
700 self.k4(event).boost(&self.com_boost_vector(event))
701 }
702
703 pub fn k1_names(&self) -> Option<&[String]> {
705 match self {
706 Topology::Full { k1, .. }
707 | Topology::MissingK2 { k1, .. }
708 | Topology::MissingK3 { k1, .. }
709 | Topology::MissingK4 { k1, .. } => Some(k1.names()),
710 Topology::MissingK1 { .. } => None,
711 }
712 }
713
714 pub fn k2_names(&self) -> Option<&[String]> {
716 match self {
717 Topology::Full { k2, .. }
718 | Topology::MissingK1 { k2, .. }
719 | Topology::MissingK3 { k2, .. }
720 | Topology::MissingK4 { k2, .. } => Some(k2.names()),
721 Topology::MissingK2 { .. } => None,
722 }
723 }
724
725 pub fn k3_names(&self) -> Option<&[String]> {
727 match self {
728 Topology::Full { k3, .. }
729 | Topology::MissingK1 { k3, .. }
730 | Topology::MissingK2 { k3, .. }
731 | Topology::MissingK4 { k3, .. } => Some(k3.names()),
732 Topology::MissingK3 { .. } => None,
733 }
734 }
735
736 pub fn k4_names(&self) -> Option<&[String]> {
738 match self {
739 Topology::Full { k4, .. }
740 | Topology::MissingK1 { k4, .. }
741 | Topology::MissingK2 { k4, .. }
742 | Topology::MissingK3 { k4, .. } => Some(k4.names()),
743 Topology::MissingK4 { .. } => None,
744 }
745 }
746}
747
748impl Display for Topology {
749 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
750 write!(
751 f,
752 "Topology(k1=[{}], k2=[{}], k3=[{}], k4=[{}])",
753 format_topology_names(self.k1_names()),
754 format_topology_names(self.k2_names()),
755 format_topology_names(self.k3_names()),
756 format_topology_names(self.k4_names())
757 )
758 }
759}
760
761fn format_topology_names(names: Option<&[String]>) -> String {
762 match names {
763 Some(names) if !names.is_empty() => names_to_string(names),
764 Some(_) => String::new(),
765 None => "<reconstructed>".to_string(),
766 }
767}
768
769#[derive(Clone, Debug, Serialize, Deserialize)]
770struct AuxSelection {
771 name: String,
772 #[serde(skip, default)]
773 index: Option<usize>,
774}
775
776impl AuxSelection {
777 fn new<S: Into<String>>(name: S) -> Self {
778 Self {
779 name: name.into(),
780 index: None,
781 }
782 }
783
784 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
785 let idx = metadata
786 .aux_index(&self.name)
787 .ok_or_else(|| LadduError::UnknownName {
788 category: "aux",
789 name: self.name.clone(),
790 })?;
791 self.index = Some(idx);
792 Ok(())
793 }
794
795 fn index(&self) -> usize {
796 self.index.expect("AuxSelection must be bound before use")
797 }
798
799 fn name(&self) -> &str {
800 &self.name
801 }
802}
803
804#[derive(Clone, Debug, Serialize, Deserialize)]
807pub struct Mass {
808 constituents: P4Selection,
809}
810impl Mass {
811 pub fn new<C>(constituents: C) -> Self
814 where
815 C: IntoP4Selection,
816 {
817 Self {
818 constituents: constituents.into_selection(),
819 }
820 }
821}
822impl Display for Mass {
823 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824 write!(
825 f,
826 "Mass(constituents=[{}])",
827 names_to_string(self.constituents.names())
828 )
829 }
830}
831#[typetag::serde]
832impl Variable for Mass {
833 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
834 self.constituents.bind(metadata)
835 }
836
837 fn value(&self, event: &EventData) -> f64 {
838 self.constituents.momentum(event).m()
839 }
840}
841
842#[derive(Clone, Debug, Serialize, Deserialize)]
845pub struct CosTheta {
846 topology: Topology,
847 daughter: P4Selection,
848 frame: Frame,
849}
850impl Display for CosTheta {
851 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
852 write!(
853 f,
854 "CosTheta(topology={}, daughter=[{}], frame={})",
855 self.topology,
856 names_to_string(self.daughter.names()),
857 self.frame
858 )
859 }
860}
861impl CosTheta {
862 pub fn new<D>(topology: Topology, daughter: D, frame: Frame) -> Self
866 where
867 D: IntoP4Selection,
868 {
869 Self {
870 topology,
871 daughter: daughter.into_selection(),
872 frame,
873 }
874 }
875}
876
877#[typetag::serde]
878impl Variable for CosTheta {
879 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
880 self.topology.bind(metadata)?;
881 self.daughter.bind(metadata)?;
882 Ok(())
883 }
884
885 fn value(&self, event: &EventData) -> f64 {
886 let com_boost = self.topology.com_boost_vector(event);
887 let beam = self.topology.k1_com(event);
888 let resonance = self.topology.k3_com(event);
889 let daughter = self.daughter.momentum(event).boost(&com_boost);
890 let daughter_res = daughter.boost(&-resonance.beta());
891 let plane_normal = beam.vec3().cross(&resonance.vec3()).unit();
892 let z = match self.frame {
893 Frame::Helicity => {
894 let recoil = self.topology.k4_com(event);
895 let recoil_res = recoil.boost(&-resonance.beta());
896 (-recoil_res.vec3()).unit()
897 }
898 Frame::GottfriedJackson => {
899 let beam_res = beam.boost(&-resonance.beta());
900 beam_res.vec3().unit()
901 }
902 };
903 let x = plane_normal.cross(&z).unit();
904 let y = z.cross(&x).unit();
905 let angles = Vec3::new(
906 daughter_res.vec3().dot(&x),
907 daughter_res.vec3().dot(&y),
908 daughter_res.vec3().dot(&z),
909 );
910 angles.costheta()
911 }
912}
913
914#[derive(Clone, Debug, Serialize, Deserialize)]
917pub struct Phi {
918 topology: Topology,
919 daughter: P4Selection,
920 frame: Frame,
921}
922impl Display for Phi {
923 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
924 write!(
925 f,
926 "Phi(topology={}, daughter=[{}], frame={})",
927 self.topology,
928 names_to_string(self.daughter.names()),
929 self.frame
930 )
931 }
932}
933impl Phi {
934 pub fn new<D>(topology: Topology, daughter: D, frame: Frame) -> Self
938 where
939 D: IntoP4Selection,
940 {
941 Self {
942 topology,
943 daughter: daughter.into_selection(),
944 frame,
945 }
946 }
947}
948#[typetag::serde]
949impl Variable for Phi {
950 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
951 self.topology.bind(metadata)?;
952 self.daughter.bind(metadata)?;
953 Ok(())
954 }
955
956 fn value(&self, event: &EventData) -> f64 {
957 let com_boost = self.topology.com_boost_vector(event);
958 let beam = self.topology.k1_com(event);
959 let resonance = self.topology.k3_com(event);
960 let daughter = self.daughter.momentum(event).boost(&com_boost);
961 let daughter_res = daughter.boost(&-resonance.beta());
962 let plane_normal = beam.vec3().cross(&resonance.vec3()).unit();
963 let z = match self.frame {
964 Frame::Helicity => {
965 let recoil = self.topology.k4_com(event);
966 let recoil_res = recoil.boost(&-resonance.beta());
967 (-recoil_res.vec3()).unit()
968 }
969 Frame::GottfriedJackson => {
970 let beam_res = beam.boost(&-resonance.beta());
971 beam_res.vec3().unit()
972 }
973 };
974 let x = plane_normal.cross(&z).unit();
975 let y = z.cross(&x).unit();
976 let angles = Vec3::new(
977 daughter_res.vec3().dot(&x),
978 daughter_res.vec3().dot(&y),
979 daughter_res.vec3().dot(&z),
980 );
981 angles.phi()
982 }
983}
984
985#[derive(Clone, Debug, Serialize, Deserialize)]
987pub struct Angles {
988 pub costheta: CosTheta,
990 pub phi: Phi,
992}
993
994impl Display for Angles {
995 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
996 write!(
997 f,
998 "Angles(topology={}, daughter=[{}], frame={})",
999 self.costheta.topology,
1000 names_to_string(self.costheta.daughter.names()),
1001 self.costheta.frame
1002 )
1003 }
1004}
1005impl Angles {
1006 pub fn new<D>(topology: Topology, daughter: D, frame: Frame) -> Self
1009 where
1010 D: IntoP4Selection,
1011 {
1012 let daughter_vertex = daughter.into_selection();
1013 let costheta = CosTheta::new(topology.clone(), daughter_vertex.clone(), frame);
1014 let phi = Phi::new(topology, daughter_vertex, frame);
1015 Self { costheta, phi }
1016 }
1017}
1018
1019#[derive(Clone, Debug, Serialize, Deserialize)]
1021pub struct PolAngle {
1022 topology: Topology,
1023 angle_aux: AuxSelection,
1024}
1025impl Display for PolAngle {
1026 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1027 write!(
1028 f,
1029 "PolAngle(topology={}, angle_aux={})",
1030 self.topology,
1031 self.angle_aux.name(),
1032 )
1033 }
1034}
1035impl PolAngle {
1036 pub fn new<A>(topology: Topology, angle_aux: A) -> Self
1039 where
1040 A: Into<String>,
1041 {
1042 Self {
1043 topology,
1044 angle_aux: AuxSelection::new(angle_aux.into()),
1045 }
1046 }
1047}
1048#[typetag::serde]
1049impl Variable for PolAngle {
1050 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1051 self.topology.bind(metadata)?;
1052 self.angle_aux.bind(metadata)?;
1053 Ok(())
1054 }
1055
1056 fn value(&self, event: &EventData) -> f64 {
1057 let beam = self.topology.k1(event);
1058 let recoil = self.topology.k4(event);
1059 let pol_angle = event.aux[self.angle_aux.index()];
1060 let polarization = Vec3::new(pol_angle.cos(), pol_angle.sin(), 0.0);
1061 let y = beam.vec3().cross(&-recoil.vec3()).unit();
1062 let numerator = y.dot(&polarization);
1063 let denominator = beam.vec3().unit().dot(&polarization.cross(&y));
1064 f64::atan2(numerator, denominator)
1065 }
1066}
1067
1068#[derive(Clone, Debug, Serialize, Deserialize)]
1070pub struct PolMagnitude {
1071 magnitude_aux: AuxSelection,
1072}
1073impl Display for PolMagnitude {
1074 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1075 write!(
1076 f,
1077 "PolMagnitude(magnitude_aux={})",
1078 self.magnitude_aux.name(),
1079 )
1080 }
1081}
1082impl PolMagnitude {
1083 pub fn new<S: Into<String>>(magnitude_aux: S) -> Self {
1086 Self {
1087 magnitude_aux: AuxSelection::new(magnitude_aux.into()),
1088 }
1089 }
1090}
1091#[typetag::serde]
1092impl Variable for PolMagnitude {
1093 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1094 self.magnitude_aux.bind(metadata)
1095 }
1096
1097 fn value(&self, event: &EventData) -> f64 {
1098 event.aux[self.magnitude_aux.index()]
1099 }
1100}
1101
1102#[derive(Clone, Debug, Serialize, Deserialize)]
1104pub struct Polarization {
1105 pub pol_magnitude: PolMagnitude,
1107 pub pol_angle: PolAngle,
1109}
1110impl Display for Polarization {
1111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1112 write!(
1113 f,
1114 "Polarization(topology={}, magnitude_aux={}, angle_aux={})",
1115 self.pol_angle.topology,
1116 self.pol_magnitude.magnitude_aux.name(),
1117 self.pol_angle.angle_aux.name(),
1118 )
1119 }
1120}
1121impl Polarization {
1122 pub fn new<M, A>(topology: Topology, magnitude_aux: M, angle_aux: A) -> Self
1129 where
1130 M: Into<String>,
1131 A: Into<String>,
1132 {
1133 let magnitude_aux = magnitude_aux.into();
1134 let angle_aux = angle_aux.into();
1135 assert!(
1136 magnitude_aux != angle_aux,
1137 "Polarization magnitude and angle must reference distinct auxiliary columns"
1138 );
1139 Self {
1140 pol_magnitude: PolMagnitude::new(magnitude_aux),
1141 pol_angle: PolAngle::new(topology, angle_aux),
1142 }
1143 }
1144}
1145
1146#[derive(Clone, Debug, Serialize, Deserialize)]
1157pub struct Mandelstam {
1158 topology: Topology,
1159 channel: Channel,
1160}
1161impl Display for Mandelstam {
1162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1163 write!(
1164 f,
1165 "Mandelstam(topology={}, channel={})",
1166 self.topology, self.channel,
1167 )
1168 }
1169}
1170impl Mandelstam {
1171 pub fn new(topology: Topology, channel: Channel) -> Self {
1173 Self { topology, channel }
1174 }
1175}
1176
1177#[typetag::serde]
1178impl Variable for Mandelstam {
1179 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1180 self.topology.bind(metadata)
1181 }
1182
1183 fn value(&self, event: &EventData) -> f64 {
1184 match self.channel {
1185 Channel::S => {
1186 let k1 = self.topology.k1(event);
1187 let k2 = self.topology.k2(event);
1188 (k1 + k2).mag2()
1189 }
1190 Channel::T => {
1191 let k1 = self.topology.k1(event);
1192 let k3 = self.topology.k3(event);
1193 (k1 - k3).mag2()
1194 }
1195 Channel::U => {
1196 let k1 = self.topology.k1(event);
1197 let k4 = self.topology.k4(event);
1198 (k1 - k4).mag2()
1199 }
1200 }
1201 }
1202}
1203
1204#[cfg(test)]
1205mod tests {
1206 use super::*;
1207 use crate::data::{test_dataset, test_event};
1208 use approx::assert_relative_eq;
1209
1210 fn topology_test_metadata() -> DatasetMetadata {
1211 DatasetMetadata::new(
1212 vec!["beam", "target", "resonance", "recoil"],
1213 Vec::<String>::new(),
1214 )
1215 .expect("topology metadata should be valid")
1216 }
1217
1218 fn topology_test_event() -> EventData {
1219 let p1 = Vec4::new(0.0, 0.0, 3.0, 3.5);
1220 let p2 = Vec4::new(0.0, 0.0, -3.0, 3.5);
1221 let p3 = Vec4::new(0.5, -0.25, 1.0, 1.9);
1222 let p4 = p1 + p2 - p3;
1223 EventData {
1224 p4s: vec![p1, p2, p3, p4],
1225 aux: vec![],
1226 weight: 1.0,
1227 }
1228 }
1229
1230 fn reaction_topology() -> Topology {
1231 Topology::missing_k2("beam", ["kshort1", "kshort2"], "proton")
1232 }
1233
1234 #[test]
1235 #[allow(clippy::needless_borrows_for_generic_args)]
1236 fn test_topology_accepts_varied_inputs() {
1237 let topo = Topology::new(
1238 "particle1",
1239 ["particle2a", "particle2b"],
1240 &["particle3"],
1241 "particle4".to_string(),
1242 );
1243 assert_eq!(
1244 topo.k1_names()
1245 .unwrap()
1246 .iter()
1247 .map(String::as_str)
1248 .collect::<Vec<_>>(),
1249 vec!["particle1"]
1250 );
1251 assert_eq!(
1252 topo.k2_names()
1253 .unwrap()
1254 .iter()
1255 .map(String::as_str)
1256 .collect::<Vec<_>>(),
1257 vec!["particle2a", "particle2b"]
1258 );
1259 let missing = Topology::missing_k2("particle1", vec!["particle3"], "particle4");
1260 assert!(missing.k2_names().is_none());
1261 assert!(missing.to_string().contains("<reconstructed>"));
1262 }
1263
1264 #[test]
1265 fn test_topology_reconstructs_missing_vertices() {
1266 let metadata = topology_test_metadata();
1267 let event = topology_test_event();
1268
1269 let mut full = Topology::new("beam", "target", "resonance", "recoil");
1270 full.bind(&metadata).unwrap();
1271 assert_relative_eq!(full.k3(&event), event.p4s[2], epsilon = 1e-12);
1272
1273 let mut missing_k1 = Topology::missing_k1("target", "resonance", "recoil");
1274 missing_k1.bind(&metadata).unwrap();
1275 assert!(missing_k1.k1_names().is_none());
1276 assert_relative_eq!(missing_k1.k1(&event), event.p4s[0], epsilon = 1e-12);
1277
1278 let mut missing_k2 = Topology::missing_k2("beam", "resonance", "recoil");
1279 missing_k2.bind(&metadata).unwrap();
1280 assert!(missing_k2.k2_names().is_none());
1281 assert_relative_eq!(missing_k2.k2(&event), event.p4s[1], epsilon = 1e-12);
1282
1283 let mut missing_k3 = Topology::missing_k3("beam", "target", "recoil");
1284 missing_k3.bind(&metadata).unwrap();
1285 assert!(missing_k3.k3_names().is_none());
1286 assert_relative_eq!(missing_k3.k3(&event), event.p4s[2], epsilon = 1e-12);
1287
1288 let mut missing_k4 = Topology::missing_k4("beam", "target", "resonance");
1289 missing_k4.bind(&metadata).unwrap();
1290 assert!(missing_k4.k4_names().is_none());
1291 assert_relative_eq!(missing_k4.k4(&event), event.p4s[3], epsilon = 1e-12);
1292 }
1293
1294 #[test]
1295 fn test_topology_com_helpers_match_manual_boost() {
1296 let metadata = topology_test_metadata();
1297 let event = topology_test_event();
1298 let mut topo = Topology::new("beam", "target", "resonance", "recoil");
1299 topo.bind(&metadata).unwrap();
1300 let beta = topo.com_boost_vector(&event);
1301 assert_relative_eq!(topo.k1_com(&event), topo.k1(&event).boost(&beta));
1302 assert_relative_eq!(topo.k2_com(&event), topo.k2(&event).boost(&beta));
1303 assert_relative_eq!(topo.k3_com(&event), topo.k3(&event).boost(&beta));
1304 assert_relative_eq!(topo.k4_com(&event), topo.k4(&event).boost(&beta));
1305 }
1306
1307 #[test]
1308 fn test_mass_single_particle() {
1309 let dataset = test_dataset();
1310 let mut mass = Mass::new("proton");
1311 mass.bind(dataset.metadata()).unwrap();
1312 assert_relative_eq!(mass.value(&dataset[0]), 1.007);
1313 }
1314
1315 #[test]
1316 fn test_mass_multiple_particles() {
1317 let dataset = test_dataset();
1318 let mut mass = Mass::new(["kshort1", "kshort2"]);
1319 mass.bind(dataset.metadata()).unwrap();
1320 assert_relative_eq!(mass.value(&dataset[0]), 1.3743786309153077);
1321 }
1322
1323 #[test]
1324 fn test_mass_display() {
1325 let mass = Mass::new(["kshort1", "kshort2"]);
1326 assert_eq!(mass.to_string(), "Mass(constituents=[kshort1, kshort2])");
1327 }
1328
1329 #[test]
1330 fn test_costheta_helicity() {
1331 let dataset = test_dataset();
1332 let mut costheta = CosTheta::new(reaction_topology(), "kshort1", Frame::Helicity);
1333 costheta.bind(dataset.metadata()).unwrap();
1334 assert_relative_eq!(
1335 costheta.value(&dataset[0]),
1336 -0.4611175068834238,
1337 epsilon = 1e-12
1338 );
1339 }
1340
1341 #[test]
1342 fn test_costheta_display() {
1343 let costheta = CosTheta::new(reaction_topology(), "kshort1", Frame::Helicity);
1344 assert_eq!(
1345 costheta.to_string(),
1346 "CosTheta(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), daughter=[kshort1], frame=Helicity)"
1347 );
1348 }
1349
1350 #[test]
1351 fn test_phi_helicity() {
1352 let dataset = test_dataset();
1353 let mut phi = Phi::new(reaction_topology(), "kshort1", Frame::Helicity);
1354 phi.bind(dataset.metadata()).unwrap();
1355 assert_relative_eq!(phi.value(&dataset[0]), -2.657462587335066, epsilon = 1e-12);
1356 }
1357
1358 #[test]
1359 fn test_phi_display() {
1360 let phi = Phi::new(reaction_topology(), "kshort1", Frame::Helicity);
1361 assert_eq!(
1362 phi.to_string(),
1363 "Phi(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), daughter=[kshort1], frame=Helicity)"
1364 );
1365 }
1366
1367 #[test]
1368 fn test_costheta_gottfried_jackson() {
1369 let dataset = test_dataset();
1370 let mut costheta = CosTheta::new(reaction_topology(), "kshort1", Frame::GottfriedJackson);
1371 costheta.bind(dataset.metadata()).unwrap();
1372 assert_relative_eq!(
1373 costheta.value(&dataset[0]),
1374 0.09198832278031577,
1375 epsilon = 1e-12
1376 );
1377 }
1378
1379 #[test]
1380 fn test_phi_gottfried_jackson() {
1381 let dataset = test_dataset();
1382 let mut phi = Phi::new(reaction_topology(), "kshort1", Frame::GottfriedJackson);
1383 phi.bind(dataset.metadata()).unwrap();
1384 assert_relative_eq!(phi.value(&dataset[0]), -2.713913199133907, epsilon = 1e-12);
1385 }
1386
1387 #[test]
1388 fn test_angles() {
1389 let dataset = test_dataset();
1390 let mut angles = Angles::new(reaction_topology(), "kshort1", Frame::Helicity);
1391 angles.costheta.bind(dataset.metadata()).unwrap();
1392 angles.phi.bind(dataset.metadata()).unwrap();
1393 assert_relative_eq!(
1394 angles.costheta.value(&dataset[0]),
1395 -0.4611175068834238,
1396 epsilon = 1e-12
1397 );
1398 assert_relative_eq!(
1399 angles.phi.value(&dataset[0]),
1400 -2.657462587335066,
1401 epsilon = 1e-12
1402 );
1403 }
1404
1405 #[test]
1406 fn test_angles_display() {
1407 let angles = Angles::new(reaction_topology(), "kshort1", Frame::Helicity);
1408 assert_eq!(
1409 angles.to_string(),
1410 "Angles(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), daughter=[kshort1], frame=Helicity)"
1411 );
1412 }
1413
1414 #[test]
1415 fn test_pol_angle() {
1416 let dataset = test_dataset();
1417 let mut pol_angle = PolAngle::new(reaction_topology(), "pol_angle");
1418 pol_angle.bind(dataset.metadata()).unwrap();
1419 assert_relative_eq!(pol_angle.value(&dataset[0]), 1.935929887818673);
1420 }
1421
1422 #[test]
1423 fn test_pol_angle_display() {
1424 let pol_angle = PolAngle::new(reaction_topology(), "pol_angle");
1425 assert_eq!(
1426 pol_angle.to_string(),
1427 "PolAngle(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), angle_aux=pol_angle)"
1428 );
1429 }
1430
1431 #[test]
1432 fn test_pol_magnitude() {
1433 let dataset = test_dataset();
1434 let mut pol_magnitude = PolMagnitude::new("pol_magnitude");
1435 pol_magnitude.bind(dataset.metadata()).unwrap();
1436 assert_relative_eq!(pol_magnitude.value(&dataset[0]), 0.38562805);
1437 }
1438
1439 #[test]
1440 fn test_pol_magnitude_display() {
1441 let pol_magnitude = PolMagnitude::new("pol_magnitude");
1442 assert_eq!(
1443 pol_magnitude.to_string(),
1444 "PolMagnitude(magnitude_aux=pol_magnitude)"
1445 );
1446 }
1447
1448 #[test]
1449 fn test_polarization() {
1450 let dataset = test_dataset();
1451 let mut polarization = Polarization::new(reaction_topology(), "pol_magnitude", "pol_angle");
1452 polarization.pol_angle.bind(dataset.metadata()).unwrap();
1453 polarization.pol_magnitude.bind(dataset.metadata()).unwrap();
1454 assert_relative_eq!(polarization.pol_angle.value(&dataset[0]), 1.935929887818673);
1455 assert_relative_eq!(polarization.pol_magnitude.value(&dataset[0]), 0.38562805);
1456 }
1457
1458 #[test]
1459 fn test_polarization_display() {
1460 let polarization = Polarization::new(reaction_topology(), "pol_magnitude", "pol_angle");
1461 assert_eq!(
1462 polarization.to_string(),
1463 "Polarization(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), magnitude_aux=pol_magnitude, angle_aux=pol_angle)"
1464 );
1465 }
1466
1467 #[test]
1468 fn test_mandelstam() {
1469 let dataset = test_dataset();
1470 let metadata = dataset.metadata();
1471 let mut s = Mandelstam::new(reaction_topology(), Channel::S);
1472 let mut t = Mandelstam::new(reaction_topology(), Channel::T);
1473 let mut u = Mandelstam::new(reaction_topology(), Channel::U);
1474 for variable in [&mut s, &mut t, &mut u] {
1475 variable.bind(metadata).unwrap();
1476 }
1477 let event = &dataset[0];
1478 assert_relative_eq!(s.value(event), 18.504011052120063);
1479 assert_relative_eq!(t.value(event), -0.19222859969898076);
1480 assert_relative_eq!(u.value(event), -14.404198931464428);
1481 let mut direct_topology = reaction_topology();
1482 direct_topology.bind(metadata).unwrap();
1483 let k2 = direct_topology.k2(event);
1484 let k3 = direct_topology.k3(event);
1485 let k4 = direct_topology.k4(event);
1486 assert_relative_eq!(s.value(event), (k3 + k4).mag2());
1487 assert_relative_eq!(t.value(event), (k2 - k4).mag2());
1488 assert_relative_eq!(u.value(event), (k3 - k2).mag2());
1489 let m2_beam = test_event().get_p4_sum([0]).m2();
1490 let m2_recoil = test_event().get_p4_sum([1]).m2();
1491 let m2_res = test_event().get_p4_sum([2, 3]).m2();
1492 assert_relative_eq!(
1493 s.value(event) + t.value(event) + u.value(event) - m2_beam - m2_recoil - m2_res,
1494 1.00,
1495 epsilon = 1e-2
1496 );
1497 }
1500
1501 #[test]
1502 fn test_mandelstam_display() {
1503 let s = Mandelstam::new(reaction_topology(), Channel::S);
1504 assert_eq!(
1505 s.to_string(),
1506 "Mandelstam(topology=Topology(k1=[beam], k2=[<reconstructed>], k3=[kshort1, kshort2], k4=[proton]), channel=s)"
1507 );
1508 }
1509
1510 #[test]
1511 fn test_variable_value_on() {
1512 let dataset = test_dataset();
1513 let mass = Mass::new(["kshort1", "kshort2"]);
1514
1515 let values = mass.value_on(&dataset).unwrap();
1516 assert_eq!(values.len(), 1);
1517 assert_relative_eq!(values[0], 1.3743786309153077);
1518 }
1519}