Skip to main content

gammalooprs/momentum/
mod.rs

1#![allow(dead_code)]
2
3pub mod sample;
4pub mod signature;
5
6use std::{
7    borrow::Borrow,
8    fmt::{Display, LowerExp},
9    ops::{Add, AddAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign},
10    str::FromStr,
11};
12
13use bincode_trait_derive::{Decode, Encode};
14use eyre::Context;
15use linnet::half_edge::involution::EdgeIndex;
16pub use linnet::num_traits::{Pow, Sign, SignError, SignOrZero};
17use momtrop::vector::Vector;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use spenso::{
21    algebra::{algebraic_traits::RefZero, complex::Complex, upgrading_arithmetic::FallibleAdd},
22    contraction::Contract,
23    iterators::IteratableTensor,
24    shadowing::Shadowable,
25    structure::{
26        CastStructure, IndexLess, NamedStructure, OrderedStructure, PermutedStructure,
27        TensorStructure, ToSymbolic,
28        abstract_index::AbstractIndex,
29        representation::{BaseRepName, Euclidean, LibraryRep, Minkowski, RepName},
30        slot::{DualSlotTo, Slot},
31    },
32    tensors::{
33        complex::RealOrComplexTensor,
34        data::{
35            DataIterator, DataTensor, DenseTensor, HasTensorData, SetTensorData, SparseTensor,
36            StorageTensor,
37        },
38        parametric::{
39            EvalTensor, FlatCoefficent, MixedTensor, ParamOrConcrete, atomcore::TensorAtomOps,
40        },
41    },
42};
43use symbolica::{
44    domains::{
45        dual::HyperDual,
46        float::{Complex as SymComplex, FloatLike as SymFloatLike},
47        rational::RationalField,
48    },
49    prelude::*,
50};
51use symbolica_utils::NoArgs;
52use thiserror::Error;
53
54use crate::{
55    GammaLoopContext,
56    settings::runtime::RotationSetting,
57    utils::{
58        ApproxEq, F, FloatLike, RefDefault, hyperdual_utils::new_constant, representations::GR,
59    },
60};
61
62#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Encode, Decode)]
63pub struct Energy<T> {
64    pub value: T,
65}
66
67impl<T> Energy<T> {
68    pub(crate) fn map_ref<U>(&self, f: &impl Fn(&T) -> U) -> Energy<U> {
69        Energy {
70            value: f(&self.value),
71        }
72    }
73
74    pub(crate) fn map<U>(self, f: &impl Fn(T) -> U) -> Energy<U> {
75        Energy {
76            value: f(self.value),
77        }
78    }
79}
80
81impl<T: FloatLike> ApproxEq<Energy<F<T>>, F<T>> for Energy<F<T>> {
82    fn approx_eq(&self, other: &Energy<F<T>>, threshold: &F<T>) -> bool {
83        self.value.approx_eq(&other.value, threshold)
84    }
85}
86
87impl<T: FloatLike> Energy<F<T>> {
88    pub(crate) fn higher(&self) -> Energy<F<T::Higher>>
89    where
90        T::Higher: FloatLike,
91    {
92        Energy {
93            value: self.value.higher(),
94        }
95    }
96
97    pub(crate) fn lower(&self) -> Energy<F<T::Lower>>
98    where
99        T::Lower: FloatLike,
100    {
101        Energy {
102            value: self.value.lower(),
103        }
104    }
105
106    pub(crate) fn from_ff64(energy: Energy<F<f64>>) -> Self {
107        Energy {
108            value: F::from_ff64(energy.value),
109        }
110    }
111}
112
113impl<T: FloatLike> From<Energy<T>> for Energy<F<T>> {
114    fn from(value: Energy<T>) -> Self {
115        Energy {
116            value: F(value.value),
117        }
118    }
119}
120
121impl<T: Real> Energy<T> {
122    pub(crate) fn zero(&self) -> Self {
123        Energy {
124            value: self.value.zero(),
125        }
126    }
127}
128
129impl<T> Add<Energy<T>> for Energy<T>
130where
131    T: Add<T, Output = T>,
132{
133    type Output = Energy<T>;
134    fn add(self, rhs: Energy<T>) -> Self::Output {
135        Energy {
136            value: self.value + rhs.value,
137        }
138    }
139}
140
141impl<T> Add<&Energy<T>> for Energy<T>
142where
143    T: for<'a> Add<&'a T, Output = T>,
144{
145    type Output = Energy<T>;
146    fn add(self, rhs: &Energy<T>) -> Self::Output {
147        Energy {
148            value: self.value + &rhs.value,
149        }
150    }
151}
152
153impl<'b, T> Add<&Energy<T>> for &'b Energy<T>
154where
155    &'b T: for<'a> Add<&'a T, Output = T>,
156{
157    type Output = Energy<T>;
158    fn add(self, rhs: &Energy<T>) -> Self::Output {
159        Energy {
160            value: &self.value + &rhs.value,
161        }
162    }
163}
164
165impl<T> Add<Energy<T>> for &Energy<T>
166where
167    T: for<'a> Add<&'a T, Output = T>,
168{
169    type Output = Energy<T>;
170    fn add(self, rhs: Energy<T>) -> Self::Output {
171        rhs + self
172    }
173}
174
175impl<T> AddAssign<Energy<T>> for Energy<T>
176where
177    T: AddAssign<T>,
178{
179    fn add_assign(&mut self, rhs: Energy<T>) {
180        self.value += rhs.value;
181    }
182}
183
184impl<'a, T> AddAssign<&'a Energy<T>> for Energy<T>
185where
186    T: AddAssign<&'a T>,
187{
188    fn add_assign(&mut self, rhs: &'a Energy<T>) {
189        self.value += &rhs.value;
190    }
191}
192
193impl<T> Sub<Energy<T>> for Energy<T>
194where
195    T: Sub<T, Output = T>,
196{
197    type Output = Energy<T>;
198    fn sub(self, rhs: Energy<T>) -> Self::Output {
199        Energy {
200            value: self.value - rhs.value,
201        }
202    }
203}
204
205impl<T> SubAssign<Energy<T>> for Energy<T>
206where
207    T: SubAssign<T>,
208{
209    fn sub_assign(&mut self, rhs: Energy<T>) {
210        self.value -= rhs.value;
211    }
212}
213
214impl<'a, T> SubAssign<&'a Energy<T>> for Energy<T>
215where
216    T: SubAssign<&'a T>,
217{
218    fn sub_assign(&mut self, rhs: &'a Energy<T>) {
219        self.value -= &rhs.value;
220    }
221}
222impl<T> Mul<Energy<T>> for Energy<T>
223where
224    T: Mul<T, Output = T>,
225{
226    type Output = T;
227    fn mul(self, rhs: Energy<T>) -> Self::Output {
228        self.value * rhs.value
229    }
230}
231
232impl<T> MulAssign<Energy<T>> for Energy<T>
233where
234    T: MulAssign<T>,
235{
236    fn mul_assign(&mut self, rhs: Energy<T>) {
237        self.value *= rhs.value;
238    }
239}
240
241impl<T> Neg for Energy<T>
242where
243    T: Neg<Output = T>,
244{
245    type Output = Energy<T>;
246    fn neg(self) -> Self::Output {
247        Energy { value: -self.value }
248    }
249}
250
251impl<T> Energy<T> {
252    pub(crate) fn new(value: T) -> Self {
253        Energy { value }
254    }
255
256    // pub(crate) fn from_three_momentum(three_momentum: &ThreeMomentum<T>) -> Self
257    // where
258    //     T: std::ops::Mul<Output = T> + std::ops::Add<Output = T> + Copy,
259    // {
260    //     let px2 = three_momentum.px * three_momentum.px;
261    //     let py2 = three_momentum.py * three_momentum.py;
262    //     let pz2 = three_momentum.pz * three_momentum.pz;
263    //     let p2 = px2 + py2 + pz2;
264    //     let value = (p2 + T::default()).sqrt();
265    //     Energy { value }
266    // }
267}
268
269// impl<U: Borrow<T>, T> Borrow<Energy<T>> for Energy<U> {
270//     fn borrow(&self) -> &Energy<T> {
271
272//     }
273// }
274
275impl<U, T: RefZero<U>> RefZero<Energy<U>> for Energy<T>
276where
277    Energy<T>: Borrow<Energy<U>>,
278{
279    fn ref_zero(&self) -> Energy<U> {
280        Energy {
281            value: self.value.ref_zero(),
282        }
283    }
284}
285
286impl Energy<Atom> {
287    pub(crate) fn new_parametric(id: usize) -> Self {
288        let value = parse!(&format!("E_{}", id));
289        Energy { value }
290    }
291}
292
293impl<T: Display> Display for Energy<T> {
294    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
295        write!(f, "E: {}", self.value)
296    }
297}
298
299impl<T: LowerExp> LowerExp for Energy<T> {
300    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
301        write!(f, "E: {:e}", self.value)
302    }
303}
304
305impl<T: Default> Default for Energy<T> {
306    fn default() -> Self {
307        Energy {
308            value: T::default(),
309        }
310    }
311}
312
313impl<T> From<T> for Energy<T> {
314    fn from(value: T) -> Self {
315        Energy { value }
316    }
317}
318
319#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Encode, Decode)]
320pub struct ThreeMomentum<T> {
321    pub px: T,
322    pub py: T,
323    pub pz: T,
324}
325
326impl<T> ThreeMomentum<T> {
327    pub(crate) fn map_ref<U>(&self, f: &impl Fn(&T) -> U) -> ThreeMomentum<U> {
328        ThreeMomentum {
329            px: f(&self.px),
330            py: f(&self.py),
331            pz: f(&self.pz),
332        }
333    }
334
335    pub(crate) fn map<U>(self, f: &impl Fn(T) -> U) -> ThreeMomentum<U> {
336        ThreeMomentum {
337            px: f(self.px),
338            py: f(self.py),
339            pz: f(self.pz),
340        }
341    }
342}
343
344impl<T: FloatLike> ApproxEq<ThreeMomentum<F<T>>, F<T>> for ThreeMomentum<F<T>> {
345    fn approx_eq(&self, other: &ThreeMomentum<F<T>>, threshold: &F<T>) -> bool {
346        F::approx_eq_iterator(
347            [&self.px, &self.py, &self.pz],
348            [&other.px, &other.py, &other.pz],
349            threshold,
350        )
351    }
352}
353
354pub struct ThreeRotation<T> {
355    pub map: fn(ThreeMomentum<T>) -> ThreeMomentum<T>,
356    pub inv_map: fn(ThreeMomentum<T>) -> ThreeMomentum<T>,
357}
358
359impl<T: Neg<Output = T>> ThreeRotation<T> {
360    pub(crate) fn half_pi_x() -> Self {
361        let map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
362            std::mem::swap(&mut momentum.py, &mut momentum.pz);
363            momentum.pz = -momentum.pz;
364            momentum
365        };
366
367        let inv_map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
368            momentum.pz = -momentum.pz;
369            std::mem::swap(&mut momentum.py, &mut momentum.pz);
370            momentum
371        };
372
373        ThreeRotation { map, inv_map }
374    }
375
376    pub(crate) fn half_pi_y() -> Self {
377        let map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
378            std::mem::swap(&mut momentum.px, &mut momentum.pz);
379            momentum.px = -momentum.px;
380            momentum
381        };
382
383        let inv_map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
384            momentum.px = -momentum.px;
385            std::mem::swap(&mut momentum.px, &mut momentum.pz);
386            momentum
387        };
388
389        ThreeRotation { map, inv_map }
390    }
391
392    pub(crate) fn half_pi_z() -> Self {
393        let map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
394            std::mem::swap(&mut momentum.px, &mut momentum.py);
395            momentum.py = -momentum.py;
396            momentum
397        };
398
399        let inv_map = |mut momentum: ThreeMomentum<T>| -> ThreeMomentum<T> {
400            momentum.py = -momentum.py;
401            std::mem::swap(&mut momentum.px, &mut momentum.py);
402            momentum
403        };
404
405        ThreeRotation { map, inv_map }
406    }
407}
408
409impl<T: FloatLike> ThreeMomentum<F<T>> {
410    pub(crate) fn higher(&self) -> ThreeMomentum<F<T::Higher>>
411    where
412        T::Higher: FloatLike,
413    {
414        ThreeMomentum {
415            px: self.px.higher(),
416            py: self.py.higher(),
417            pz: self.pz.higher(),
418        }
419    }
420
421    pub(crate) fn lower(&self) -> ThreeMomentum<F<T::Lower>>
422    where
423        T::Lower: FloatLike,
424    {
425        ThreeMomentum {
426            px: self.px.lower(),
427            py: self.py.lower(),
428            pz: self.pz.lower(),
429        }
430    }
431
432    pub(crate) fn from_ff64(three_mom: ThreeMomentum<F<f64>>) -> Self {
433        ThreeMomentum {
434            px: F::from_ff64(three_mom.px),
435            py: F::from_ff64(three_mom.py),
436            pz: F::from_ff64(three_mom.pz),
437        }
438    }
439}
440
441impl<T: FloatLike> From<ThreeMomentum<T>> for ThreeMomentum<F<T>> {
442    fn from(value: ThreeMomentum<T>) -> Self {
443        ThreeMomentum {
444            px: F(value.px),
445            py: F(value.py),
446            pz: F(value.pz),
447        }
448    }
449}
450
451impl<T> IntoIterator for ThreeMomentum<T> {
452    type Item = T;
453    type IntoIter = std::array::IntoIter<T, 3>;
454
455    fn into_iter(self) -> Self::IntoIter {
456        let [px, py, pz] = [self.px, self.py, self.pz];
457        [px, py, pz].into_iter()
458    }
459}
460
461impl<'a, T> IntoIterator for &'a ThreeMomentum<T> {
462    type Item = &'a T;
463    type IntoIter = std::array::IntoIter<&'a T, 3>;
464
465    fn into_iter(self) -> Self::IntoIter {
466        let [px, py, pz] = [&self.px, &self.py, &self.pz];
467        [px, py, pz].into_iter()
468    }
469}
470
471impl<T: Real> RefDefault for ThreeMomentum<T> {
472    fn default(&self) -> Self {
473        let zero = self.px.zero();
474        ThreeMomentum {
475            px: zero.clone(),
476            py: zero.clone(),
477            pz: zero.clone(),
478        }
479    }
480}
481
482impl<T: Real> ThreeMomentum<T> {
483    pub(crate) fn zero(&self) -> Self {
484        let zero = self.px.zero();
485        ThreeMomentum {
486            px: zero.clone(),
487            py: zero.clone(),
488            pz: zero.clone(),
489        }
490    }
491}
492
493impl<T: RefZero> RefZero for ThreeMomentum<T> {
494    fn ref_zero(&self) -> Self {
495        ThreeMomentum {
496            px: self.px.ref_zero(),
497            py: self.py.ref_zero(),
498            pz: self.pz.ref_zero(),
499        }
500    }
501}
502
503impl<T> ThreeMomentum<T> {
504    pub(crate) fn new(px: T, py: T, pz: T) -> Self {
505        ThreeMomentum { px, py, pz }
506    }
507
508    pub(crate) fn into_dense(self, index: AbstractIndex) -> DenseTensor<T, OrderedStructure>
509    where
510        T: Clone,
511    {
512        let structure =
513            PermutedStructure::from_iter(vec![Euclidean {}.new_slot(3, index)]).structure;
514        DenseTensor::from_data(vec![self.px, self.py, self.pz], structure).unwrap()
515    }
516
517    pub(crate) fn into_dense_param(self, index: AbstractIndex) -> DenseTensor<T, OrderedStructure>
518    where
519        T: Clone,
520    {
521        let structure =
522            PermutedStructure::from_iter(vec![Euclidean {}.new_slot(3, index)]).structure;
523        DenseTensor::from_data(vec![self.px, self.py, self.pz], structure).unwrap()
524    }
525}
526
527impl<T: FloatLike> ThreeMomentum<F<T>> {
528    pub(crate) fn to_f64(&self) -> ThreeMomentum<F<f64>> {
529        ThreeMomentum {
530            px: F(self.px.to_f64()),
531            py: F(self.py.to_f64()),
532            pz: F(self.pz.to_f64()),
533        }
534    }
535    /// Compute the phi-angle separation with p2.
536    pub(crate) fn getdelphi(&self, p2: &ThreeMomentum<F<T>>) -> F<T> {
537        let pt1 = self.pt();
538        let pt2 = p2.pt();
539        if pt1.is_zero() {
540            return pt1.max_value();
541        }
542        if pt2.is_zero() {
543            return pt2.max_value();
544        }
545
546        let mut tmp = self.px.clone() * &p2.px + self.py.clone() * &p2.py;
547
548        tmp /= pt1 * pt2;
549        if tmp.norm() > tmp.one() + tmp.epsilon() {
550            panic!("Cosine larger than 1. in phase-space cuts.")
551        }
552        if tmp.norm() > tmp.one() {
553            (tmp.clone() / tmp.norm()).acos()
554        } else {
555            tmp.acos()
556        }
557    }
558
559    /// Compute the deltaR separation with momentum p2.
560    #[inline]
561    pub(crate) fn delta_r(&self, p2: &ThreeMomentum<F<T>>) -> F<T>
562    where
563        T: Real,
564    {
565        let delta_eta = self.pseudo_rap() - p2.pseudo_rap();
566        let delta_phi = self.getdelphi(p2);
567        (delta_eta.square() + delta_phi.square()).sqrt()
568    }
569
570    pub(crate) fn rotate_mut(&mut self, alpha: &F<T>, beta: &F<T>, gamma: &F<T>) {
571        let sin_alpha = alpha.sin();
572        let cos_alpha = alpha.cos();
573        let sin_beta = beta.sin();
574        let cos_beta = beta.cos();
575        let sin_gamma = gamma.sin();
576        let cos_gamma = gamma.cos();
577
578        let px = self.px.clone();
579        let py = self.py.clone();
580        let pz = self.pz.clone();
581
582        self.px = cos_gamma.clone() * &cos_beta * &px
583            + (-(cos_alpha.clone()) * &sin_gamma + sin_alpha.clone() * &sin_beta * &cos_gamma)
584                * &py
585            + (sin_alpha.clone() * &sin_gamma + cos_alpha.clone() * &sin_beta * &cos_gamma) * &pz;
586
587        self.py = sin_gamma.clone() * &cos_beta * &px
588            + (cos_alpha.clone() * &cos_gamma + sin_alpha.clone() * &sin_beta * &sin_gamma) * &py
589            + (-sin_alpha.clone() * &cos_gamma + cos_alpha.clone() * &sin_beta * &sin_gamma) * &pz;
590
591        self.pz =
592            -sin_beta * &px + cos_beta.clone() * &sin_alpha * &py + cos_alpha * &cos_beta * &pz;
593    }
594
595    /// Compute transverse momentum.
596    #[inline]
597    pub(crate) fn pt(&self) -> F<T> {
598        (self.px.square() + self.py.square()).sqrt()
599    }
600
601    /// Compute pseudorapidity.
602    #[inline]
603    pub(crate) fn pseudo_rap(&self) -> F<T> {
604        let pt = self.pt();
605        if pt.less_than_epsilon() && self.pz.norm().less_than_epsilon() {
606            if self.pz.positive() {
607                return pt.max_value();
608            } else {
609                return pt.min_value();
610            }
611        }
612        let th = pt.atan2(&self.pz);
613        let two = pt.from_i64(2);
614        -(th / two).tan().ln()
615    }
616
617    pub(crate) fn cross_product(&self, rhs: &ThreeMomentum<F<T>>) -> ThreeMomentum<F<T>> {
618        ThreeMomentum {
619            px: &self.py * &rhs.pz - &self.pz * &rhs.py,
620            py: &self.pz * &rhs.px - &self.px * &rhs.pz,
621            pz: &self.px * &rhs.py - &self.py * &rhs.px,
622        }
623    }
624
625    // Rodriguez rotation formula
626    pub(crate) fn axis_angle_rotation(
627        &self,
628        cos_theta: &F<T>,
629        axis: &ThreeMomentum<F<T>>,
630    ) -> ThreeMomentum<F<T>> {
631        // ensure unit vector
632        let axis = axis * &axis.norm().inv();
633        let sin_theta = if cos_theta >= &cos_theta.one() {
634            cos_theta.zero()
635        } else {
636            (cos_theta.one() - cos_theta.square()).sqrt()
637        };
638        let k_cross_v = axis.cross_product(self);
639        let k_dot_v = self * axis.clone();
640
641        self * cos_theta + k_cross_v * &sin_theta + &axis * &k_dot_v * &(self.px.one() - cos_theta)
642    }
643
644    pub(crate) fn get_cos_theta_with(&self, rhs: &ThreeMomentum<F<T>>) -> F<T> {
645        let self_norm = self.norm();
646        let rhs_norm = rhs.norm();
647        if self_norm < self_norm.epsilon() || rhs_norm < rhs_norm.epsilon() {
648            return self_norm.one();
649        }
650
651        let dot = self * rhs.clone();
652        dot / (self.norm() * rhs.norm())
653    }
654}
655
656impl<T: Neg<Output = T> + Clone> ThreeMomentum<T> {
657    pub(crate) fn perform_pi2_rotation_x_mut(&mut self) {
658        self.pz = -self.pz.clone();
659        std::mem::swap(&mut self.pz, &mut self.py);
660    }
661
662    pub(crate) fn perform_pi2_rotation_x(&self) -> Self {
663        // println!("X rotation");
664        Self {
665            px: self.px.clone(),
666            py: -self.pz.clone(),
667            pz: self.py.clone(),
668        }
669    }
670
671    pub(crate) fn perform_pi2_rotation_y_mut(&mut self) {
672        self.px = -self.px.clone();
673        std::mem::swap(&mut self.px, &mut self.pz);
674    }
675
676    pub(crate) fn perform_pi2_rotation_y(&self) -> Self {
677        // println!("Y rotation");
678        Self {
679            px: self.pz.clone(),
680            py: self.py.clone(),
681            pz: -self.px.clone(),
682        }
683    }
684
685    pub(crate) fn perform_pi2_rotation_z_mut(&mut self) {
686        self.py = -self.py.clone();
687        std::mem::swap(&mut self.px, &mut self.py);
688    }
689
690    pub(crate) fn perform_pi2_rotation_z(&self) -> Self {
691        // println!("Z rotation");
692        Self {
693            px: -self.py.clone(),
694            py: self.px.clone(),
695            pz: self.pz.clone(),
696        }
697    }
698}
699
700impl<T> Add<ThreeMomentum<T>> for ThreeMomentum<T>
701where
702    T: Add<T, Output = T>,
703{
704    type Output = ThreeMomentum<T>;
705    fn add(self, rhs: ThreeMomentum<T>) -> Self::Output {
706        ThreeMomentum {
707            px: self.px + rhs.px,
708            py: self.py + rhs.py,
709            pz: self.pz + rhs.pz,
710        }
711    }
712}
713
714impl<T> Add<&ThreeMomentum<T>> for ThreeMomentum<T>
715where
716    T: for<'a> Add<&'a T, Output = T>,
717{
718    type Output = ThreeMomentum<T>;
719    fn add(self, rhs: &ThreeMomentum<T>) -> Self::Output {
720        ThreeMomentum {
721            px: self.px + &rhs.px,
722            py: self.py + &rhs.py,
723            pz: self.pz + &rhs.pz,
724        }
725    }
726}
727
728impl<'b, T> Add<&ThreeMomentum<T>> for &'b ThreeMomentum<T>
729where
730    &'b T: for<'a> Add<&'a T, Output = T>,
731{
732    type Output = ThreeMomentum<T>;
733    fn add(self, rhs: &ThreeMomentum<T>) -> Self::Output {
734        ThreeMomentum {
735            px: &self.px + &rhs.px,
736            py: &self.py + &rhs.py,
737            pz: &self.pz + &rhs.pz,
738        }
739    }
740}
741
742impl<T> Add<ThreeMomentum<T>> for &ThreeMomentum<T>
743where
744    T: for<'a> Add<&'a T, Output = T>,
745{
746    type Output = ThreeMomentum<T>;
747    fn add(self, rhs: ThreeMomentum<T>) -> Self::Output {
748        rhs + self
749    }
750}
751
752impl<T> AddAssign<ThreeMomentum<T>> for ThreeMomentum<T>
753where
754    T: AddAssign<T>,
755{
756    fn add_assign(&mut self, rhs: ThreeMomentum<T>) {
757        self.px += rhs.px;
758        self.py += rhs.py;
759        self.pz += rhs.pz;
760    }
761}
762
763impl<'a, T> AddAssign<&'a ThreeMomentum<T>> for ThreeMomentum<T>
764where
765    T: AddAssign<&'a T>,
766{
767    fn add_assign(&mut self, rhs: &'a ThreeMomentum<T>) {
768        self.px += &rhs.px;
769        self.py += &rhs.py;
770        self.pz += &rhs.pz;
771    }
772}
773
774impl<T> Sub<ThreeMomentum<T>> for ThreeMomentum<T>
775where
776    T: Sub<T, Output = T>,
777{
778    type Output = ThreeMomentum<T>;
779    fn sub(self, rhs: ThreeMomentum<T>) -> Self::Output {
780        ThreeMomentum {
781            px: self.px - rhs.px,
782            py: self.py - rhs.py,
783            pz: self.pz - rhs.pz,
784        }
785    }
786}
787
788impl<T> Sub<&ThreeMomentum<T>> for &ThreeMomentum<T>
789where
790    for<'a> &'a T: Sub<&'a T, Output = T>,
791{
792    type Output = ThreeMomentum<T>;
793    fn sub(self, rhs: &ThreeMomentum<T>) -> Self::Output {
794        ThreeMomentum {
795            px: &self.px - &rhs.px,
796            py: &self.py - &rhs.py,
797            pz: &self.pz - &rhs.pz,
798        }
799    }
800}
801
802impl<T> SubAssign<ThreeMomentum<T>> for ThreeMomentum<T>
803where
804    T: SubAssign<T>,
805{
806    fn sub_assign(&mut self, rhs: ThreeMomentum<T>) {
807        self.px -= rhs.px;
808        self.py -= rhs.py;
809        self.pz -= rhs.pz;
810    }
811}
812
813impl<'a, T> SubAssign<&'a ThreeMomentum<T>> for ThreeMomentum<T>
814where
815    T: SubAssign<&'a T>,
816{
817    fn sub_assign(&mut self, rhs: &'a ThreeMomentum<T>) {
818        self.px -= &rhs.px;
819        self.py -= &rhs.py;
820        self.pz -= &rhs.pz;
821    }
822}
823
824impl<T> Mul<ThreeMomentum<T>> for ThreeMomentum<T>
825where
826    T: Mul<T, Output = T> + Add<T, Output = T>,
827{
828    type Output = T;
829    fn mul(self, rhs: ThreeMomentum<T>) -> Self::Output {
830        self.px * rhs.px + self.py * rhs.py + self.pz * rhs.pz
831    }
832}
833
834impl<T> Mul<&ThreeMomentum<T>> for ThreeMomentum<T>
835where
836    T: for<'a> Mul<&'a T, Output = T> + Add<T, Output = T>,
837{
838    type Output = T;
839    fn mul(self, rhs: &ThreeMomentum<T>) -> Self::Output {
840        self.px * &rhs.px + self.py * &rhs.py + self.pz * &rhs.pz
841    }
842}
843
844impl<T> Mul<ThreeMomentum<T>> for &ThreeMomentum<T>
845where
846    T: for<'b> Mul<&'b T, Output = T> + Add<T, Output = T>,
847{
848    type Output = T;
849    fn mul(self, rhs: ThreeMomentum<T>) -> Self::Output {
850        rhs * self
851    }
852}
853
854impl<T> Mul<T> for ThreeMomentum<T>
855where
856    T: Mul<T, Output = T> + Clone,
857{
858    type Output = ThreeMomentum<T>;
859    fn mul(self, rhs: T) -> Self::Output {
860        ThreeMomentum {
861            px: self.px * rhs.clone(),
862            py: self.py * rhs.clone(),
863            pz: self.pz * rhs,
864        }
865    }
866}
867
868impl<T> Mul<&T> for ThreeMomentum<T>
869where
870    T: for<'a> Mul<&'a T, Output = T> + Clone,
871{
872    type Output = ThreeMomentum<T>;
873    fn mul(self, rhs: &T) -> Self::Output {
874        ThreeMomentum {
875            px: self.px * rhs,
876            py: self.py * rhs,
877            pz: self.pz * rhs,
878        }
879    }
880}
881
882impl<T> Mul<T> for &ThreeMomentum<T>
883where
884    T: Mul<T, Output = T> + Clone,
885{
886    type Output = ThreeMomentum<T>;
887    fn mul(self, rhs: T) -> Self::Output {
888        ThreeMomentum {
889            px: self.px.clone() * rhs.clone(),
890            py: self.py.clone() * rhs.clone(),
891            pz: self.pz.clone() * rhs,
892        }
893    }
894}
895
896impl<T> Mul<&T> for &ThreeMomentum<T>
897where
898    T: for<'b> Mul<&'b T, Output = T> + Clone,
899{
900    type Output = ThreeMomentum<T>;
901    fn mul(self, rhs: &T) -> Self::Output {
902        ThreeMomentum {
903            px: self.px.clone() * rhs,
904            py: self.py.clone() * rhs,
905            pz: self.pz.clone() * rhs,
906        }
907    }
908}
909
910impl<T> Neg for ThreeMomentum<T>
911where
912    T: Neg<Output = T>,
913{
914    type Output = ThreeMomentum<T>;
915    fn neg(self) -> Self::Output {
916        ThreeMomentum {
917            px: -self.px,
918            py: -self.py,
919            pz: -self.pz,
920        }
921    }
922}
923
924impl<T> Neg for &ThreeMomentum<T>
925where
926    T: Neg<Output = T> + Clone,
927{
928    type Output = ThreeMomentum<T>;
929    fn neg(self) -> Self::Output {
930        ThreeMomentum {
931            px: -self.px.clone(),
932            py: -self.py.clone(),
933            pz: -self.pz.clone(),
934        }
935    }
936}
937
938impl<T: Display> Display for ThreeMomentum<T> {
939    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
940        write!(f, "px: {}, py: {}, pz: {}", self.px, self.py, self.pz)
941    }
942}
943
944impl<T: LowerExp> LowerExp for ThreeMomentum<T> {
945    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
946        write!(f, "px: {:e}, py: {:e}, pz: {:e}", self.px, self.py, self.pz)
947    }
948}
949
950impl<T: Default> Default for ThreeMomentum<T> {
951    fn default() -> Self {
952        ThreeMomentum {
953            px: T::default(),
954            py: T::default(),
955            pz: T::default(),
956        }
957    }
958}
959
960impl<T> ThreeMomentum<T> {
961    pub(crate) fn norm_squared(&self) -> T
962    where
963        T: for<'a> Mul<&'a T, Output = T> + Add<T, Output = T> + Clone,
964    {
965        self.px.clone() * &self.px + self.py.clone() * &self.py + self.pz.clone() * &self.pz
966    }
967
968    pub(crate) fn on_shell_energy(&self, mass: Option<T>) -> Energy<T>
969    where
970        T: Mul<T, Output = T> + Add<T, Output = T> + Clone + std::ops::Add<Output = T> + Real,
971    {
972        let energy_squared = self.on_shell_energy_squared(mass);
973        Energy {
974            value: energy_squared.value.sqrt(),
975        }
976    }
977
978    pub(crate) fn on_shell_energy_squared(&self, mass: Option<T>) -> Energy<T>
979    where
980        T: for<'a> Mul<&'a T, Output = T>
981            + Add<T, Output = T>
982            + Clone
983            + std::ops::Add<Output = T>
984            + Display,
985    {
986        let p2 = self.norm_squared();
987        if let Some(mass) = mass {
988            // println!("mass: {}", mass);
989            Energy {
990                value: p2 + mass.clone() * &mass,
991            }
992        } else {
993            Energy { value: p2 }
994        }
995    }
996
997    pub(crate) fn norm(&self) -> T
998    where
999        T: for<'a> Mul<&'a T, Output = T> + Add<T> + Real,
1000    {
1001        self.norm_squared().sqrt()
1002    }
1003
1004    pub(crate) fn into_four_momentum_parametric(self, id: usize) -> FourMomentum<T, Atom> {
1005        let energy = Energy::new_parametric(id);
1006        FourMomentum {
1007            temporal: energy,
1008            spatial: self,
1009        }
1010    }
1011
1012    pub(crate) fn into_on_shell_four_momentum(self, mass: Option<T>) -> FourMomentum<T, T>
1013    where
1014        T: Mul<T> + Add<T> + std::ops::Add<Output = T> + Real,
1015    {
1016        FourMomentum::new_on_shell(self, mass)
1017    }
1018
1019    pub(crate) fn cast<U>(&self) -> ThreeMomentum<U>
1020    where
1021        T: Clone + Into<U>,
1022    {
1023        ThreeMomentum {
1024            px: (self.px.clone()).into(),
1025            py: (self.py.clone()).into(),
1026            pz: (self.pz.clone()).into(),
1027        }
1028    }
1029
1030    #[allow(clippy::wrong_self_convention)]
1031    pub(crate) fn into_f64(&self) -> ThreeMomentum<f64>
1032    where
1033        T: FloatLike,
1034    {
1035        ThreeMomentum {
1036            px: self.px.to_f64(),
1037            py: self.py.to_f64(),
1038            pz: self.pz.to_f64(),
1039        }
1040    }
1041}
1042
1043impl<T> From<[T; 3]> for ThreeMomentum<T> {
1044    fn from(data: [T; 3]) -> Self {
1045        let [px, py, pz] = data;
1046        ThreeMomentum { px, py, pz }
1047    }
1048}
1049
1050impl<T> From<ThreeMomentum<T>> for [T; 3] {
1051    fn from(data: ThreeMomentum<T>) -> Self {
1052        [data.px, data.py, data.pz]
1053    }
1054}
1055
1056impl<T> From<(T, T, T)> for ThreeMomentum<T> {
1057    fn from(data: (T, T, T)) -> Self {
1058        let (px, py, pz) = data;
1059        ThreeMomentum { px, py, pz }
1060    }
1061}
1062
1063impl<T> From<ThreeMomentum<T>> for (T, T, T) {
1064    fn from(data: ThreeMomentum<T>) -> Self {
1065        (data.px, data.py, data.pz)
1066    }
1067}
1068
1069#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Encode, Decode)]
1070pub struct FourMomentum<T, U = T> {
1071    pub temporal: Energy<U>,
1072    pub spatial: ThreeMomentum<T>,
1073}
1074
1075impl<T: FloatLike> Sub<&FourMomentum<F<T>>> for &FourMomentum<F<T>> {
1076    type Output = FourMomentum<F<T>>;
1077    fn sub(self, rhs: &FourMomentum<F<T>>) -> Self::Output {
1078        FourMomentum {
1079            temporal: Energy {
1080                value: &self.temporal.value - &rhs.temporal.value,
1081            },
1082            spatial: &self.spatial - &rhs.spatial,
1083        }
1084    }
1085}
1086
1087impl<T> FourMomentum<T> {
1088    pub(crate) fn map_ref<U>(&self, f: &impl Fn(&T) -> U) -> FourMomentum<U> {
1089        FourMomentum {
1090            temporal: self.temporal.map_ref(f),
1091            spatial: self.spatial.map_ref(f),
1092        }
1093    }
1094
1095    pub(crate) fn map<U>(self, f: &impl Fn(T) -> U) -> FourMomentum<U> {
1096        FourMomentum {
1097            temporal: self.temporal.map(f),
1098            spatial: self.spatial.map(f),
1099        }
1100    }
1101}
1102
1103impl<T: FloatLike> ApproxEq<FourMomentum<F<T>>, F<T>> for FourMomentum<F<T>> {
1104    fn approx_eq(&self, other: &FourMomentum<F<T>>, threshold: &F<T>) -> bool {
1105        self.temporal.approx_eq(&other.temporal, threshold)
1106            && self.spatial.approx_eq(&other.spatial, threshold)
1107    }
1108}
1109
1110impl<T: FloatLike> ApproxEq<Polarization<Complex<F<T>>>, F<T>> for FourMomentum<F<T>> {
1111    fn approx_eq(&self, other: &Polarization<Complex<F<T>>>, tolerance: &F<T>) -> bool {
1112        if other.tensor.size().unwrap() != 4 {
1113            false
1114        } else {
1115            self.into_iter()
1116                .zip(other.tensor.iter_flat())
1117                .all(|(a, (_, b))| a.approx_eq(b, tolerance))
1118        }
1119    }
1120
1121    fn approx_eq_res(
1122        &self,
1123        other: &Polarization<Complex<F<T>>>,
1124        tolerance: &F<T>,
1125    ) -> color_eyre::Result<()> {
1126        if other.tensor.size().unwrap() != 4 {
1127            Err(eyre::eyre!("Polarization tensor has wrong size."))
1128        } else {
1129            self.into_iter()
1130                .zip(other.tensor.iter_flat())
1131                .try_for_each(|(a, (i, b))| {
1132                    a.approx_eq_res(b, tolerance).wrap_err(format!(
1133                        "Polarization tensor element {} does not match. ",
1134                        i
1135                    ))
1136                })
1137        }
1138    }
1139}
1140
1141impl<T: FloatLike> FourMomentum<F<T>> {
1142    pub(crate) fn from_ff64(four_momentum: &FourMomentum<F<f64>>) -> Self {
1143        let temporal = Energy::from_ff64(four_momentum.temporal);
1144        let spatial = ThreeMomentum::from_ff64(four_momentum.spatial);
1145        FourMomentum { temporal, spatial }
1146    }
1147
1148    pub(crate) fn rescale_spatial(&self, x: &F<T>) -> Self {
1149        FourMomentum {
1150            temporal: self.temporal.clone(),
1151            spatial: ThreeMomentum {
1152                px: &self.spatial.px * x,
1153                py: &self.spatial.py * x,
1154                pz: &self.spatial.pz * x,
1155            },
1156        }
1157    }
1158
1159    pub(crate) fn set_energy_on_shell(&self, mass: Option<F<T>>) -> Self {
1160        let energy = self.spatial.on_shell_energy(mass);
1161        FourMomentum {
1162            temporal: energy,
1163            spatial: self.spatial.clone(),
1164        }
1165    }
1166
1167    pub(crate) fn rapidity(&self) -> F<T> {
1168        let pt = self.spatial.pt();
1169        self.rapidity_with_pt2(&pt.square())
1170    }
1171
1172    pub(crate) fn rapidity_with_pt2(&self, pt2: &F<T>) -> F<T> {
1173        const MAX_RAPIDITY_SENTINEL: usize = 100_000;
1174
1175        let zero = self.temporal.value.zero();
1176        let pz = self.spatial.pz.clone();
1177        let abs_pz = pz.norm();
1178        let energy = self.temporal.value.clone();
1179
1180        if *pt2 == zero.clone() && energy == abs_pz {
1181            let max_rapidity_here = abs_pz.from_usize(MAX_RAPIDITY_SENTINEL) + abs_pz;
1182            if pz >= zero {
1183                max_rapidity_here
1184            } else {
1185                -max_rapidity_here
1186            }
1187        } else {
1188            let mass2 = energy.square()
1189                - (self.spatial.px.square() + self.spatial.py.square() + self.spatial.pz.square());
1190            let effective_mass2 = if mass2 < zero { zero.clone() } else { mass2 };
1191            let e_plus_abs_pz = energy + abs_pz;
1192            let half = e_plus_abs_pz.one() / e_plus_abs_pz.from_usize(2);
1193            let mut rapidity =
1194                ((pt2.clone() + effective_mass2) / (e_plus_abs_pz.clone() * e_plus_abs_pz)).ln()
1195                    * half;
1196            if pz > zero {
1197                rapidity = -rapidity;
1198            }
1199            rapidity
1200        }
1201    }
1202}
1203
1204#[derive(
1205    Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Encode, Decode, Hash, JsonSchema,
1206)]
1207// #[trait_decode(trait= GammaLoopContext)]
1208#[serde(untagged)]
1209pub enum ExternalMomenta<T> {
1210    Dependent(Dep),
1211    Independent([T; 4]),
1212}
1213
1214impl<T: FloatLike> Rotatable for ExternalMomenta<F<T>> {
1215    fn rotate(&self, rotation: &Rotation) -> Self {
1216        match self {
1217            ExternalMomenta::Dependent(_) => ExternalMomenta::Dependent(Dep::Dep),
1218            ExternalMomenta::Independent(data) => {
1219                let fm = FourMomentum::from(data.clone());
1220                fm.rotate(rotation).into()
1221            }
1222        }
1223    }
1224}
1225
1226#[derive(
1227    Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Encode, Decode, Hash, JsonSchema,
1228)]
1229pub enum Dep {
1230    #[serde(rename = "dependent")]
1231    Dep,
1232}
1233
1234#[derive(Error, Debug)]
1235pub enum ExternalMomentaError {
1236    #[error("Dependent momenta cannot be converted to FourMomentum")]
1237    Dependent,
1238}
1239
1240impl<T> TryFrom<ExternalMomenta<T>> for FourMomentum<T> {
1241    type Error = ExternalMomentaError;
1242    fn try_from(value: ExternalMomenta<T>) -> Result<Self, Self::Error> {
1243        match value {
1244            ExternalMomenta::Dependent(_) => Err(ExternalMomentaError::Dependent),
1245            ExternalMomenta::Independent(data) => Ok(FourMomentum::from(data)),
1246        }
1247    }
1248}
1249
1250impl<T> From<FourMomentum<T>> for ExternalMomenta<T> {
1251    fn from(value: FourMomentum<T>) -> Self {
1252        ExternalMomenta::Independent(value.into())
1253    }
1254}
1255
1256impl<T> From<[T; 4]> for ExternalMomenta<T> {
1257    fn from(data: [T; 4]) -> Self {
1258        ExternalMomenta::Independent(data)
1259    }
1260}
1261
1262impl<T: RefZero, U: RefZero> RefZero for FourMomentum<T, U> {
1263    fn ref_zero(&self) -> Self {
1264        FourMomentum {
1265            temporal: self.temporal.ref_zero(),
1266            spatial: self.spatial.ref_zero(),
1267        }
1268    }
1269}
1270
1271impl<T: RefZero, U: RefZero> RefZero<FourMomentum<T, U>> for &FourMomentum<T, U> {
1272    fn ref_zero(&self) -> FourMomentum<T, U> {
1273        FourMomentum {
1274            temporal: self.temporal.ref_zero(),
1275            spatial: self.spatial.ref_zero(),
1276        }
1277    }
1278}
1279
1280#[derive(
1281    Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Encode, Decode, Eq, PartialOrd, Ord,
1282)]
1283pub struct PolDef {
1284    pub pol_type: PolType,
1285    pub eid: EdgeIndex,
1286}
1287
1288#[derive(
1289    Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Encode, Decode, Eq, PartialOrd, Ord,
1290)]
1291pub enum PolType {
1292    U,
1293    V,
1294    UBar,
1295    VBar,
1296    Scalar,
1297    Epsilon,
1298    EpsilonBar,
1299}
1300
1301impl PolType {
1302    pub(crate) fn bar(self) -> Self {
1303        match self {
1304            Self::Epsilon => Self::EpsilonBar,
1305            Self::Scalar => Self::Scalar,
1306            Self::U => Self::UBar,
1307            Self::UBar => Self::U,
1308            Self::EpsilonBar => Self::Epsilon,
1309            Self::VBar => Self::V,
1310            Self::V => Self::VBar,
1311        }
1312    }
1313}
1314
1315impl Display for PolType {
1316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1317        match self {
1318            Self::U => write!(f, "u"),
1319            Self::V => write!(f, "v"),
1320            Self::VBar => write!(f, "vbar"),
1321            Self::UBar => write!(f, "ubar"),
1322            Self::Scalar => write!(f, ""),
1323            Self::Epsilon => write!(f, "ϵ"),
1324            Self::EpsilonBar => write!(f, "ϵbar"),
1325        }
1326    }
1327}
1328
1329#[derive(Debug, PartialEq, Clone, bincode_trait_derive::Encode, bincode_trait_derive::Decode)]
1330#[trait_decode(trait = symbolica::state::HasStateMap)]
1331pub struct Polarization<T> {
1332    pub tensor: DenseTensor<T, IndexLess<LibraryRep>>,
1333    pol_type: PolType,
1334}
1335
1336impl<T: FloatLike> ApproxEq<Polarization<Complex<F<T>>>, F<T>> for Polarization<Complex<F<T>>> {
1337    fn approx_eq(&self, other: &Polarization<Complex<F<T>>>, tolerance: &F<T>) -> bool {
1338        self.pol_type == other.pol_type
1339            && self
1340                .tensor
1341                .flat_iter()
1342                .zip(other.tensor.flat_iter())
1343                .all(|((_, a), (_, b))| a.approx_eq(b, tolerance))
1344    }
1345
1346    fn approx_eq_res(
1347        &self,
1348        other: &Polarization<Complex<F<T>>>,
1349        tolerance: &F<T>,
1350    ) -> color_eyre::Result<()> {
1351        if self.pol_type != other.pol_type {
1352            Err(eyre::eyre!(
1353                "Polarization types do not match. self: {}, other: {}",
1354                self,
1355                other
1356            ))
1357        } else {
1358            self.tensor
1359                .flat_iter()
1360                .zip(other.tensor.flat_iter())
1361                .try_for_each(|((i, a), (_, b))| {
1362                    a.approx_eq_res(b, tolerance).wrap_err(format!(
1363                        "Polarization tensor element {} does not match. self: {}, other: {}",
1364                        i, self, other
1365                    ))
1366                })
1367        }
1368    }
1369}
1370
1371impl<T: for<'a> std::ops::AddAssign<&'a T>> AddAssign<Polarization<T>> for Polarization<T> {
1372    fn add_assign(&mut self, rhs: Polarization<T>) {
1373        self.tensor += rhs.tensor;
1374    }
1375}
1376
1377impl<T: for<'a> SubAssign<&'a T>> SubAssign<Polarization<T>> for Polarization<T> {
1378    fn sub_assign(&mut self, rhs: Polarization<T>) {
1379        self.tensor -= rhs.tensor;
1380    }
1381}
1382
1383impl<T: FloatLike> Display for Polarization<F<T>> {
1384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1385        write!(f, "Pol {}: {}", self.pol_type, self.tensor)
1386    }
1387}
1388
1389impl<T: FloatLike> Display for Polarization<Complex<F<T>>> {
1390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1391        write!(f, "Pol {}: {}", self.pol_type, self.tensor)
1392    }
1393}
1394
1395impl<T: FloatLike> LowerExp for Polarization<Complex<F<T>>> {
1396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1397        write!(f, "Pol {}: {:+e}", self.pol_type, self.tensor)
1398    }
1399}
1400
1401impl<T> Polarization<T> {
1402    pub(crate) fn map<U>(&self, f: impl Fn(&T) -> U) -> Polarization<U> {
1403        Polarization {
1404            tensor: self.tensor.map_data_ref(f),
1405            pol_type: self.pol_type,
1406        }
1407    }
1408}
1409
1410impl<T: Clone> Polarization<T> {
1411    pub(crate) fn is_scalar(&self) -> bool {
1412        self.pol_type == PolType::Scalar
1413    }
1414    pub(crate) fn scalar(value: T) -> Self {
1415        let structure = IndexLess::new(vec![]);
1416        Polarization {
1417            tensor: DenseTensor {
1418                data: vec![value],
1419                structure,
1420            },
1421            pol_type: PolType::Scalar,
1422        }
1423    }
1424
1425    pub(crate) fn lorentz(value: [T; 4]) -> Self {
1426        let structure = IndexLess::new(vec![Minkowski {}.new_rep(4).cast()]);
1427        Polarization {
1428            tensor: DenseTensor {
1429                data: value.to_vec(),
1430                structure,
1431            },
1432            pol_type: PolType::Epsilon,
1433        }
1434    }
1435
1436    pub(crate) fn bispinor_u(value: [T; 4]) -> Self {
1437        let structure = IndexLess::new(vec![GR.bis.new_rep(4)]);
1438
1439        Polarization {
1440            tensor: DenseTensor {
1441                data: value.to_vec(),
1442                structure,
1443            },
1444            pol_type: PolType::U,
1445        }
1446    }
1447
1448    pub(crate) fn bispinor_v(value: [T; 4]) -> Self {
1449        let structure = IndexLess::new(vec![GR.bis.new_rep(4)]);
1450
1451        Polarization {
1452            tensor: DenseTensor {
1453                data: value.to_vec(),
1454                structure,
1455            },
1456            pol_type: PolType::V,
1457        }
1458    }
1459
1460    pub(crate) fn shadow(&self) -> DenseTensor<Atom, IndexLess<LibraryRep>> {
1461        self.tensor
1462            .structure
1463            .clone()
1464            .to_dense_labeled(|_, i| FlatCoefficent::<NoArgs> {
1465                index: i,
1466                name: Some(symbol!(self.pol_type.to_string())),
1467                args: None,
1468            })
1469            .unwrap()
1470    }
1471}
1472
1473impl<T> Index<usize> for Polarization<T> {
1474    type Output = T;
1475    fn index(&self, index: usize) -> &Self::Output {
1476        &self.tensor[index.into()]
1477    }
1478}
1479
1480impl<T> RefZero for Polarization<T>
1481where
1482    T: RefZero + Clone,
1483{
1484    fn ref_zero(&self) -> Self {
1485        Polarization {
1486            tensor: <DenseTensor<_, _> as RefZero>::ref_zero(&self.tensor),
1487            pol_type: self.pol_type,
1488        }
1489    }
1490}
1491
1492impl<'a, T, U> Mul<&'a U> for Polarization<T>
1493where
1494    T: Clone + MulAssign<&'a U>,
1495{
1496    type Output = Polarization<T>;
1497    fn mul(mut self, rhs: &'a U) -> Self::Output {
1498        for i in self.tensor.data.iter_mut() {
1499            *i *= rhs;
1500        }
1501        self
1502    }
1503}
1504
1505impl<'a, T> MulAssign<&'a T> for Polarization<T>
1506where
1507    T: Clone + MulAssign<&'a T>,
1508{
1509    fn mul_assign(&mut self, rhs: &'a T) {
1510        for i in self.tensor.data.iter_mut() {
1511            *i *= rhs;
1512        }
1513    }
1514}
1515
1516impl<T, U, Out> FallibleAdd<Polarization<U>> for Polarization<T>
1517where
1518    T: FallibleAdd<U, Output = Out>,
1519{
1520    type Output = Polarization<Out>;
1521    fn add_fallible(&self, rhs: &Polarization<U>) -> Option<Self::Output> {
1522        Some(Polarization {
1523            tensor: self.tensor.add_fallible(&rhs.tensor)?,
1524            pol_type: self.pol_type,
1525        })
1526    }
1527}
1528
1529impl<T> Polarization<T> {
1530    pub(crate) fn cast<U>(&self) -> Polarization<U>
1531    where
1532        T: Clone,
1533        U: Clone + From<T>,
1534    {
1535        Polarization {
1536            tensor: self.tensor.cast(),
1537            pol_type: self.pol_type,
1538        }
1539    }
1540}
1541
1542impl<T> Polarization<Complex<T>> {
1543    pub(crate) fn complex_cast<U>(&self) -> Polarization<Complex<U>>
1544    where
1545        T: Clone,
1546        U: Clone + From<T>,
1547    {
1548        Polarization {
1549            tensor: self
1550                .tensor
1551                .map_data_ref(|d| d.map_ref(|r| r.clone().into())),
1552            pol_type: self.pol_type,
1553        }
1554    }
1555}
1556
1557impl<T: FloatLike> Polarization<Complex<F<T>>> {
1558    pub(crate) fn higher(&self) -> Polarization<Complex<F<T::Higher>>>
1559    where
1560        T::Higher: FloatLike,
1561    {
1562        Polarization {
1563            tensor: self.tensor.map_data_ref(|t| t.map_ref(|r| r.higher())),
1564            pol_type: self.pol_type,
1565        }
1566    }
1567
1568    pub(crate) fn lower(&self) -> Polarization<Complex<F<T::Lower>>>
1569    where
1570        T::Lower: FloatLike,
1571    {
1572        Polarization {
1573            tensor: self.tensor.map_data_ref(|t| t.map_ref(|r| r.lower())),
1574            pol_type: self.pol_type,
1575        }
1576    }
1577}
1578
1579impl<T: FloatLike, U: FloatLike> From<FourMomentum<T, U>> for FourMomentum<F<T>, F<U>> {
1580    fn from(value: FourMomentum<T, U>) -> Self {
1581        FourMomentum {
1582            temporal: value.temporal.into(),
1583            spatial: value.spatial.into(),
1584        }
1585    }
1586}
1587
1588impl<T: FloatLike, U: FloatLike> FourMomentum<F<T>, F<U>> {
1589    pub(crate) fn higher(&self) -> FourMomentum<F<T::Higher>, F<U::Higher>>
1590    where
1591        T::Higher: FloatLike,
1592        U::Higher: FloatLike,
1593    {
1594        FourMomentum {
1595            temporal: self.temporal.higher(),
1596            spatial: self.spatial.higher(),
1597        }
1598    }
1599
1600    pub(crate) fn lower(&self) -> FourMomentum<F<T::Lower>, F<U::Lower>>
1601    where
1602        T::Lower: FloatLike,
1603        U::Lower: FloatLike,
1604    {
1605        FourMomentum {
1606            temporal: self.temporal.lower(),
1607            spatial: self.spatial.lower(),
1608        }
1609    }
1610}
1611
1612impl<T, U> FourMomentum<T, U> {
1613    pub(crate) fn new(energy: Energy<U>, three_momentum: ThreeMomentum<T>) -> Self {
1614        FourMomentum {
1615            temporal: energy,
1616            spatial: three_momentum,
1617        }
1618    }
1619}
1620
1621impl<T: Real> RefDefault for FourMomentum<T, T> {
1622    fn default(&self) -> Self {
1623        let zero = self.temporal.value.zero();
1624        FourMomentum {
1625            temporal: Energy::new(zero.clone()),
1626            spatial: ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone()),
1627        }
1628    }
1629}
1630
1631impl<T> Mul<T> for &FourMomentum<T, T>
1632where
1633    T: Mul<T, Output = T> + Clone,
1634{
1635    type Output = FourMomentum<T, T>;
1636    fn mul(self, rhs: T) -> Self::Output {
1637        FourMomentum {
1638            temporal: Energy {
1639                value: self.temporal.value.clone() * rhs.clone(),
1640            },
1641            spatial: self.spatial.clone() * rhs,
1642        }
1643    }
1644}
1645
1646impl<T> Mul<&T> for &FourMomentum<T, T>
1647where
1648    T: for<'b> Mul<&'b T, Output = T> + Clone,
1649{
1650    type Output = FourMomentum<T, T>;
1651    fn mul(self, rhs: &T) -> Self::Output {
1652        FourMomentum {
1653            temporal: Energy {
1654                value: self.temporal.value.clone() * rhs,
1655            },
1656            spatial: self.spatial.clone() * rhs,
1657        }
1658    }
1659}
1660
1661impl<T> FourMomentum<T, T> {
1662    pub(crate) fn zero(&self) -> Self
1663    where
1664        T: Real,
1665    {
1666        let zero = self.temporal.value.zero();
1667        FourMomentum {
1668            temporal: Energy::new(zero.clone()),
1669            spatial: ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone()),
1670        }
1671    }
1672
1673    pub(crate) fn square(&self) -> T
1674    where
1675        T: for<'a> Mul<&'a T, Output = T> + Add<T, Output = T> + Clone + Sub<T, Output = T>,
1676    {
1677        let temporal = self.temporal.value.clone();
1678        let spatial = self.spatial.norm_squared();
1679        temporal * &self.temporal.value - spatial
1680    }
1681
1682    pub(crate) fn norm(&self) -> T
1683    where
1684        T: Real,
1685    {
1686        self.square().sqrt()
1687    }
1688
1689    pub(crate) fn from_args(energy: T, px: T, py: T, pz: T) -> Self {
1690        let energy = Energy::new(energy);
1691        let three_momentum = ThreeMomentum { px, py, pz };
1692        FourMomentum {
1693            temporal: energy,
1694            spatial: three_momentum,
1695        }
1696    }
1697    pub(crate) fn new_on_shell(three_momentum: ThreeMomentum<T>, mass: Option<T>) -> Self
1698    where
1699        T: Mul<T> + Add<T> + std::ops::Add<Output = T> + Real,
1700    {
1701        let energy = three_momentum.on_shell_energy(mass);
1702        // println!("{}", energy);
1703        FourMomentum {
1704            temporal: energy,
1705            spatial: three_momentum,
1706        }
1707    }
1708
1709    pub(crate) fn into_dense(self, index: AbstractIndex) -> DenseTensor<T, OrderedStructure>
1710    where
1711        T: Clone,
1712    {
1713        let structure =
1714            PermutedStructure::from_iter([LibraryRep::new_slot(Minkowski {}.into(), 4, index)])
1715                .structure;
1716        DenseTensor::from_data(
1717            vec![
1718                self.temporal.value,
1719                self.spatial.px,
1720                self.spatial.py,
1721                self.spatial.pz,
1722            ],
1723            structure,
1724        )
1725        .unwrap()
1726    }
1727
1728    pub(crate) fn into_dense_named(
1729        self,
1730        index: AbstractIndex,
1731        name: Symbol,
1732        num: usize,
1733    ) -> DenseTensor<T, NamedStructure<Symbol, usize>>
1734    where
1735        T: Clone,
1736    {
1737        let structure = PermutedStructure::<OrderedStructure>::from_iter([LibraryRep::new_slot(
1738            Minkowski {}.into(),
1739            4,
1740            index,
1741        )])
1742        .structure
1743        .to_named(name, Some(num));
1744        DenseTensor::from_data(
1745            vec![
1746                self.temporal.value,
1747                self.spatial.px,
1748                self.spatial.py,
1749                self.spatial.pz,
1750            ],
1751            structure,
1752        )
1753        .unwrap()
1754    }
1755
1756    pub(crate) fn cast<U>(&self) -> FourMomentum<U, U>
1757    where
1758        T: Clone + Into<U>,
1759    {
1760        FourMomentum {
1761            temporal: Energy::new((self.temporal.value.clone()).into()),
1762            spatial: ThreeMomentum::cast(&self.spatial),
1763        }
1764    }
1765
1766    pub(crate) fn boost(&self, boost_vector: &ThreeMomentum<T>) -> FourMomentum<T>
1767    where
1768        T: Real + SingleFloat + PartialOrd,
1769    {
1770        let b2 = boost_vector.norm_squared();
1771        let one = b2.one();
1772        let zero = one.zero();
1773        let gamma = (one.clone() - &b2).sqrt().inv();
1774
1775        let bp = self.spatial.clone() * boost_vector;
1776        let gamma2 = if b2 > zero {
1777            (gamma.clone() - &one) / b2
1778        } else {
1779            zero
1780        };
1781        let factor = gamma2 * &bp + gamma.clone() * &self.temporal.value;
1782
1783        FourMomentum::from_args(
1784            (bp + &self.temporal.value) * &gamma,
1785            self.spatial.px.mul_add(&factor, &boost_vector.px),
1786            self.spatial.py.mul_add(&factor, &boost_vector.py),
1787            self.spatial.pz.mul_add(&factor, &boost_vector.pz),
1788        )
1789    }
1790}
1791
1792impl<T: FloatLike> FourMomentum<F<T>, F<T>> {
1793    pub(crate) fn dot(&self, p2: &FourMomentum<F<T>>) -> F<T> {
1794        &self.temporal.value * &p2.temporal.value
1795            - (&self.spatial.px * &p2.spatial.px
1796                + &self.spatial.py * &p2.spatial.py
1797                + &self.spatial.pz * &p2.spatial.pz)
1798    }
1799    /// Compute the phi-angle separation with p2.
1800    pub(crate) fn getdelphi(&self, p2: &FourMomentum<F<T>>) -> F<T>
1801    where
1802        T: Real,
1803    {
1804        self.spatial.getdelphi(&p2.spatial)
1805    }
1806
1807    /// Compute the deltaR separation with momentum p2.
1808    #[inline]
1809    pub(crate) fn delta_r(&self, p2: &FourMomentum<F<T>>) -> F<T>
1810    where
1811        T: Real,
1812    {
1813        self.spatial.delta_r(&p2.spatial)
1814    }
1815
1816    pub(crate) fn pt(&self) -> F<T>
1817    where
1818        T: Real,
1819    {
1820        self.spatial.pt()
1821    }
1822
1823    pub(crate) fn to_f64(&self) -> FourMomentum<F<f64>, F<f64>> {
1824        FourMomentum {
1825            temporal: Energy {
1826                value: F(self.temporal.value.to_f64()),
1827            },
1828            spatial: self.spatial.to_f64().cast(),
1829        }
1830    }
1831
1832    pub(crate) fn pol_one(&self) -> [F<T>; 4]
1833    where
1834        T: FloatLike,
1835    {
1836        // definition from helas_ref A.2
1837
1838        // debug!("pol_one in: {}", self);
1839
1840        let pt = self.pt();
1841        let p = self.spatial.norm();
1842
1843        let (e1, e2, e3) = if pt.is_zero() {
1844            (pt.one(), pt.zero(), pt.zero())
1845        } else {
1846            (
1847                &self.spatial.px * &self.spatial.pz / (&pt * &p),
1848                &self.spatial.py * &self.spatial.pz / (&pt * &p),
1849                -(&pt / &p),
1850            )
1851        };
1852
1853        // debug!(
1854        //     " (pt.zero(), e1, e2, e3) {} {} {} {}",
1855        //     pt.zero(),
1856        //     e1,
1857        //     e2,
1858        //     e3
1859        // );
1860        [pt.zero(), e1, e2, e3]
1861
1862        // debug!("pol :{pol}");
1863    }
1864
1865    pub(crate) fn pol_two(&self) -> [F<T>; 4]
1866    where
1867        T: FloatLike,
1868    {
1869        // definition from helas_ref A.2
1870        let pt = self.pt();
1871        let (e1, e2, e3) = if pt.is_zero() {
1872            if self.spatial.pz.positive() {
1873                (pt.zero(), pt.one(), pt.zero())
1874            } else {
1875                (pt.zero(), -pt.one(), pt.zero())
1876            }
1877        } else {
1878            (-(&self.spatial.py / &pt), &self.spatial.px / &pt, pt.zero())
1879        };
1880        [pt.zero(), e1, e2, e3]
1881    }
1882
1883    pub(crate) fn pol_three(&self) -> Polarization<F<T>>
1884    where
1885        T: FloatLike,
1886    {
1887        // definition from helas_ref A.2
1888        let m = self.norm();
1889        let p = self.spatial.norm();
1890        let emp = &self.temporal.value / (&m * &p);
1891        let e0 = p / &m;
1892        let e1 = &self.spatial.px * &emp;
1893        let e2 = &self.spatial.py * &emp;
1894        let e3 = &self.spatial.pz * &emp;
1895
1896        Polarization::lorentz([e0, e1, e2, e3])
1897    }
1898
1899    pub(crate) fn eps_pol<Hel: Into<Helicity>>(&self, lambda: Hel) -> Polarization<Complex<F<T>>> {
1900        match lambda.into() {
1901            Helicity::Signed(lambda) => {
1902                if lambda.is_zero() {
1903                    self.pol_three().cast()
1904                } else {
1905                    let one = self.temporal.value.one();
1906                    let sqrt_2_inv: F<T> = (&one + &one).sqrt().inv();
1907
1908                    let [eone0, eone1, eone2, eone3] = self.pol_one();
1909
1910                    let [etwo0, etwo1, etwo2, etwo3] = self.pol_two();
1911
1912                    let components = [
1913                        Complex {
1914                            re: -lambda * eone0 * &sqrt_2_inv,
1915                            im: -etwo0 * &sqrt_2_inv, //using opposite convention with respect to helas to align with madgraph
1916                        },
1917                        Complex {
1918                            re: -lambda * eone1 * &sqrt_2_inv,
1919                            im: -etwo1 * &sqrt_2_inv,
1920                        },
1921                        Complex {
1922                            re: -lambda * eone2 * &sqrt_2_inv,
1923                            im: -etwo2 * &sqrt_2_inv,
1924                        },
1925                        Complex {
1926                            re: -lambda * eone3 * &sqrt_2_inv,
1927                            im: -etwo3 * &sqrt_2_inv,
1928                        },
1929                    ];
1930
1931                    Polarization::lorentz(components)
1932                }
1933            }
1934            _ => {
1935                let zero = self.temporal.value.zero();
1936                let cmpz = Complex::new_re(zero);
1937                Polarization::lorentz([cmpz.clone(), cmpz.clone(), cmpz.clone(), cmpz.clone()])
1938            }
1939        }
1940    }
1941
1942    pub(crate) fn omega(&self, lambda: Sign) -> Complex<F<T>> {
1943        match lambda {
1944            Sign::Positive => (&self.temporal.value + self.spatial.norm()).complex_sqrt(),
1945            Sign::Negative => (&self.temporal.value - self.spatial.norm()).complex_sqrt(),
1946        }
1947    }
1948
1949    pub(crate) fn u(&self, lambda: Sign) -> Polarization<Complex<F<T>>> {
1950        let xi = self.xi(lambda);
1951        Polarization::bispinor_u([
1952            self.omega(-lambda) * &xi[0],
1953            self.omega(-lambda) * &xi[1],
1954            self.omega(lambda) * &xi[0],
1955            self.omega(lambda) * &xi[1],
1956        ])
1957    }
1958
1959    pub(crate) fn v(&self, lambda: Sign) -> Polarization<Complex<F<T>>> {
1960        let xi = self.xi(-lambda);
1961        Polarization::bispinor_v([
1962            (-lambda) * self.omega(lambda) * &xi[0],
1963            (-lambda) * self.omega(lambda) * &xi[1],
1964            lambda * self.omega(-lambda) * &xi[0],
1965            lambda * self.omega(-lambda) * &xi[1],
1966        ])
1967    }
1968
1969    pub(crate) fn xi(&self, lambda: Sign) -> [Complex<F<T>>; 2] {
1970        if (self.spatial.pz < self.spatial.pz.zero()
1971            && self.spatial.py.is_zero()
1972            && self.spatial.px.is_zero())
1973            || (self.spatial.pz == -self.spatial.norm())
1974        {
1975            let zero: Complex<F<T>> = self.temporal.value.zero().into();
1976            let one = zero.one();
1977            // We are defining using madgraph conventions not helas, taking py =0 and the limit px =0 from below
1978            match lambda {
1979                Sign::Positive => [zero, -one],
1980                Sign::Negative => [one, zero],
1981            }
1982        } else {
1983            let prefactor: F<T> = ((F::from_f64(2.)
1984                * self.spatial.norm()
1985                * (self.spatial.norm() + &self.spatial.pz))
1986                .sqrt())
1987            .inv();
1988            let mut xi: [Complex<F<T>>; 2] = [
1989                Complex::new_re(&prefactor * (self.spatial.norm() + &self.spatial.pz)),
1990                Complex::new(self.spatial.px.clone(), self.spatial.py.clone()) * &prefactor,
1991            ]; //plus
1992
1993            if matches!(lambda, Sign::Negative) {
1994                xi.swap(0, 1);
1995                xi[0].re = -xi[0].re.clone();
1996            }
1997            xi
1998        }
1999    }
2000}
2001
2002impl<T: FloatLike> Polarization<Complex<F<T>>> {
2003    pub(crate) fn bar(&self) -> Self {
2004        let mut tensor = self.tensor.map_data_ref(Complex::conj);
2005
2006        if matches!(
2007            self.pol_type,
2008            PolType::U | PolType::V | PolType::UBar | PolType::VBar
2009        ) {
2010            tensor.data.swap(0, 2);
2011            tensor.data.swap(1, 3);
2012        }
2013        Polarization {
2014            tensor,
2015            pol_type: self.pol_type.bar(),
2016        }
2017    }
2018}
2019
2020impl<T> IntoIterator for Polarization<T> {
2021    type IntoIter = std::vec::IntoIter<Self::Item>;
2022    type Item = T;
2023
2024    fn into_iter(self) -> Self::IntoIter {
2025        self.tensor.data.into_iter()
2026    }
2027}
2028
2029impl<'a, T> IntoIterator for &'a Polarization<T> {
2030    type IntoIter = std::slice::Iter<'a, T>;
2031    type Item = &'a T;
2032
2033    fn into_iter(self) -> Self::IntoIter {
2034        self.tensor.data.iter()
2035    }
2036}
2037
2038#[derive(
2039    Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash, bincode::Encode, bincode::Decode,
2040)]
2041pub enum Helicity {
2042    Signed(SignOrZero),
2043    Summed,
2044    SummedAveraged,
2045}
2046
2047impl Display for Helicity {
2048    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2049        match self {
2050            Helicity::Signed(s) => write!(f, "{}", s),
2051            Helicity::Summed => write!(f, "summed"),
2052            Helicity::SummedAveraged => write!(f, "summed_averaged"),
2053        }
2054    }
2055}
2056
2057impl Serialize for Helicity {
2058    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2059    where
2060        S: serde::Serializer,
2061    {
2062        match self {
2063            Helicity::Signed(SignOrZero::Plus) => serializer.serialize_i8(1),
2064            Helicity::Signed(SignOrZero::Minus) => serializer.serialize_i8(-1),
2065            Helicity::Signed(SignOrZero::Zero) => serializer.serialize_i8(0),
2066            Helicity::Summed => serializer.serialize_str("summed"),
2067            Helicity::SummedAveraged => serializer.serialize_str("summed_averaged"),
2068        }
2069    }
2070}
2071
2072#[derive(Deserialize)]
2073#[serde(untagged)]
2074enum HelicitySerde {
2075    Numeric(i8),
2076    Text(String),
2077}
2078
2079impl<'de> Deserialize<'de> for Helicity {
2080    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2081    where
2082        D: serde::Deserializer<'de>,
2083    {
2084        let value = HelicitySerde::deserialize(deserializer)?;
2085        match value {
2086            HelicitySerde::Numeric(value) => {
2087                Helicity::try_from(value).map_err(serde::de::Error::custom)
2088            }
2089            HelicitySerde::Text(value) => {
2090                Helicity::from_str(&value).map_err(serde::de::Error::custom)
2091            }
2092        }
2093    }
2094}
2095
2096impl JsonSchema for Helicity {
2097    fn schema_name() -> std::borrow::Cow<'static, str> {
2098        "Helicity".into()
2099    }
2100
2101    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
2102        schemars::json_schema!({
2103            "description": "Helicity input. Use -1, 0, or 1 for fixed helicities; or use a string for summed modes.",
2104            "oneOf": [
2105                {
2106                    "type": "integer",
2107                    "enum": [-1, 0, 1]
2108                },
2109                {
2110                    "type": "string",
2111                    "enum": [
2112                        "+",
2113                        "-",
2114                        "0",
2115                        "plus",
2116                        "minus",
2117                        "zero",
2118                        "summed",
2119                        "summed_averaged",
2120                        "summed-averaged"
2121                    ]
2122                }
2123            ]
2124        })
2125    }
2126}
2127
2128impl TryFrom<i8> for Helicity {
2129    type Error = SignError;
2130    fn try_from(value: i8) -> Result<Self, Self::Error> {
2131        Ok(Helicity::Signed(SignOrZero::try_from(value)?))
2132    }
2133}
2134
2135impl From<Sign> for Helicity {
2136    fn from(sign: Sign) -> Self {
2137        Self::Signed(sign.into())
2138    }
2139}
2140
2141impl From<SignOrZero> for Helicity {
2142    fn from(sign: SignOrZero) -> Self {
2143        Self::Signed(sign)
2144    }
2145}
2146
2147impl TryFrom<Helicity> for SignOrZero {
2148    type Error = SignError;
2149
2150    fn try_from(value: Helicity) -> Result<Self, Self::Error> {
2151        match value {
2152            Helicity::Signed(sign) => Ok(sign),
2153            Helicity::Summed | Helicity::SummedAveraged => Err(SignError::InvalidValue),
2154        }
2155    }
2156}
2157
2158impl TryFrom<Helicity> for Sign {
2159    type Error = SignError;
2160
2161    fn try_from(value: Helicity) -> Result<Self, Self::Error> {
2162        match value {
2163            Helicity::Signed(sign) => sign.try_into(),
2164            Helicity::Summed | Helicity::SummedAveraged => Err(SignError::InvalidValue),
2165        }
2166    }
2167}
2168
2169#[allow(non_upper_case_globals)]
2170impl Helicity {
2171    pub const ZERO: Self = Self::Signed(SignOrZero::Zero);
2172    pub const PLUS: Self = Self::Signed(SignOrZero::Plus);
2173    pub const MINUS: Self = Self::Signed(SignOrZero::Minus);
2174    pub const Zero: Self = Self::ZERO;
2175    pub const Plus: Self = Self::PLUS;
2176    pub const Minus: Self = Self::MINUS;
2177}
2178
2179impl FromStr for Helicity {
2180    type Err = String;
2181
2182    fn from_str(value: &str) -> Result<Self, Self::Err> {
2183        match value.trim().to_ascii_lowercase().as_str() {
2184            "+" | "plus" => Ok(Self::PLUS),
2185            "-" | "minus" => Ok(Self::MINUS),
2186            "0" | "zero" => Ok(Self::ZERO),
2187            "summed" => Ok(Self::Summed),
2188            "summed_averaged" | "summed-averaged" => Ok(Self::SummedAveraged),
2189            _ => Err(format!(
2190                "Invalid helicity '{value}'. Expected one of -1, 0, 1, plus, minus, zero, summed, summed_averaged"
2191            )),
2192        }
2193    }
2194}
2195
2196#[derive(
2197    Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, PartialOrd, Ord, Hash,
2198)]
2199pub struct Signature(pub Vec<SignOrZero>);
2200
2201impl Display for Signature {
2202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2203        for sign in &self.0 {
2204            write!(f, "{}", sign)?;
2205        }
2206        Ok(())
2207    }
2208}
2209
2210impl FromIterator<SignOrZero> for Signature {
2211    fn from_iter<I: IntoIterator<Item = SignOrZero>>(iter: I) -> Self {
2212        Signature(iter.into_iter().collect())
2213    }
2214}
2215
2216impl FromIterator<i8> for Signature {
2217    fn from_iter<I: IntoIterator<Item = i8>>(iter: I) -> Self {
2218        Signature(
2219            iter.into_iter()
2220                .map(|x| match x {
2221                    0 => SignOrZero::Zero,
2222                    1 => SignOrZero::Plus,
2223                    -1 => SignOrZero::Minus,
2224                    _ => panic!("Invalid value for Signature"),
2225                })
2226                .collect(),
2227        )
2228    }
2229}
2230
2231impl FromIterator<isize> for Signature {
2232    fn from_iter<I: IntoIterator<Item = isize>>(iter: I) -> Self {
2233        Signature(
2234            iter.into_iter()
2235                .map(|x| match x {
2236                    0 => SignOrZero::Zero,
2237                    1 => SignOrZero::Plus,
2238                    -1 => SignOrZero::Minus,
2239                    _ => panic!("Invalid value for Signature"),
2240                })
2241                .collect(),
2242        )
2243    }
2244}
2245
2246impl From<Vec<i8>> for Signature {
2247    fn from(value: Vec<i8>) -> Self {
2248        Signature::from_iter(value)
2249    }
2250}
2251
2252impl IntoIterator for Signature {
2253    type Item = SignOrZero;
2254    type IntoIter = std::vec::IntoIter<Self::Item>;
2255
2256    fn into_iter(self) -> Self::IntoIter {
2257        self.0.into_iter()
2258    }
2259}
2260
2261impl<'a> IntoIterator for &'a Signature {
2262    type Item = SignOrZero;
2263    type IntoIter = std::iter::Copied<std::slice::Iter<'a, Self::Item>>;
2264
2265    fn into_iter(self) -> Self::IntoIter {
2266        self.0.iter().copied()
2267    }
2268}
2269
2270impl Index<usize> for Signature {
2271    type Output = SignOrZero;
2272    fn index(&self, index: usize) -> &Self::Output {
2273        &self.0[index]
2274    }
2275}
2276
2277impl Signature {
2278    pub(crate) fn validate_basis<T>(&self, basis: &[T]) -> bool {
2279        self.len() == basis.len()
2280    }
2281
2282    pub(crate) fn sum(&mut self, other: &Self) {
2283        for (i, sign) in other.iter().enumerate() {
2284            match (self[i], sign) {
2285                (SignOrZero::Zero, SignOrZero::Zero) => self.0[i] = SignOrZero::Zero,
2286                (SignOrZero::Zero, SignOrZero::Plus) => self.0[i] = SignOrZero::Plus,
2287                (SignOrZero::Zero, SignOrZero::Minus) => self.0[i] = SignOrZero::Minus,
2288                (SignOrZero::Plus, SignOrZero::Zero) => self.0[i] = SignOrZero::Plus,
2289                (SignOrZero::Plus, SignOrZero::Plus) => panic!("cannot add two positive signs"),
2290                (SignOrZero::Plus, SignOrZero::Minus) => self.0[i] = SignOrZero::Zero,
2291                (SignOrZero::Minus, SignOrZero::Zero) => self.0[i] = SignOrZero::Minus,
2292                (SignOrZero::Minus, SignOrZero::Plus) => self.0[i] = SignOrZero::Zero,
2293                (SignOrZero::Minus, SignOrZero::Minus) => panic!("cannot add two negative signs"),
2294            }
2295        }
2296    }
2297
2298    pub(crate) fn panic_validate_basis<T>(&self, basis: &[T]) {
2299        if !self.validate_basis(basis) {
2300            panic!(
2301                "Invalid basis for Signature, expected length {}, got length {}",
2302                self.len(),
2303                basis.len()
2304            );
2305        }
2306    }
2307
2308    pub(crate) fn to_momtrop_format(&self) -> Vec<isize> {
2309        self.0
2310            .iter()
2311            .map(|x| match x {
2312                SignOrZero::Zero => 0,
2313                SignOrZero::Plus => 1,
2314                SignOrZero::Minus => -1,
2315            })
2316            .collect()
2317    }
2318    pub(crate) fn len(&self) -> usize {
2319        self.0.len()
2320    }
2321
2322    pub(crate) fn iter(&'_ self) -> std::slice::Iter<'_, SignOrZero> {
2323        self.0.iter()
2324    }
2325
2326    pub(crate) fn is_empty(&self) -> bool {
2327        self.0.is_empty()
2328    }
2329    pub(crate) fn apply<T>(&self, basis: &[T]) -> T
2330    where
2331        T: RefZero + Clone + Neg<Output = T> + AddAssign<T>,
2332    {
2333        // self.panic_validate_basis(basis);
2334        let mut result = basis[0].ref_zero();
2335        for (&sign, t) in self.0.iter().zip(basis.iter().cloned()) {
2336            result += sign * t;
2337        }
2338        result
2339    }
2340
2341    pub(crate) fn apply_iter<I, T>(&self, basis: I) -> Option<T>
2342    where
2343        I: IntoIterator,
2344        I::Item: RefZero<T>,
2345        T: Clone + SubAssign<I::Item> + AddAssign<I::Item>,
2346    {
2347        let mut basis_iter = basis.into_iter();
2348        let mut signature_iter = self.into_iter();
2349
2350        while let (Some(sign), Some(item)) = (signature_iter.next(), basis_iter.next()) {
2351            if sign.is_sign() {
2352                // Initialize the result based on the first non-zero sign
2353                let mut result = item.ref_zero();
2354                match sign {
2355                    SignOrZero::Zero => {
2356                        panic!("unreachable");
2357                        // return None;
2358                    }
2359                    SignOrZero::Plus => {
2360                        result += item;
2361                    }
2362                    SignOrZero::Minus => {
2363                        result -= item;
2364                    }
2365                }
2366
2367                // Continue processing the rest of the iterator
2368                while let (Some(sign), Some(item)) = (signature_iter.next(), basis_iter.next()) {
2369                    match sign {
2370                        SignOrZero::Zero => {}
2371                        SignOrZero::Plus => {
2372                            result += item;
2373                        }
2374                        SignOrZero::Minus => {
2375                            result -= item;
2376                        }
2377                    }
2378                }
2379
2380                return Some(result);
2381            }
2382        }
2383
2384        // Return None if no non-zero sign was found
2385        None
2386    }
2387
2388    pub(crate) fn label_with(&self, label: &str) -> String {
2389        let mut result = String::new();
2390        let mut first = true;
2391        for (i, sign) in self.0.iter().enumerate() {
2392            if !first {
2393                result.push_str(&sign.to_string());
2394            } else {
2395                first = false;
2396            }
2397            if sign.is_sign() {
2398                result.push_str(&format!("{}_{}", label, i));
2399            }
2400        }
2401        result
2402    }
2403}
2404
2405#[test]
2406fn test_signature() {
2407    let sig = Signature(vec![SignOrZero::Plus, SignOrZero::Minus]);
2408    let basis: Vec<i32> = vec![1, 2];
2409    assert_eq!(sig.apply(&basis), 1 - 2);
2410    assert_eq!(sig.apply_iter(basis.iter()), Some(-1));
2411
2412    let basis: [FourMomentum<i32>; 4] = [
2413        FourMomentum::from_args(1, 1, 0, 0),
2414        FourMomentum::from_args(1, 0, 1, 0),
2415        FourMomentum::from_args(1, 0, 0, 1),
2416        FourMomentum::from_args(1, 1, 1, 1),
2417    ];
2418
2419    let sig = Signature(vec![
2420        SignOrZero::Plus,
2421        SignOrZero::Minus,
2422        SignOrZero::Zero,
2423        SignOrZero::Plus,
2424    ]);
2425
2426    assert_eq!(sig.apply(&basis), FourMomentum::from_args(1, 2, 0, 1));
2427    let sig = Signature(vec![
2428        SignOrZero::Zero,
2429        SignOrZero::Zero,
2430        SignOrZero::Zero,
2431        SignOrZero::Zero,
2432    ]);
2433    assert_eq!(sig.apply_iter(basis.iter()), None);
2434    let sig = Signature(vec![
2435        SignOrZero::Zero,
2436        SignOrZero::Zero,
2437        SignOrZero::Zero,
2438        SignOrZero::Minus,
2439    ]);
2440    assert_eq!(sig.apply_iter(basis.iter()), Some(-basis[3]));
2441}
2442
2443// impl Serialize for Helicity {
2444//     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2445//     where
2446//         S: Serializer,
2447//     {
2448//         match *self {
2449//             Helicity::Sign(ref sign) => sign.serialize(serializer),
2450//             Helicity::ZERO => serializer.serialize_i32(0),
2451//         }
2452//     }
2453// }
2454
2455// impl<'de> Deserialize<'de> for Helicity {
2456//     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2457//     where
2458//         D: Deserializer<'de>,
2459//     {
2460//         let value = i32::deserialize(deserializer)?;
2461//         match value {
2462//             1 => Ok(Helicity::Sign(Sign::Positive)),
2463//             -1 => Ok(Helicity::Sign(Sign::Negative)),
2464//             0 => Ok(Helicity::ZERO),
2465//             _ => Err(de::Error::custom(
2466//                 "Expected 1 for Positive, -1 for Negative, or 0 for Zero",
2467//             )),
2468//         }
2469//     }
2470// }
2471
2472impl<T> IntoIterator for FourMomentum<T> {
2473    type Item = T;
2474    type IntoIter = std::array::IntoIter<T, 4>;
2475
2476    fn into_iter(self) -> Self::IntoIter {
2477        let [e, px, py, pz] = [
2478            self.temporal.value,
2479            self.spatial.px,
2480            self.spatial.py,
2481            self.spatial.pz,
2482        ];
2483        [e, px, py, pz].into_iter()
2484    }
2485}
2486
2487impl<'a, T> IntoIterator for &'a FourMomentum<T> {
2488    type Item = &'a T;
2489    type IntoIter = std::array::IntoIter<&'a T, 4>;
2490
2491    fn into_iter(self) -> Self::IntoIter {
2492        let [e, px, py, pz] = [
2493            &self.temporal.value,
2494            &self.spatial.px,
2495            &self.spatial.py,
2496            &self.spatial.pz,
2497        ];
2498        [e, px, py, pz].into_iter()
2499    }
2500}
2501
2502impl<T> From<[T; 4]> for FourMomentum<T, T> {
2503    fn from(data: [T; 4]) -> Self {
2504        let [t, px, py, pz] = data;
2505        FourMomentum {
2506            temporal: Energy::new(t),
2507            spatial: ThreeMomentum { px, py, pz },
2508        }
2509    }
2510}
2511
2512impl<T> From<FourMomentum<T, T>> for [T; 4] {
2513    fn from(data: FourMomentum<T, T>) -> Self {
2514        [
2515            data.temporal.value,
2516            data.spatial.px,
2517            data.spatial.py,
2518            data.spatial.pz,
2519        ]
2520    }
2521}
2522
2523impl<T> From<(T, T, T, T)> for FourMomentum<T, T> {
2524    fn from(data: (T, T, T, T)) -> Self {
2525        let (t, px, py, pz) = data;
2526        FourMomentum {
2527            temporal: Energy::new(t),
2528            spatial: ThreeMomentum { px, py, pz },
2529        }
2530    }
2531}
2532
2533impl<T> From<FourMomentum<T, T>> for (T, T, T, T) {
2534    fn from(data: FourMomentum<T, T>) -> Self {
2535        (
2536            data.temporal.value,
2537            data.spatial.px,
2538            data.spatial.py,
2539            data.spatial.pz,
2540        )
2541    }
2542}
2543
2544impl<T> FourMomentum<T, Atom> {
2545    pub(crate) fn into_dense_param(
2546        self,
2547        index: AbstractIndex,
2548    ) -> DenseTensor<MultivariatePolynomial<RationalField, T>, OrderedStructure>
2549    where
2550        T: Clone + Into<Coefficient> + Exponent,
2551    {
2552        let structure =
2553            PermutedStructure::from_iter([LibraryRep::new_slot(Minkowski {}.into(), 4, index)])
2554                .structure;
2555        let energy = self
2556            .temporal
2557            .value
2558            .to_polynomial(&RationalField::new(IntegerRing {}), None);
2559
2560        let px: MultivariatePolynomial<RationalField, _> =
2561            Atom::num(self.spatial.px).to_polynomial(&RationalField::new(IntegerRing {}), None);
2562        let py =
2563            Atom::num(self.spatial.py).to_polynomial(&RationalField::new(IntegerRing {}), None);
2564        let pz =
2565            Atom::num(self.spatial.pz).to_polynomial(&RationalField::new(IntegerRing {}), None);
2566
2567        DenseTensor::from_data(vec![energy, px, py, pz], structure).unwrap()
2568    }
2569}
2570
2571impl<T, U> Add<FourMomentum<T, U>> for FourMomentum<T, U>
2572where
2573    T: Add<T, Output = T>,
2574    U: Add<U, Output = U>,
2575{
2576    type Output = FourMomentum<T, U>;
2577    fn add(self, rhs: FourMomentum<T, U>) -> Self::Output {
2578        FourMomentum {
2579            temporal: self.temporal + rhs.temporal,
2580            spatial: self.spatial + rhs.spatial,
2581        }
2582    }
2583}
2584
2585impl<T, U> AddAssign<FourMomentum<T, U>> for FourMomentum<T, U>
2586where
2587    T: AddAssign<T>,
2588    U: AddAssign<U>,
2589{
2590    fn add_assign(&mut self, rhs: FourMomentum<T, U>) {
2591        self.temporal += rhs.temporal;
2592        self.spatial += rhs.spatial;
2593    }
2594}
2595
2596impl<'a, T, U> AddAssign<&'a FourMomentum<T, U>> for FourMomentum<T, U>
2597where
2598    T: AddAssign<&'a T>,
2599    U: AddAssign<&'a U>,
2600{
2601    fn add_assign(&mut self, rhs: &'a FourMomentum<T, U>) {
2602        self.temporal += &rhs.temporal;
2603        self.spatial += &rhs.spatial;
2604    }
2605}
2606
2607impl<T, U> Sub<FourMomentum<T, U>> for FourMomentum<T, U>
2608where
2609    T: Sub<T, Output = T>,
2610    U: Sub<U, Output = U>,
2611{
2612    type Output = FourMomentum<T, U>;
2613    fn sub(self, rhs: FourMomentum<T, U>) -> Self::Output {
2614        FourMomentum {
2615            temporal: self.temporal - rhs.temporal,
2616            spatial: self.spatial - rhs.spatial,
2617        }
2618    }
2619}
2620
2621impl<T, U> Neg for FourMomentum<T, U>
2622where
2623    T: Neg<Output = T>,
2624    U: Neg<Output = U>,
2625{
2626    type Output = FourMomentum<T, U>;
2627    fn neg(self) -> Self::Output {
2628        FourMomentum {
2629            temporal: -self.temporal,
2630            spatial: -self.spatial,
2631        }
2632    }
2633}
2634
2635impl<T, U> Neg for &FourMomentum<T, U>
2636where
2637    T: Neg<Output = T> + Clone,
2638    U: Neg<Output = U> + Clone,
2639{
2640    type Output = FourMomentum<T, U>;
2641    fn neg(self) -> Self::Output {
2642        FourMomentum {
2643            temporal: -self.temporal.clone(),
2644            spatial: -self.spatial.clone(),
2645        }
2646    }
2647}
2648
2649impl<T, U> SubAssign<FourMomentum<T, U>> for FourMomentum<T, U>
2650where
2651    T: SubAssign<T>,
2652    U: SubAssign<U>,
2653{
2654    fn sub_assign(&mut self, rhs: FourMomentum<T, U>) {
2655        self.temporal -= rhs.temporal;
2656        self.spatial -= rhs.spatial;
2657    }
2658}
2659
2660impl<'a, T, U> SubAssign<&'a FourMomentum<T, U>> for FourMomentum<T, U>
2661where
2662    T: SubAssign<&'a T>,
2663    U: SubAssign<&'a U>,
2664{
2665    fn sub_assign(&mut self, rhs: &'a FourMomentum<T, U>) {
2666        self.temporal -= &rhs.temporal;
2667        self.spatial -= &rhs.spatial;
2668    }
2669}
2670
2671impl<T: Display, U: Display> Display for FourMomentum<T, U> {
2672    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2673        write!(f, "{}, {}", self.temporal, self.spatial)
2674    }
2675}
2676
2677impl<T: LowerExp, U: LowerExp> LowerExp for FourMomentum<T, U> {
2678    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2679        write!(f, "{:e}, {:e}", self.temporal, self.spatial)
2680    }
2681}
2682
2683impl<T: FloatLike> From<Vector<F<T>, 3>> for ThreeMomentum<F<T>> {
2684    fn from(value: Vector<F<T>, 3>) -> Self {
2685        ThreeMomentum::new(value[0].clone(), value[1].clone(), value[2].clone())
2686    }
2687}
2688
2689pub enum LorentzTransformation<T> {
2690    Boost(ThreeMomentum<T>),
2691    Rotation(Rotation),
2692    General {
2693        boost: ThreeMomentum<T>,
2694        rotation: Rotation,
2695    },
2696}
2697
2698pub trait LorentzTransformable<T>: Rotatable {
2699    fn lorentz_transform(&self, transformation: &LorentzTransformation<T>) -> Self;
2700}
2701
2702// pub struct IrriducibleLorentzRep {
2703//     m: usize,
2704//     n: usize,
2705// }
2706
2707// impl IrriducibleLorentzRep {
2708//     pub(crate) fn scalar() -> Self {
2709//         Self { m: 0, n: 0 }
2710//     }
2711
2712//     pub(crate) fn vector() -> Self {
2713//         Self { m: 1, n: 1 }
2714//     }
2715
2716//     pub(crate) fn tensor() -> Self {
2717//         Self { m: 2, n: 2 }
2718//     }
2719
2720//     pub(crate) fn left_weyl() -> Self {
2721//         Self { m: 1, n: 0 }
2722//     }
2723
2724//     pub(crate) fn right_weyl() -> Self {
2725//         Self { m: 0, n: 1 }
2726//     }
2727// }
2728
2729pub enum LorentzRep {
2730    // Irriducible(IrriducibleLorentzRep),
2731    // DirectSum(Vec<LorentzRep>),
2732    Scalar,
2733    Vector,
2734    Bispinor,
2735}
2736
2737#[derive(Clone, Copy, Debug, Encode, Decode)]
2738pub enum RotationMethod {
2739    EulerAngles(f64, f64, f64),
2740    Pi2X,
2741    Pi2Y,
2742    Pi2Z,
2743    Identity,
2744}
2745
2746impl Display for RotationMethod {
2747    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2748        match self {
2749            RotationMethod::EulerAngles(alpha, beta, gamma) => {
2750                write!(f, "EulerAngles({}, {}, {})", alpha, beta, gamma)
2751            }
2752            RotationMethod::Pi2X => write!(f, "Pi/2 rotation around x-axis"),
2753            RotationMethod::Pi2Y => write!(f, "Pi/2 rotation around y-axis"),
2754            RotationMethod::Pi2Z => write!(f, "Pi/2 rotation around z-axis"),
2755            RotationMethod::Identity => write!(f, "Identity rotation"),
2756        }
2757    }
2758}
2759
2760impl From<RotationMethod> for Rotation {
2761    fn from(method: RotationMethod) -> Self {
2762        Rotation::new(method)
2763    }
2764}
2765
2766#[derive(Clone, Encode, Decode)]
2767#[trait_decode(trait = GammaLoopContext)]
2768pub struct Rotation {
2769    pub method: RotationMethod,
2770    pub lorentz_rotation: EvalTensor<ExpressionEvaluator<Complex<F<f64>>>, OrderedStructure>,
2771    pub bispinor_rotation: EvalTensor<ExpressionEvaluator<Complex<F<f64>>>, OrderedStructure>,
2772    // phantom: PhantomData<T>,
2773}
2774
2775impl Rotation {
2776    pub(crate) fn is_identity(&self) -> bool {
2777        matches!(self.method, RotationMethod::Identity)
2778    }
2779
2780    pub(crate) fn inverse_rotate_three<T: FloatLike>(
2781        &self,
2782        momentum: &ThreeMomentum<F<T>>,
2783    ) -> ThreeMomentum<F<T>> {
2784        match self.method {
2785            RotationMethod::Identity => momentum.clone(),
2786            RotationMethod::Pi2X => ThreeMomentum {
2787                px: momentum.px.clone(),
2788                py: momentum.pz.clone(),
2789                pz: -momentum.py.clone(),
2790            },
2791            RotationMethod::Pi2Y => ThreeMomentum {
2792                px: -momentum.pz.clone(),
2793                py: momentum.py.clone(),
2794                pz: momentum.px.clone(),
2795            },
2796            RotationMethod::Pi2Z => ThreeMomentum {
2797                px: momentum.py.clone(),
2798                py: -momentum.px.clone(),
2799                pz: momentum.pz.clone(),
2800            },
2801            RotationMethod::EulerAngles(alpha, beta, gamma) => {
2802                // Rotation settings are persisted as f64, so reconstructing them at the
2803                // active numeric precision is an explicit f64 boundary.
2804                let alpha = F::<T>::from_f64(alpha);
2805                let beta = F::<T>::from_f64(beta);
2806                let gamma = F::<T>::from_f64(gamma);
2807                let sin_alpha = alpha.sin();
2808                let cos_alpha = alpha.cos();
2809                let sin_beta = beta.sin();
2810                let cos_beta = beta.cos();
2811                let sin_gamma = gamma.sin();
2812                let cos_gamma = gamma.cos();
2813
2814                let px = momentum.px.clone();
2815                let py = momentum.py.clone();
2816                let pz = momentum.pz.clone();
2817
2818                ThreeMomentum {
2819                    px: cos_gamma.clone() * &cos_beta * &px + sin_gamma.clone() * &cos_beta * &py
2820                        - sin_beta.clone() * &pz,
2821                    py: (-(cos_alpha.clone()) * &sin_gamma
2822                        + sin_alpha.clone() * &sin_beta * &cos_gamma)
2823                        * &px
2824                        + (cos_alpha.clone() * &cos_gamma
2825                            + sin_alpha.clone() * &sin_beta * &sin_gamma)
2826                            * &py
2827                        + cos_beta.clone() * &sin_alpha * &pz,
2828                    pz: (sin_alpha.clone() * &sin_gamma
2829                        + cos_alpha.clone() * &sin_beta * &cos_gamma)
2830                        * &px
2831                        + (-sin_alpha.clone() * &cos_gamma
2832                            + cos_alpha.clone() * &sin_beta * &sin_gamma)
2833                            * &py
2834                        + cos_alpha * &cos_beta * &pz,
2835                }
2836            }
2837        }
2838    }
2839
2840    pub(crate) fn inverse_rotate_four<T: FloatLike>(
2841        &self,
2842        momentum: &FourMomentum<F<T>>,
2843    ) -> FourMomentum<F<T>> {
2844        FourMomentum {
2845            temporal: momentum.temporal.clone(),
2846            spatial: self.inverse_rotate_three(&momentum.spatial),
2847        }
2848    }
2849
2850    pub(crate) fn setting(&self) -> RotationSetting {
2851        match self.method {
2852            RotationMethod::EulerAngles(alpha, beta, gamma) => {
2853                RotationSetting::EulerAngles { alpha, beta, gamma }
2854            }
2855            RotationMethod::Pi2X => RotationSetting::Pi2X {},
2856            RotationMethod::Pi2Y => RotationSetting::Pi2Y {},
2857            RotationMethod::Pi2Z => RotationSetting::Pi2Z {},
2858            RotationMethod::Identity => RotationSetting::None {},
2859        }
2860    }
2861    pub(crate) fn new(method: RotationMethod) -> Self {
2862        let mu = Minkowski::slot(4, 1);
2863
2864        let al = Minkowski::slot(4, 3);
2865        let mud = mu.dual();
2866
2867        let shadow: NamedStructure<String, ()> =
2868            PermutedStructure::<OrderedStructure>::from_iter([mu])
2869                .structure
2870                .to_named("eps".to_string(), None);
2871        let shadow_t: MixedTensor<_, OrderedStructure> =
2872            ParamOrConcrete::param(shadow.to_shell().expanded_shadow().unwrap().into())
2873                .cast_structure();
2874
2875        let rotation: MixedTensor<f64, _> = method
2876            .lorentz_tensor(mud, al)
2877            .try_into_dense()
2878            .unwrap()
2879            .into();
2880
2881        let fn_map = FunctionMap::new();
2882
2883        let lorentz_eval: EvalTensor<ExpressionEvaluator<SymComplex<Rational>>, OrderedStructure> =
2884            shadow_t
2885                .contract(&rotation)
2886                .unwrap()
2887                .try_into_parametric()
2888                .unwrap()
2889                .to_evaluation_tree(
2890                    &fn_map,
2891                    &shadow_t.try_into_parametric().unwrap().tensor.data(),
2892                )
2893                .unwrap()
2894                .linearize(
2895                    &OptimizationSettings::new()
2896                        .cpe_iterations(Some(1))
2897                        .verbose(false),
2898                );
2899
2900        let i = GR.bis.new_slot(4, 1);
2901
2902        let j = GR.bis.new_slot(4, 3);
2903
2904        let shadow: NamedStructure<String, ()> =
2905            PermutedStructure::from_iter([i.cast::<LibraryRep>()])
2906                .structure
2907                .to_named("u".to_string(), None);
2908        let shadow_t: MixedTensor<_, OrderedStructure> =
2909            ParamOrConcrete::param(shadow.to_shell().expanded_shadow().unwrap().into())
2910                .cast_structure();
2911
2912        let rotation: MixedTensor<f64, _> =
2913            ParamOrConcrete::Concrete(RealOrComplexTensor::Complex(method.bispinor_tensor(i, j)));
2914
2915        let res = shadow_t
2916            .contract(&rotation)
2917            .unwrap()
2918            .try_into_parametric()
2919            .unwrap();
2920
2921        let fn_map = FunctionMap::new();
2922        let mut params = shadow_t.try_into_parametric().unwrap().tensor.data();
2923        params.push(Atom::i());
2924
2925        let spinor_eval: EvalTensor<ExpressionEvaluator<SymComplex<Rational>>, OrderedStructure> =
2926            res.to_evaluation_tree(&fn_map, &params).unwrap().linearize(
2927                &OptimizationSettings::new()
2928                    .cpe_iterations(Some(1))
2929                    .verbose(false),
2930            );
2931
2932        Self {
2933            method,
2934            lorentz_rotation: lorentz_eval.map_coeff(&|f| {
2935                Complex::new(F::from_f64(f.re.to_f64()), F::from_f64(f.im.to_f64()))
2936            }),
2937            bispinor_rotation: spinor_eval.map_coeff(&|f| {
2938                Complex::new(F::from_f64(f.re.to_f64()), F::from_f64(f.im.to_f64()))
2939            }),
2940        }
2941    }
2942}
2943
2944impl RotationMethod {
2945    pub(crate) fn generator(
2946        &self,
2947        i: AbstractIndex,
2948        j: AbstractIndex,
2949    ) -> DataTensor<f64, OrderedStructure> {
2950        let structure = PermutedStructure::from_iter([
2951            LibraryRep::new_slot(Minkowski {}.into(), 4, i),
2952            LibraryRep::new_slot(Minkowski {}.into(), 4, j),
2953        ])
2954        .structure;
2955        let zero = 0.;
2956        match self {
2957            RotationMethod::Identity => {
2958                let omega = SparseTensor::empty(structure, zero);
2959                omega.into()
2960            }
2961            RotationMethod::Pi2X => {
2962                let mut omega = SparseTensor::empty(structure, zero);
2963                omega.set(&[2, 3], -zero.PIHALF()).unwrap();
2964                omega.set(&[3, 2], zero.PIHALF()).unwrap();
2965                omega.into()
2966            }
2967            RotationMethod::Pi2Y => {
2968                let mut omega = SparseTensor::empty(structure, zero);
2969                omega.set(&[1, 3], zero.PIHALF()).unwrap();
2970                omega.set(&[3, 1], -zero.PIHALF()).unwrap();
2971                omega.into()
2972            }
2973            RotationMethod::Pi2Z => {
2974                let mut omega = SparseTensor::empty(structure, zero);
2975                omega.set(&[1, 2], -zero.PIHALF()).unwrap();
2976                omega.set(&[2, 1], zero.PIHALF()).unwrap();
2977                omega.into()
2978            }
2979            RotationMethod::EulerAngles(alpha, beta, gamma) => DenseTensor::from_data(
2980                vec![
2981                    // row 0
2982                    zero, zero, zero, zero, // row 1
2983                    zero, zero, -gamma, *beta, // row 2
2984                    zero, *gamma, zero, -alpha, // row 3
2985                    zero, -beta, *alpha, zero,
2986                ],
2987                structure,
2988            )
2989            .unwrap()
2990            .into(),
2991        }
2992    }
2993
2994    pub(crate) fn lorentz_tensor(
2995        &self,
2996        i: Slot<Minkowski>,
2997        j: Slot<Minkowski>,
2998    ) -> DataTensor<f64, OrderedStructure> {
2999        let structure = PermutedStructure::from_iter([i.cast::<LibraryRep>(), j.cast()]).structure;
3000
3001        match self {
3002            RotationMethod::Identity => {
3003                let rot = DenseTensor::from_data(
3004                    vec![
3005                        1., 0., 0., 0., // row 1
3006                        0., -1., 0., 0., // row 2
3007                        0., 0., -1., 0., // row 3
3008                        0., 0., 0., -1.,
3009                    ],
3010                    structure,
3011                )
3012                .unwrap();
3013                rot.into()
3014            }
3015            RotationMethod::Pi2X => {
3016                let rot = DenseTensor::from_data(
3017                    vec![
3018                        1., 0., 0., 0., // row 1
3019                        0., -1., 0., 0., // row 2
3020                        0., 0., 0., -1., // row 3
3021                        0., 0., 1., 0.,
3022                    ],
3023                    structure,
3024                )
3025                .unwrap();
3026                rot.into()
3027            }
3028            RotationMethod::Pi2Y => {
3029                let rot = DenseTensor::from_data(
3030                    vec![
3031                        1., 0., 0., 0., // row 1
3032                        0., 0., 0., 1., // row 2
3033                        0., 0., -1., 0., // row 3
3034                        0., -1., 0., 0.,
3035                    ],
3036                    structure,
3037                )
3038                .unwrap();
3039                rot.into()
3040            }
3041            RotationMethod::Pi2Z => {
3042                let rot = DenseTensor::from_data(
3043                    vec![
3044                        1., 0., 0., 0., // row 1
3045                        0., 0., -1., 0., // row 2
3046                        0., 1., 0., 0., // row 3
3047                        0., 0., 0., -1.,
3048                    ],
3049                    structure,
3050                )
3051                .unwrap();
3052                rot.into()
3053            }
3054            RotationMethod::EulerAngles(alpha, beta, gamma) => DenseTensor::from_data(
3055                vec![
3056                    // row 0
3057                    1.,
3058                    0.,
3059                    0.,
3060                    0.,
3061                    // row 1
3062                    0.,
3063                    -gamma.cos() * beta.cos(),
3064                    alpha.sin() * beta.sin() * gamma.cos() - alpha.cos() * gamma.sin(),
3065                    alpha.sin() * gamma.sin() + alpha.cos() * beta.sin() * gamma.cos(),
3066                    // row 2
3067                    0.,
3068                    gamma.sin() * beta.cos(),
3069                    -alpha.cos() * gamma.cos() - alpha.sin() * beta.sin() * gamma.sin(),
3070                    -alpha.sin() * gamma.cos() + alpha.cos() * beta.sin() * gamma.sin(),
3071                    // row 3
3072                    0.,
3073                    -beta.sin(),
3074                    alpha.sin() * beta.cos(),
3075                    -alpha.cos() * beta.cos(),
3076                ],
3077                structure,
3078            )
3079            .unwrap()
3080            .into(),
3081            // Rotation::EulerAngles(alpha, beta, gamma) => DenseTensor::from_data(
3082            //     vec![
3083            //         // row 0
3084            //         zero.one(),
3085            //         zero.clone(),
3086            //         zero.clone(),
3087            //         zero.clone(),
3088            //         // row 1
3089            //         zero.clone(),
3090            //         alpha.cos() * beta.cos(),
3091            //         alpha.sin() * beta.sin() * gamma.cos() - alpha.cos() * gamma.sin(),
3092            //         alpha.sin() * gamma.sin() + alpha.cos() * beta.sin() * gamma.cos(),
3093            //         // row 2
3094            //         zero.clone(),
3095            //         gamma.sin() * beta.cos(),
3096            //         alpha.cos() * gamma.cos() + alpha.sin() * beta.sin() * gamma.sin(),
3097            //         -alpha.sin() * gamma.cos() + alpha.cos() * beta.sin() * gamma.sin(),
3098            //         // row 3
3099            //         zero.clone(),
3100            //         -beta.sin(),
3101            //         alpha.sin() * beta.cos(),
3102            //         alpha.cos() * beta.cos(),
3103            //     ],
3104            //     structure,
3105            // )
3106            // .unwrap()
3107            // .into(),
3108        }
3109    }
3110
3111    pub(crate) fn bispinor_tensor(
3112        &self,
3113        i: Slot<LibraryRep>,
3114        j: Slot<LibraryRep>,
3115    ) -> DataTensor<Complex<f64>, OrderedStructure> {
3116        let structure = PermutedStructure::from_iter([i.cast::<LibraryRep>(), j.cast()]).structure;
3117        let zero = 0.; // F::new_zero();
3118        let zeroc = Complex::new_re(zero);
3119
3120        match self {
3121            RotationMethod::Identity => {
3122                let mut rot = SparseTensor::empty(structure, zeroc);
3123                rot.set(&[0, 0], zeroc.one()).unwrap();
3124                rot.set(&[1, 1], zeroc.one()).unwrap();
3125                rot.set(&[2, 2], zeroc.one()).unwrap();
3126                rot.set(&[3, 3], zeroc.one()).unwrap();
3127                rot.into()
3128            }
3129            RotationMethod::Pi2X => {
3130                let mut rot = SparseTensor::empty(structure, zeroc);
3131                let sqrt2_half = zero.SQRT_2_HALF();
3132                let sqrt2_halfim = Complex::new_im(sqrt2_half);
3133                let sqrt2_halfre = Complex::new_re(sqrt2_half);
3134
3135                rot.set(&[0, 0], sqrt2_halfim).unwrap();
3136                rot.set(&[0, 1], sqrt2_halfim).unwrap();
3137                rot.set(&[1, 0], sqrt2_halfim).unwrap();
3138                rot.set(&[1, 1], sqrt2_halfre).unwrap();
3139                rot.set(&[2, 2], sqrt2_halfim).unwrap();
3140                rot.set(&[2, 3], sqrt2_halfim).unwrap();
3141                rot.set(&[3, 2], sqrt2_halfim).unwrap();
3142                rot.set(&[3, 3], sqrt2_halfre).unwrap();
3143                rot.into()
3144            }
3145            RotationMethod::Pi2Y => {
3146                let mut rot = SparseTensor::empty(structure, zeroc);
3147                let sqrt2_half = zero.SQRT_2_HALF();
3148                let sqrt2_halfim = Complex::new_im(sqrt2_half);
3149                let sqrt2_halfre = Complex::new_re(sqrt2_half);
3150                let nsqrt2_half = -sqrt2_halfre;
3151
3152                rot.set(&[0, 0], sqrt2_halfim).unwrap();
3153                rot.set(&[0, 1], nsqrt2_half).unwrap();
3154                rot.set(&[1, 0], nsqrt2_half).unwrap();
3155                rot.set(&[1, 1], sqrt2_halfre).unwrap();
3156                rot.set(&[2, 2], sqrt2_halfim).unwrap();
3157                rot.set(&[2, 3], nsqrt2_half).unwrap();
3158                rot.set(&[3, 2], nsqrt2_half).unwrap();
3159                rot.set(&[3, 3], sqrt2_halfre).unwrap();
3160                rot.into()
3161            }
3162            RotationMethod::Pi2Z => {
3163                let mut rot = SparseTensor::empty(structure, zeroc);
3164                let sqrt2_half = zero.SQRT_2_HALF();
3165                let sqrt2_halfc = Complex::new(sqrt2_half, sqrt2_half);
3166                let sqrt2_halfcc = sqrt2_halfc.conj();
3167
3168                rot.set(&[0, 0], sqrt2_halfc).unwrap();
3169                rot.set(&[1, 1], sqrt2_halfcc).unwrap();
3170                rot.set(&[2, 2], sqrt2_halfc).unwrap();
3171                rot.set(&[3, 3], sqrt2_halfcc).unwrap();
3172                rot.into()
3173            }
3174            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3175                let norm = alpha.square() + beta.square() + gamma.square();
3176
3177                if alpha.is_zero() && beta.is_zero() {
3178                    let normhalf = &norm / 2.; //F::from_f64(2.);
3179                    let cos_phihalf = normhalf.cos();
3180                    let sin_phihalf = normhalf.sin();
3181
3182                    let e = Complex::new(cos_phihalf, sin_phihalf);
3183                    let econj = e.conj();
3184                    return DenseTensor::from_data(
3185                        vec![
3186                            // row 0
3187                            e, zeroc, zeroc, zeroc, // row 1
3188                            zeroc, econj, zeroc, zeroc, // row 2
3189                            zeroc, zeroc, e, zeroc, // row 3
3190                            zeroc, zeroc, zeroc, econj,
3191                        ],
3192                        structure,
3193                    )
3194                    .unwrap()
3195                    .into();
3196                }
3197
3198                let complex_phi = Complex::new(*alpha, *beta);
3199
3200                let normhalf = &norm / 2.; //F::from_f64(2.);
3201                let cos_phihalf = normhalf.cos();
3202                let sin_phihalf = normhalf.sin();
3203
3204                let a_00 = Complex::new(gamma / norm * cos_phihalf, sin_phihalf);
3205                let a_01 =
3206                    Complex::new_im((norm - gamma.square() / norm) * sin_phihalf) / complex_phi;
3207                let a_10 = complex_phi * Complex::new_im(sin_phihalf / norm);
3208                let a_11 = Complex::new(cos_phihalf, -gamma / norm * sin_phihalf);
3209
3210                DenseTensor::from_data(
3211                    vec![
3212                        // row 0
3213                        a_00, a_01, zeroc, zeroc, // row 1
3214                        a_10, a_11, zeroc, zeroc, // row 2
3215                        zeroc, zeroc, a_00, a_01, // row 3
3216                        zeroc, zeroc, a_10, a_11,
3217                    ],
3218                    structure,
3219                )
3220                .unwrap()
3221                .into()
3222            }
3223        }
3224    }
3225}
3226
3227pub trait Rotatable {
3228    fn rotate(&self, rotation: &Rotation) -> Self;
3229}
3230
3231impl<T: FloatLike> Rotatable for ThreeMomentum<F<T>> {
3232    fn rotate(&self, rotation: &Rotation) -> Self {
3233        match rotation.method {
3234            RotationMethod::Identity => self.clone(),
3235            RotationMethod::Pi2X => ThreeMomentum::perform_pi2_rotation_x(self),
3236            RotationMethod::Pi2Y => ThreeMomentum::perform_pi2_rotation_y(self),
3237            RotationMethod::Pi2Z => ThreeMomentum::perform_pi2_rotation_z(self),
3238            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3239                let mut result = self.clone();
3240                result.rotate_mut(&F::from_f64(alpha), &F::from_f64(beta), &F::from_f64(gamma));
3241                result
3242            }
3243        }
3244    }
3245}
3246
3247impl<T: FloatLike> Rotatable for ThreeMomentum<HyperDual<F<T>>> {
3248    fn rotate(&self, rotation: &Rotation) -> Self {
3249        match rotation.method {
3250            RotationMethod::Identity => self.clone(),
3251            RotationMethod::Pi2X => ThreeMomentum::perform_pi2_rotation_x(self),
3252            RotationMethod::Pi2Y => ThreeMomentum::perform_pi2_rotation_y(self),
3253            RotationMethod::Pi2Z => ThreeMomentum::perform_pi2_rotation_z(self),
3254            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3255                let mut result = self.clone();
3256                result.rotate_mut(
3257                    &new_constant(&self.px, &F::from_f64(alpha)),
3258                    &new_constant(&self.py, &F::from_f64(beta)),
3259                    &new_constant(&self.pz, &F::from_f64(gamma)),
3260                );
3261                result
3262            }
3263        }
3264    }
3265}
3266
3267impl<T: FloatLike> ThreeMomentum<HyperDual<F<T>>> {
3268    fn rotate_mut(
3269        &mut self,
3270        alpha: &HyperDual<F<T>>,
3271        beta: &HyperDual<F<T>>,
3272        gamma: &HyperDual<F<T>>,
3273    ) {
3274        let sin_alpha = alpha.sin();
3275        let cos_alpha = alpha.cos();
3276        let sin_beta = beta.sin();
3277        let cos_beta = beta.cos();
3278        let sin_gamma = gamma.sin();
3279        let cos_gamma = gamma.cos();
3280
3281        let px = self.px.clone();
3282        let py = self.py.clone();
3283        let pz = self.pz.clone();
3284
3285        self.px = cos_gamma.clone() * &cos_beta * &px
3286            + (-(cos_alpha.clone()) * &sin_gamma + sin_alpha.clone() * &sin_beta * &cos_gamma)
3287                * &py
3288            + (sin_alpha.clone() * &sin_gamma + cos_alpha.clone() * &sin_beta * &cos_gamma) * &pz;
3289
3290        self.py = sin_gamma.clone() * &cos_beta * &px
3291            + (cos_alpha.clone() * &cos_gamma + sin_alpha.clone() * &sin_beta * &sin_gamma) * &py
3292            + (-sin_alpha.clone() * &cos_gamma + cos_alpha.clone() * &sin_beta * &sin_gamma) * &pz;
3293
3294        self.pz =
3295            -sin_beta * &px + cos_beta.clone() * &sin_alpha * &py + cos_alpha * &cos_beta * &pz;
3296    }
3297}
3298
3299impl<T: FloatLike> Rotatable for FourMomentum<F<T>> {
3300    fn rotate(&self, rotation: &Rotation) -> Self {
3301        Self {
3302            temporal: self.temporal.clone(),
3303            spatial: self.spatial.rotate(rotation),
3304        }
3305    }
3306}
3307
3308impl<T: FloatLike + EvaluationDomain> Rotatable for Polarization<Complex<F<T>>> {
3309    fn rotate(&self, rotation: &Rotation) -> Self {
3310        let rotated = match self.pol_type {
3311            PolType::Epsilon | PolType::EpsilonBar => rotation
3312                .lorentz_rotation
3313                .clone()
3314                .map_coeff(&|t| t.map(|f| F::from_ff64(f)))
3315                .evaluate(&self.tensor.data)
3316                .try_into_dense()
3317                .unwrap()
3318                .cast_structure(),
3319            PolType::U | PolType::UBar | PolType::V | PolType::VBar | PolType::Scalar => {
3320                self.tensor.clone()
3321            }
3322        };
3323
3324        Polarization {
3325            tensor: rotated,
3326            pol_type: self.pol_type,
3327        }
3328    }
3329}
3330
3331#[cfg(test)]
3332mod tests {
3333
3334    use core::f64;
3335
3336    use eyre::Context;
3337    use schemars::schema_for;
3338
3339    use crate::utils::F;
3340
3341    use super::*;
3342    use crate::utils::ApproxEq;
3343
3344    #[test]
3345    fn polarization() {
3346        let mom = FourMomentum::from_args(F(1.), F(1.), F(0.), F(0.));
3347
3348        let pol: Polarization<F<f64>> = Polarization::lorentz(mom.pol_one());
3349        println!("{}", pol)
3350
3351        // let pol2 = pol.clone();
3352
3353        // print!("{}", pol.add_fallible(&pol2).unwrap());
3354
3355        // println!("pol_one: {:?}", pol);
3356
3357        // let structure = pol.tensor.structure.clone();
3358
3359        // println!("{}", structure.flat_index([2]).unwrap());
3360
3361        // pol.tensor
3362        //     .iter_flat()
3363        //     .for_each(|(i, d)| println!("{}{}", i, d));
3364    }
3365
3366    #[test]
3367    fn serialization() {
3368        #[derive(Debug, Serialize, Deserialize, PartialEq)]
3369        struct HelicityToml {
3370            helicities: Vec<Helicity>,
3371        }
3372
3373        let hels = vec![
3374            Helicity::PLUS,
3375            Helicity::MINUS,
3376            Helicity::ZERO,
3377            Helicity::Summed,
3378            Helicity::SummedAveraged,
3379        ];
3380        let serialized = serde_json::to_string(&hels).unwrap();
3381        assert_eq!(serialized, r#"[1,-1,0,"summed","summed_averaged"]"#);
3382
3383        let deserialized: Vec<Helicity> =
3384            serde_json::from_str(r#"[1,"minus","0","summed","summed-averaged"]"#).unwrap();
3385        assert_eq!(deserialized, hels);
3386
3387        let toml_value = toml::from_str::<HelicityToml>(
3388            r#"
3389helicities = [1, "minus", "zero", "summed", "summed_averaged"]
3390"#,
3391        )
3392        .unwrap();
3393        assert_eq!(toml_value.helicities, hels);
3394
3395        let schema = serde_json::to_value(schema_for!(Helicity)).unwrap();
3396        let allowed_strings = &schema["enum"];
3397        assert!(allowed_strings.is_null());
3398        assert!(
3399            schema["oneOf"][0]["enum"]
3400                .as_array()
3401                .unwrap()
3402                .contains(&(-1).into())
3403        );
3404        assert!(
3405            schema["oneOf"][1]["enum"]
3406                .as_array()
3407                .unwrap()
3408                .contains(&"summed_averaged".into())
3409        );
3410    }
3411
3412    #[test]
3413    fn eps() {
3414        let mom = FourMomentum::from_args(
3415            F(156.2565490076973),
3416            F(-108.59017233120495),
3417            F(-100.2097685717689),
3418            F(50.81619686346704),
3419        );
3420
3421        let pol = mom.eps_pol(SignOrZero::Plus);
3422        println!("{}", pol);
3423
3424        let mom = FourMomentum::from_args(
3425            F(441.7831721921727),
3426            F(237.7632083614734),
3427            F(368.76330416225863),
3428            F(-51.523329523343534),
3429        );
3430
3431        let pol = mom.eps_pol(SignOrZero::Plus);
3432        println!("{}", pol);
3433
3434        let mom = FourMomentum::from_args(F(485.0355), F(0.), F(0.), F(485.0355));
3435
3436        let pol = mom.eps_pol(SignOrZero::Plus);
3437
3438        println!("{}", pol);
3439
3440        println!("pt{}", mom.pt());
3441
3442        let mom = FourMomentum::from_args(F(485.0355), F(-107.6044), F(-431.5174), F(193.5805));
3443
3444        let pol = mom.eps_pol(SignOrZero::Plus).bar();
3445
3446        // Helicity=           1           1           1           1
3447        //  W(*,1)=               (5.4707403520912967,0.0000000000000000)               (0.0000000000000000,0.0000000000000000)               (31.622776601683793,0.0000000000000000)               (0.0000000000000000,0.0000000000000000)
3448        //  W(*,2)=               (0.0000000000000000,0.0000000000000000)             (-0.70710678118654757,0.0000000000000000)              (0.0000000000000000,0.70710678118654757)               (0.0000000000000000,0.0000000000000000)
3449        //  W(*,3)=               (17.333409072341226,0.0000000000000000)              (6.3994499859138338,-25.663202641304650)               (2.9986797695150327,0.0000000000000000)              (1.1071048475630934,-4.4397340569457056)
3450        //  W(*,4)=               (0.0000000000000000,0.0000000000000000)        (6.82818793416064135E-002,0.68609707126238750)            (0.27382536157480858,-0.17108713804717862)              (0.64834964048112464,0.0000000000000000)
3451
3452        println!("{}", pol);
3453        println!("pt{}", mom.pt());
3454    }
3455
3456    #[test]
3457    fn spinors_degenerate() {
3458        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3459
3460        assert_eq!(
3461            [Complex::new_zero(), Complex::new_re(F(-1.))],
3462            mom.xi(Sign::Positive)
3463        );
3464
3465        assert_eq!(
3466            [Complex::new_re(F(1.)), Complex::new_re(F(0.))],
3467            mom.xi(Sign::Negative)
3468        );
3469
3470        let u_p = mom.u(Sign::Positive);
3471        let u_p_target: Polarization<Complex<F<_>>> =
3472            Polarization::bispinor_u([F(0.), F(0.), F(0.), F(-2.)]).cast();
3473
3474        u_p.approx_eq_res(&u_p_target, &F(0.001))
3475            .wrap_err("u+((2,0,0,-2)) does not match target: (0,0,0,-2)")
3476            .unwrap();
3477
3478        let mut u_p_bar_target: Polarization<Complex<F<_>>> =
3479            Polarization::bispinor_u([F(0.), F(-2.), F(0.), F(0.)]).cast();
3480
3481        u_p_bar_target.pol_type = PolType::UBar;
3482
3483        u_p.bar()
3484            .approx_eq_res(&u_p_bar_target, &F(0.001))
3485            .wrap_err("u+bar((2,0,0,-2)) does not match target: (0,-2,0,0)")
3486            .unwrap();
3487
3488        let u_m = mom.u(Sign::Negative);
3489        let u_m_target: Polarization<Complex<F<_>>> =
3490            Polarization::bispinor_u([F(2.), F(0.), F(0.), F(0.)]).cast();
3491
3492        u_m.approx_eq_res(&u_m_target, &F(0.001))
3493            .wrap_err("u-((2,0,0,-2)) does not match target: (2,0,0,0)")
3494            .unwrap();
3495
3496        let mut u_m_bar_target: Polarization<Complex<F<_>>> =
3497            Polarization::bispinor_u([F(0.), F(0.), F(2.), F(0.)]).cast();
3498
3499        u_m_bar_target.pol_type = PolType::UBar;
3500
3501        u_m.bar()
3502            .approx_eq_res(&u_m_bar_target, &F(0.001))
3503            .wrap_err("u-bar((2,0,0,-2)) does not match target: (0,0,2,0)")
3504            .unwrap();
3505
3506        let v_p = mom.v(Sign::Positive);
3507        let v_p_target: Polarization<Complex<F<_>>> =
3508            Polarization::bispinor_v([F(-2.), F(0.), F(0.), F(0.)]).cast();
3509
3510        v_p.approx_eq_res(&v_p_target, &F(0.001))
3511            .wrap_err("v+((2,0,0,-2)) does not match target: (-2,0,0,0)")
3512            .unwrap();
3513
3514        let mut v_p_bar_target: Polarization<Complex<F<_>>> =
3515            Polarization::bispinor_v([F(0.), F(0.), F(-2.), F(0.)]).cast();
3516
3517        v_p_bar_target.pol_type = PolType::VBar;
3518
3519        v_p.bar()
3520            .approx_eq_res(&v_p_bar_target, &F(0.001))
3521            .wrap_err("v+bar((2,0,0,-2)) does not match target: (0,0,-2,0)")
3522            .unwrap();
3523
3524        let v_m = mom.v(Sign::Negative);
3525        let v_m_target: Polarization<Complex<F<_>>> =
3526            Polarization::bispinor_v([F(0.), F(0.), F(0.), F(2.)]).cast();
3527
3528        v_m.approx_eq_res(&v_m_target, &F(0.001))
3529            .wrap_err("v-((2,0,0,-2)) does not match target: (0,0,0,2)")
3530            .unwrap();
3531
3532        let mut v_m_bar_target: Polarization<Complex<F<_>>> =
3533            Polarization::bispinor_v([F(0.), F(2.), F(0.), F(0.)]).cast();
3534
3535        v_m_bar_target.pol_type = PolType::VBar;
3536
3537        v_m.bar()
3538            .approx_eq_res(&v_m_bar_target, &F(0.001))
3539            .wrap_err("v-bar((2,0,0,-2)) does not match target: (0,2,0,0)")
3540            .unwrap();
3541    }
3542
3543    #[test]
3544    fn spinors() {
3545        let mom = FourMomentum::from_args(F(4.), F(0.), F(4.), F(0.));
3546
3547        assert!(
3548            Complex::approx_eq_slice(
3549                &[
3550                    Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3551                    Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3552                ],
3553                &mom.xi(Sign::Positive),
3554                &F(0.001),
3555            ),
3556            "xi+((4,0,4,0)) does not match target: (1/sqrt(2),i/sqrt(2))"
3557        );
3558
3559        assert!(
3560            Complex::approx_eq_slice(
3561                &[
3562                    Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3563                    Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3564                ],
3565                &mom.xi(Sign::Negative),
3566                &F(0.001),
3567            ),
3568            "xi-((4,0,4,0)) does not match target: (i/sqrt(2),1/sqrt(2))"
3569        );
3570
3571        let zero: Complex<_> = F(0.).into();
3572        let u_p = mom.u(Sign::Positive);
3573        let u_p_target: Polarization<Complex<F<_>>> =
3574            Polarization::bispinor_u([zero, zero, Complex::new_re(F(2.)), Complex::new_im(F(2.))])
3575                .cast();
3576
3577        u_p.approx_eq_res(&u_p_target, &F(0.001))
3578            .wrap_err("u+((4,0,4,0)) does not match target: (0,0,2,i2)")
3579            .unwrap();
3580
3581        let mut u_p_bar_target: Polarization<Complex<F<_>>> =
3582            Polarization::bispinor_u([Complex::new_re(F(2.)), Complex::new_im(-F(2.)), zero, zero])
3583                .cast();
3584
3585        u_p_bar_target.pol_type = PolType::UBar;
3586
3587        u_p.bar()
3588            .approx_eq_res(&u_p_bar_target, &F(0.001))
3589            .wrap_err("u+bar((4,0,4,0)) does not match target: (2,-2i,0,0)")
3590            .unwrap();
3591
3592        let u_m = mom.u(Sign::Negative);
3593        let u_m_target: Polarization<Complex<F<_>>> =
3594            Polarization::bispinor_u([Complex::new_im(F(2.)), Complex::new_re(F(2.)), zero, zero])
3595                .cast();
3596
3597        u_m.approx_eq_res(&u_m_target, &F(0.001))
3598            .wrap_err("u-((4,0,4,0)) does not match target: (2i,2,0,0)")
3599            .unwrap();
3600
3601        let mut u_m_bar_target: Polarization<Complex<F<_>>> =
3602            Polarization::bispinor_u([zero, zero, Complex::new_im(-F(2.)), Complex::new_re(F(2.))])
3603                .cast();
3604
3605        u_m_bar_target.pol_type = PolType::UBar;
3606
3607        u_m.bar()
3608            .approx_eq_res(&u_m_bar_target, &F(0.001))
3609            .wrap_err("u-bar((4,0,4,0)) does not match target: (0,0,-2i,2)")
3610            .unwrap();
3611
3612        let v_p = mom.v(Sign::Positive);
3613        let v_p_target: Polarization<Complex<F<_>>> = Polarization::bispinor_v([
3614            Complex::new_im(-F(2.)),
3615            Complex::new_re(-F(2.)),
3616            zero,
3617            zero,
3618        ])
3619        .cast();
3620
3621        v_p.approx_eq_res(&v_p_target, &F(0.001))
3622            .wrap_err("v+((4,0,4,0)) does not match target: (-2i,-2,0,0)")
3623            .unwrap();
3624
3625        let mut v_p_bar_target: Polarization<Complex<F<_>>> =
3626            Polarization::bispinor_v([zero, zero, Complex::new_im(F(2.)), Complex::new_re(F(-2.))])
3627                .cast();
3628
3629        v_p_bar_target.pol_type = PolType::VBar;
3630
3631        v_p.bar()
3632            .approx_eq_res(&v_p_bar_target, &F(0.001))
3633            .wrap_err("v+bar((4,0,4,0)) does not match target: (0,0,2i,-2)")
3634            .unwrap();
3635
3636        let v_m = mom.v(Sign::Negative);
3637        let v_m_target: Polarization<Complex<F<_>>> = Polarization::bispinor_v([
3638            zero,
3639            zero,
3640            Complex::new_re(-F(2.)),
3641            Complex::new_im(-F(2.)),
3642        ])
3643        .cast();
3644
3645        v_m.approx_eq_res(&v_m_target, &F(0.001))
3646            .wrap_err("v-((4,0,4,0)) does not match target: (0,0,-2,-2i)")
3647            .unwrap();
3648
3649        let mut v_m_bar_target: Polarization<Complex<F<_>>> =
3650            Polarization::bispinor_v([Complex::new_re(-F(2.)), Complex::new_im(F(2.)), zero, zero])
3651                .cast();
3652
3653        v_m_bar_target.pol_type = PolType::VBar;
3654
3655        v_m.bar()
3656            .approx_eq_res(&v_m_bar_target, &F(0.001))
3657            .wrap_err("v-bar((4,0,4,0)) does not match target: (-2,2i,0,0)")
3658            .unwrap();
3659    }
3660
3661    #[test]
3662    fn vectors_degenerate() {
3663        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3664
3665        assert!(
3666            F::approx_eq_slice(&mom.pol_one(), &[0., 1., 0., 0.].map(F), &F(0.001)),
3667            "pol_one((2,0,0,-2)) does not match target: (0,1,0,0)"
3668        );
3669
3670        assert!(
3671            F::approx_eq_slice(&mom.pol_two(), &[0., 0., -1., 0.].map(F), &F(0.001)),
3672            "pol_two((2,0,0,-2)) does not match target: (0,0,-1,0)"
3673        );
3674
3675        let e_p = mom.eps_pol(SignOrZero::Plus);
3676
3677        let zero = F(0.).into();
3678        let e_p_target = Polarization::lorentz([
3679            zero,
3680            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3681            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3682            zero,
3683        ]);
3684
3685        let mut e_p_bar_target = Polarization::lorentz([
3686            zero,
3687            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3688            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3689            zero,
3690        ]);
3691
3692        e_p_bar_target.pol_type = PolType::EpsilonBar;
3693
3694        e_p.approx_eq_res(&e_p_target, &F(0.0001))
3695            .wrap_err("e+((2,0,0,-2)) does not match target: (0,-1/sqrt(2),i/sqrt(2),0)")
3696            .unwrap();
3697
3698        e_p.bar()
3699            .approx_eq_res(&e_p_bar_target, &F(0.0001))
3700            .wrap_err("e+bar((2,0,0,-2)) does not match target: (0,-1/sqrt(2),-i/sqrt(2),0)")
3701            .unwrap();
3702
3703        let e_m = mom.eps_pol(SignOrZero::Minus);
3704
3705        let e_m_target = Polarization::lorentz([
3706            zero,
3707            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3708            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3709            zero,
3710        ]);
3711
3712        let mut e_m_bar_target = Polarization::lorentz([
3713            zero,
3714            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3715            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3716            zero,
3717        ]);
3718
3719        e_m_bar_target.pol_type = PolType::EpsilonBar;
3720
3721        e_m.approx_eq_res(&e_m_target, &F(0.0001))
3722            .wrap_err("e-((2,0,0,-2)) does not match target: (0,1/sqrt(2),i/sqrt(2),0)")
3723            .unwrap();
3724
3725        e_m.bar()
3726            .approx_eq_res(&e_m_bar_target, &F(0.0001))
3727            .wrap_err("e-bar((2,0,0,-2)) does not match target: (0,1/sqrt(2),-i/sqrt(2),0)")
3728            .unwrap();
3729    }
3730
3731    #[test]
3732    fn vectors() {
3733        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3734
3735        assert!(
3736            F::approx_eq_slice(&mom.pol_one(), &[0., 1., 0., 0.].map(F), &F(0.001)),
3737            "pol_one((2,0,0,-2)) does not match target: (0,1,0,0)"
3738        );
3739
3740        assert!(
3741            F::approx_eq_slice(&mom.pol_two(), &[0., 0., -1., 0.].map(F), &F(0.001)),
3742            "pol_two((2,0,0,-2)) does not match target: (0,0,-1,0)"
3743        );
3744
3745        let e_p = mom.eps_pol(SignOrZero::Plus);
3746
3747        let zero = F(0.).into();
3748        let e_p_target = Polarization::lorentz([
3749            zero,
3750            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3751            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3752            zero,
3753        ]);
3754
3755        let mut e_p_bar_target = Polarization::lorentz([
3756            zero,
3757            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3758            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3759            zero,
3760        ]);
3761
3762        e_p_bar_target.pol_type = PolType::EpsilonBar;
3763
3764        e_p.approx_eq_res(&e_p_target, &F(0.0001))
3765            .wrap_err("e+((2,0,0,-2)) does not match target: (0,-1/sqrt(2),i/sqrt(2),0)")
3766            .unwrap();
3767
3768        e_p.bar()
3769            .approx_eq_res(&e_p_bar_target, &F(0.0001))
3770            .wrap_err("e+bar((2,0,0,-2)) does not match target: (0,-1/sqrt(2),-i/sqrt(2),0)")
3771            .unwrap();
3772
3773        let e_m = mom.eps_pol(SignOrZero::Minus);
3774
3775        let e_m_target = Polarization::lorentz([
3776            zero,
3777            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3778            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3779            zero,
3780        ]);
3781
3782        let mut e_m_bar_target = Polarization::lorentz([
3783            zero,
3784            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3785            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3786            zero,
3787        ]);
3788
3789        e_m_bar_target.pol_type = PolType::EpsilonBar;
3790
3791        e_m.approx_eq_res(&e_m_target, &F(0.0001))
3792            .wrap_err("e-((2,0,0,-2)) does not match target: (0,1/sqrt(2),i/sqrt(2),0)")
3793            .unwrap();
3794
3795        e_m.bar()
3796            .approx_eq_res(&e_m_bar_target, &F(0.0001))
3797            .wrap_err("e-bar((2,0,0,-2)) does not match target: (0,1/sqrt(2),-i/sqrt(2),0)")
3798            .unwrap();
3799    }
3800
3801    #[test]
3802    fn rotations() {
3803        let mom = FourMomentum::from_args(F(2.), F(3.), F(1.), F(2.));
3804
3805        let momc: [F<f64>; 4] = mom.into();
3806        let e = Polarization::lorentz(momc.map(Complex::new_re));
3807        let u = mom.u(Sign::Negative);
3808
3809        let rot: Rotation = RotationMethod::EulerAngles(std::f64::consts::PI / 2., 0., 0.).into();
3810        let rotx = RotationMethod::Pi2X.into();
3811
3812        let rotye: Rotation = RotationMethod::EulerAngles(0., std::f64::consts::PI / 2., 0.).into();
3813        let roty = RotationMethod::Pi2Y.into();
3814
3815        let rotze: Rotation = RotationMethod::EulerAngles(0., 0., std::f64::consts::PI / 2.).into();
3816        let rotz = RotationMethod::Pi2Z.into();
3817
3818        let rotid = RotationMethod::Identity.into();
3819        // println!(
3820        //     "{}",
3821        //     rot.bispinor_tensor(
3822        //         Bispinor::new_slot_selfless(4, 2),
3823        //         Bispinor::new_slot_selfless(4, 1),
3824        //     )
3825        // );
3826
3827        let mom_rot = mom.rotate(&rot);
3828        let mom_id = mom.rotate(&rotid);
3829        let other_mom_rot = mom.rotate(&rotx);
3830        let mom_rotye = mom.rotate(&rotye);
3831        let mom_roty = mom.rotate(&roty);
3832        let mom_rotze = mom.rotate(&rotze);
3833        let mom_rotz = mom.rotate(&rotz);
3834
3835        println!("id:\t{}", mom_id);
3836        mom_id
3837            .approx_eq_res(&mom, &F(0.001))
3838            .wrap_err("mom is not identical to mom transformed with identity")
3839            .unwrap();
3840        println!("orig:\t{}", mom);
3841
3842        println!("rotxe:\t{}", mom_rot);
3843        println!("pi2x:\t{}", other_mom_rot);
3844        mom_rot.approx_eq_res(&other_mom_rot, &F(0.001)).unwrap();
3845
3846        println!("rotye:\t{}", mom_rotye);
3847        println!("pi2y:\t{}", mom_roty);
3848
3849        mom_rotye.approx_eq_res(&mom_roty, &F(0.001)).unwrap();
3850        println!("rotze:\t{}", mom_rotze);
3851        println!("pi2z:\t{}", mom_rotz);
3852        mom_rotze.approx_eq_res(&mom_rotz, &F(0.001)).unwrap();
3853
3854        let e_rot = e.rotate(&rot);
3855        println!("rotxe:{}", e_rot);
3856        mom_rot.approx_eq_res(&e_rot, &F(0.001)).unwrap();
3857
3858        let e_id = e.rotate(&rotid);
3859        mom_id.approx_eq_res(&e_id, &F(0.001)).unwrap();
3860        let e_rotye = e.rotate(&rotye);
3861
3862        println!("rotye:{}", e_rotye);
3863        mom_rotye.approx_eq_res(&e_rotye, &F(0.001)).unwrap();
3864        let e_roty = e.rotate(&roty);
3865        mom_roty.approx_eq_res(&e_roty, &F(0.001)).unwrap();
3866        let e_rotze = e.rotate(&rotze);
3867        mom_rotze.approx_eq_res(&e_rotze, &F(0.001)).unwrap();
3868        let e_rotz = e.rotate(&rotz);
3869        mom_rotz.approx_eq_res(&e_rotz, &F(0.001)).unwrap();
3870        let u_rot = u.rotate(&rot);
3871        let u_id = u.rotate(&rotid);
3872
3873        let other_e_rot = e.rotate(&rotx);
3874
3875        let other_u_rot = u.rotate(&rotx);
3876
3877        println!("id:{}", e_id);
3878        println!("orig:{}", e);
3879        println!("euler:{}", e_rot);
3880        println!("Pi2X:{}", other_e_rot);
3881        println!("rotye:{}", e_rotye);
3882        println!("pi2y:{}", e_roty);
3883        println!("rotze:{}", e_rotze);
3884        println!("pi2z:{}", e_rotz);
3885
3886        println!("id:{}", u_id);
3887        println!("orig:{}", u);
3888        println!("euler:{}", u_rot);
3889        println!("Pi2X {}", other_u_rot);
3890    }
3891
3892    // #[test]
3893    // fn omega() {
3894    //     let mu = PhysReps::new_slot(Minkowski {}.into(), 4, 0);
3895
3896    //     let nu = PhysReps::new_slot(Minkowski {}.into(), 4, 1);
3897
3898    //     let mud = mu.dual();
3899
3900    //     let nud = nu.dual();
3901
3902    //     let i = PhysReps::new_slot(GR.bis.into(), 4, 2);
3903
3904    //     let j = PhysReps::new_slot(GR.bis.into(), 4, 3);
3905
3906    //     let k = PhysReps::new_slot(GR.bis.into(), 4, 4);
3907
3908    //     let zero = Atom::num(0);
3909    //     let theta_x = parse!("theta_x").unwrap();
3910    //     let theta_y = parse!("theta_y").unwrap();
3911    //     let theta_z = parse!("theta_z").unwrap();
3912
3913    //     let omega = DenseTensor::from_data(
3914    //         vec![
3915    //             zero.clone(),
3916    //             zero.clone(),
3917    //             zero.clone(),
3918    //             zero.clone(),
3919    //             //
3920    //             zero.clone(),
3921    //             zero.clone(),
3922    //             theta_z.clone(),
3923    //             -theta_y.clone(),
3924    //             //
3925    //             zero.clone(),
3926    //             -theta_z.clone(),
3927    //             zero.clone(),
3928    //             theta_x.clone(),
3929    //             //
3930    //             zero.clone(),
3931    //             theta_y.clone(),
3932    //             -theta_x.clone(),
3933    //             zero.clone(),
3934    //         ],
3935    //         OrderedStructure::from_iter([mu, nu]),
3936    //     )
3937    //     .unwrap();
3938
3939    //     println!("{}", omega);
3940
3941    //     let gammamujk: SparseTensor<Complex<f64>> =
3942    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([mud, j, k]));
3943
3944    //     let gammamukj: SparseTensor<Complex<f64>> =
3945    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([mud, k, i]));
3946
3947    //     let gammanuki: SparseTensor<Complex<f64>> =
3948    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([nud, k, i]));
3949
3950    //     let gammanujk: SparseTensor<Complex<f64>> =
3951    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([nud, j, k]));
3952
3953    //     let sigma = (gammamujk
3954    //         .contract(&gammanuki)
3955    //         .unwrap()
3956    //         .sub_fallible(&gammanujk.contract(&gammamukj).unwrap())
3957    //         .unwrap())
3958    //     .scalar_mul(&Complex::new_im(0.25))
3959    //     .unwrap();
3960
3961    //     println!("{}", sigma);
3962
3963    //     println!("{}", omega.contract(&sigma).unwrap());
3964    // }
3965}