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        Canonicalized, CastStructure, IndexLess, NamedStructure, OrderedStructure,
27        TensorDataLayout, 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            Canonicalized::from_iter(vec![Euclidean {}.new_slot(3, index)]).into_canonical();
514        DenseTensor::from_storage_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            Canonicalized::from_iter(vec![Euclidean {}.new_slot(3, index)]).into_canonical();
523        DenseTensor::from_storage_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            Canonicalized::from_iter([LibraryRep::new_slot(Minkowski {}.into(), 4, index)])
1715                .into_canonical();
1716        DenseTensor::from_storage_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 = Canonicalized::<OrderedStructure>::from_iter([LibraryRep::new_slot(
1738            Minkowski {}.into(),
1739            4,
1740            index,
1741        )])
1742        .into_canonical()
1743        .to_named(name, Some(num));
1744        DenseTensor::from_storage_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            Canonicalized::from_iter([LibraryRep::new_slot(Minkowski {}.into(), 4, index)])
2554                .into_canonical();
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_storage_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, ()> = Canonicalized::<OrderedStructure>::from_iter([mu])
2868            .into_canonical()
2869            .to_named("eps".to_string(), None);
2870        let shadow_t: MixedTensor<_, OrderedStructure> =
2871            ParamOrConcrete::param(shadow.to_shell().expanded_shadow().unwrap().into())
2872                .cast_structure();
2873
2874        let rotation: MixedTensor<f64, _> = method
2875            .lorentz_tensor(mud, al)
2876            .try_into_dense()
2877            .unwrap()
2878            .into();
2879
2880        let fn_map = FunctionMap::new();
2881
2882        let lorentz_eval: EvalTensor<ExpressionEvaluator<SymComplex<Rational>>, OrderedStructure> =
2883            shadow_t
2884                .contract(&rotation)
2885                .unwrap()
2886                .try_into_parametric()
2887                .unwrap()
2888                .to_evaluation_tree(
2889                    &fn_map,
2890                    &shadow_t.try_into_parametric().unwrap().tensor.data(),
2891                )
2892                .unwrap()
2893                .linearize(
2894                    &OptimizationSettings::new()
2895                        .cpe_iterations(Some(1))
2896                        .verbose(false),
2897                );
2898
2899        let i = GR.bis.new_slot(4, 1);
2900
2901        let j = GR.bis.new_slot(4, 3);
2902
2903        let shadow: NamedStructure<String, ()> = Canonicalized::from_iter([i.cast::<LibraryRep>()])
2904            .into_canonical()
2905            .to_named("u".to_string(), None);
2906        let shadow_t: MixedTensor<_, OrderedStructure> =
2907            ParamOrConcrete::param(shadow.to_shell().expanded_shadow().unwrap().into())
2908                .cast_structure();
2909
2910        let rotation: MixedTensor<f64, _> =
2911            ParamOrConcrete::Concrete(RealOrComplexTensor::Complex(method.bispinor_tensor(i, j)));
2912
2913        let res = shadow_t
2914            .contract(&rotation)
2915            .unwrap()
2916            .try_into_parametric()
2917            .unwrap();
2918
2919        let fn_map = FunctionMap::new();
2920        let mut params = shadow_t.try_into_parametric().unwrap().tensor.data();
2921        params.push(Atom::i());
2922
2923        let spinor_eval: EvalTensor<ExpressionEvaluator<SymComplex<Rational>>, OrderedStructure> =
2924            res.to_evaluation_tree(&fn_map, &params).unwrap().linearize(
2925                &OptimizationSettings::new()
2926                    .cpe_iterations(Some(1))
2927                    .verbose(false),
2928            );
2929
2930        Self {
2931            method,
2932            lorentz_rotation: lorentz_eval.map_coeff(&|f| {
2933                Complex::new(F::from_f64(f.re.to_f64()), F::from_f64(f.im.to_f64()))
2934            }),
2935            bispinor_rotation: spinor_eval.map_coeff(&|f| {
2936                Complex::new(F::from_f64(f.re.to_f64()), F::from_f64(f.im.to_f64()))
2937            }),
2938        }
2939    }
2940}
2941
2942impl RotationMethod {
2943    pub(crate) fn generator(
2944        &self,
2945        i: AbstractIndex,
2946        j: AbstractIndex,
2947    ) -> DataTensor<f64, OrderedStructure> {
2948        let structure = Canonicalized::from_iter([
2949            LibraryRep::new_slot(Minkowski {}.into(), 4, i),
2950            LibraryRep::new_slot(Minkowski {}.into(), 4, j),
2951        ]);
2952        let layout = TensorDataLayout::from_canonicalized(&structure)
2953            .expect("rotation generators have concrete dimensions");
2954        let zero = 0.;
2955        match self {
2956            RotationMethod::Identity => {
2957                let omega = SparseTensor::empty(structure.into_canonical(), zero);
2958                omega.into()
2959            }
2960            RotationMethod::Pi2X => {
2961                let mut omega = SparseTensor::empty(structure.into_canonical(), zero);
2962                omega
2963                    .set(
2964                        &layout
2965                            .logical_expanded_to_storage_expanded(&[2, 3])
2966                            .unwrap(),
2967                        -zero.PIHALF(),
2968                    )
2969                    .unwrap();
2970                omega
2971                    .set(
2972                        &layout
2973                            .logical_expanded_to_storage_expanded(&[3, 2])
2974                            .unwrap(),
2975                        zero.PIHALF(),
2976                    )
2977                    .unwrap();
2978                omega.into()
2979            }
2980            RotationMethod::Pi2Y => {
2981                let mut omega = SparseTensor::empty(structure.into_canonical(), zero);
2982                omega
2983                    .set(
2984                        &layout
2985                            .logical_expanded_to_storage_expanded(&[1, 3])
2986                            .unwrap(),
2987                        zero.PIHALF(),
2988                    )
2989                    .unwrap();
2990                omega
2991                    .set(
2992                        &layout
2993                            .logical_expanded_to_storage_expanded(&[3, 1])
2994                            .unwrap(),
2995                        -zero.PIHALF(),
2996                    )
2997                    .unwrap();
2998                omega.into()
2999            }
3000            RotationMethod::Pi2Z => {
3001                let mut omega = SparseTensor::empty(structure.into_canonical(), zero);
3002                omega
3003                    .set(
3004                        &layout
3005                            .logical_expanded_to_storage_expanded(&[1, 2])
3006                            .unwrap(),
3007                        -zero.PIHALF(),
3008                    )
3009                    .unwrap();
3010                omega
3011                    .set(
3012                        &layout
3013                            .logical_expanded_to_storage_expanded(&[2, 1])
3014                            .unwrap(),
3015                        zero.PIHALF(),
3016                    )
3017                    .unwrap();
3018                omega.into()
3019            }
3020            RotationMethod::EulerAngles(alpha, beta, gamma) => DenseTensor::from_storage_data(
3021                layout
3022                    .reorder_to_storage(vec![
3023                        // row 0
3024                        zero, zero, zero, zero, // row 1
3025                        zero, zero, -gamma, *beta, // row 2
3026                        zero, *gamma, zero, -alpha, // row 3
3027                        zero, -beta, *alpha, zero,
3028                    ])
3029                    .unwrap(),
3030                structure.into_canonical(),
3031            )
3032            .unwrap()
3033            .into(),
3034        }
3035    }
3036
3037    pub(crate) fn lorentz_tensor(
3038        &self,
3039        i: Slot<Minkowski>,
3040        j: Slot<Minkowski>,
3041    ) -> DataTensor<f64, OrderedStructure> {
3042        let structure = Canonicalized::from_iter([i.cast::<LibraryRep>(), j.cast()]);
3043        let layout = TensorDataLayout::from_canonicalized(&structure)
3044            .expect("Lorentz rotations have concrete dimensions");
3045
3046        match self {
3047            RotationMethod::Identity => {
3048                let rot = DenseTensor::from_storage_data(
3049                    layout
3050                        .reorder_to_storage(vec![
3051                            1., 0., 0., 0., // row 1
3052                            0., -1., 0., 0., // row 2
3053                            0., 0., -1., 0., // row 3
3054                            0., 0., 0., -1.,
3055                        ])
3056                        .unwrap(),
3057                    structure.into_canonical(),
3058                )
3059                .unwrap();
3060                rot.into()
3061            }
3062            RotationMethod::Pi2X => {
3063                let rot = DenseTensor::from_storage_data(
3064                    layout
3065                        .reorder_to_storage(vec![
3066                            1., 0., 0., 0., // row 1
3067                            0., -1., 0., 0., // row 2
3068                            0., 0., 0., -1., // row 3
3069                            0., 0., 1., 0.,
3070                        ])
3071                        .unwrap(),
3072                    structure.into_canonical(),
3073                )
3074                .unwrap();
3075                rot.into()
3076            }
3077            RotationMethod::Pi2Y => {
3078                let rot = DenseTensor::from_storage_data(
3079                    layout
3080                        .reorder_to_storage(vec![
3081                            1., 0., 0., 0., // row 1
3082                            0., 0., 0., 1., // row 2
3083                            0., 0., -1., 0., // row 3
3084                            0., -1., 0., 0.,
3085                        ])
3086                        .unwrap(),
3087                    structure.into_canonical(),
3088                )
3089                .unwrap();
3090                rot.into()
3091            }
3092            RotationMethod::Pi2Z => {
3093                let rot = DenseTensor::from_storage_data(
3094                    layout
3095                        .reorder_to_storage(vec![
3096                            1., 0., 0., 0., // row 1
3097                            0., 0., -1., 0., // row 2
3098                            0., 1., 0., 0., // row 3
3099                            0., 0., 0., -1.,
3100                        ])
3101                        .unwrap(),
3102                    structure.into_canonical(),
3103                )
3104                .unwrap();
3105                rot.into()
3106            }
3107            RotationMethod::EulerAngles(alpha, beta, gamma) => DenseTensor::from_storage_data(
3108                layout
3109                    .reorder_to_storage(vec![
3110                        // row 0
3111                        1.,
3112                        0.,
3113                        0.,
3114                        0.,
3115                        // row 1
3116                        0.,
3117                        -gamma.cos() * beta.cos(),
3118                        alpha.sin() * beta.sin() * gamma.cos() - alpha.cos() * gamma.sin(),
3119                        alpha.sin() * gamma.sin() + alpha.cos() * beta.sin() * gamma.cos(),
3120                        // row 2
3121                        0.,
3122                        gamma.sin() * beta.cos(),
3123                        -alpha.cos() * gamma.cos() - alpha.sin() * beta.sin() * gamma.sin(),
3124                        -alpha.sin() * gamma.cos() + alpha.cos() * beta.sin() * gamma.sin(),
3125                        // row 3
3126                        0.,
3127                        -beta.sin(),
3128                        alpha.sin() * beta.cos(),
3129                        -alpha.cos() * beta.cos(),
3130                    ])
3131                    .unwrap(),
3132                structure.into_canonical(),
3133            )
3134            .unwrap()
3135            .into(),
3136            // Rotation::EulerAngles(alpha, beta, gamma) => DenseTensor::from_storage_data(
3137            //     vec![
3138            //         // row 0
3139            //         zero.one(),
3140            //         zero.clone(),
3141            //         zero.clone(),
3142            //         zero.clone(),
3143            //         // row 1
3144            //         zero.clone(),
3145            //         alpha.cos() * beta.cos(),
3146            //         alpha.sin() * beta.sin() * gamma.cos() - alpha.cos() * gamma.sin(),
3147            //         alpha.sin() * gamma.sin() + alpha.cos() * beta.sin() * gamma.cos(),
3148            //         // row 2
3149            //         zero.clone(),
3150            //         gamma.sin() * beta.cos(),
3151            //         alpha.cos() * gamma.cos() + alpha.sin() * beta.sin() * gamma.sin(),
3152            //         -alpha.sin() * gamma.cos() + alpha.cos() * beta.sin() * gamma.sin(),
3153            //         // row 3
3154            //         zero.clone(),
3155            //         -beta.sin(),
3156            //         alpha.sin() * beta.cos(),
3157            //         alpha.cos() * beta.cos(),
3158            //     ],
3159            //     structure,
3160            // )
3161            // .unwrap()
3162            // .into(),
3163        }
3164    }
3165
3166    pub(crate) fn bispinor_tensor(
3167        &self,
3168        i: Slot<LibraryRep>,
3169        j: Slot<LibraryRep>,
3170    ) -> DataTensor<Complex<f64>, OrderedStructure> {
3171        let structure = Canonicalized::from_iter([i.cast::<LibraryRep>(), j.cast()]);
3172        let layout = TensorDataLayout::from_canonicalized(&structure)
3173            .expect("bispinor rotations have concrete dimensions");
3174        let storage_indices = |logical: &[usize]| {
3175            layout
3176                .logical_expanded_to_storage_expanded(logical)
3177                .expect("hard-coded bispinor coordinates are in bounds")
3178        };
3179        let zero = 0.; // F::new_zero();
3180        let zeroc = Complex::new_re(zero);
3181
3182        match self {
3183            RotationMethod::Identity => {
3184                let mut rot = SparseTensor::empty(structure.into_canonical(), zeroc);
3185                rot.set(&storage_indices(&[0, 0]), zeroc.one()).unwrap();
3186                rot.set(&storage_indices(&[1, 1]), zeroc.one()).unwrap();
3187                rot.set(&storage_indices(&[2, 2]), zeroc.one()).unwrap();
3188                rot.set(&storage_indices(&[3, 3]), zeroc.one()).unwrap();
3189                rot.into()
3190            }
3191            RotationMethod::Pi2X => {
3192                let mut rot = SparseTensor::empty(structure.into_canonical(), zeroc);
3193                let sqrt2_half = zero.SQRT_2_HALF();
3194                let sqrt2_halfim = Complex::new_im(sqrt2_half);
3195                let sqrt2_halfre = Complex::new_re(sqrt2_half);
3196
3197                rot.set(&storage_indices(&[0, 0]), sqrt2_halfim).unwrap();
3198                rot.set(&storage_indices(&[0, 1]), sqrt2_halfim).unwrap();
3199                rot.set(&storage_indices(&[1, 0]), sqrt2_halfim).unwrap();
3200                rot.set(&storage_indices(&[1, 1]), sqrt2_halfre).unwrap();
3201                rot.set(&storage_indices(&[2, 2]), sqrt2_halfim).unwrap();
3202                rot.set(&storage_indices(&[2, 3]), sqrt2_halfim).unwrap();
3203                rot.set(&storage_indices(&[3, 2]), sqrt2_halfim).unwrap();
3204                rot.set(&storage_indices(&[3, 3]), sqrt2_halfre).unwrap();
3205                rot.into()
3206            }
3207            RotationMethod::Pi2Y => {
3208                let mut rot = SparseTensor::empty(structure.into_canonical(), zeroc);
3209                let sqrt2_half = zero.SQRT_2_HALF();
3210                let sqrt2_halfim = Complex::new_im(sqrt2_half);
3211                let sqrt2_halfre = Complex::new_re(sqrt2_half);
3212                let nsqrt2_half = -sqrt2_halfre;
3213
3214                rot.set(&storage_indices(&[0, 0]), sqrt2_halfim).unwrap();
3215                rot.set(&storage_indices(&[0, 1]), nsqrt2_half).unwrap();
3216                rot.set(&storage_indices(&[1, 0]), nsqrt2_half).unwrap();
3217                rot.set(&storage_indices(&[1, 1]), sqrt2_halfre).unwrap();
3218                rot.set(&storage_indices(&[2, 2]), sqrt2_halfim).unwrap();
3219                rot.set(&storage_indices(&[2, 3]), nsqrt2_half).unwrap();
3220                rot.set(&storage_indices(&[3, 2]), nsqrt2_half).unwrap();
3221                rot.set(&storage_indices(&[3, 3]), sqrt2_halfre).unwrap();
3222                rot.into()
3223            }
3224            RotationMethod::Pi2Z => {
3225                let mut rot = SparseTensor::empty(structure.into_canonical(), zeroc);
3226                let sqrt2_half = zero.SQRT_2_HALF();
3227                let sqrt2_halfc = Complex::new(sqrt2_half, sqrt2_half);
3228                let sqrt2_halfcc = sqrt2_halfc.conj();
3229
3230                rot.set(&storage_indices(&[0, 0]), sqrt2_halfc).unwrap();
3231                rot.set(&storage_indices(&[1, 1]), sqrt2_halfcc).unwrap();
3232                rot.set(&storage_indices(&[2, 2]), sqrt2_halfc).unwrap();
3233                rot.set(&storage_indices(&[3, 3]), sqrt2_halfcc).unwrap();
3234                rot.into()
3235            }
3236            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3237                let norm = alpha.square() + beta.square() + gamma.square();
3238
3239                if alpha.is_zero() && beta.is_zero() {
3240                    let normhalf = &norm / 2.; //F::from_f64(2.);
3241                    let cos_phihalf = normhalf.cos();
3242                    let sin_phihalf = normhalf.sin();
3243
3244                    let e = Complex::new(cos_phihalf, sin_phihalf);
3245                    let econj = e.conj();
3246                    return DenseTensor::from_storage_data(
3247                        layout
3248                            .reorder_to_storage(vec![
3249                                // row 0
3250                                e, zeroc, zeroc, zeroc, // row 1
3251                                zeroc, econj, zeroc, zeroc, // row 2
3252                                zeroc, zeroc, e, zeroc, // row 3
3253                                zeroc, zeroc, zeroc, econj,
3254                            ])
3255                            .unwrap(),
3256                        structure.into_canonical(),
3257                    )
3258                    .unwrap()
3259                    .into();
3260                }
3261
3262                let complex_phi = Complex::new(*alpha, *beta);
3263
3264                let normhalf = &norm / 2.; //F::from_f64(2.);
3265                let cos_phihalf = normhalf.cos();
3266                let sin_phihalf = normhalf.sin();
3267
3268                let a_00 = Complex::new(gamma / norm * cos_phihalf, sin_phihalf);
3269                let a_01 =
3270                    Complex::new_im((norm - gamma.square() / norm) * sin_phihalf) / complex_phi;
3271                let a_10 = complex_phi * Complex::new_im(sin_phihalf / norm);
3272                let a_11 = Complex::new(cos_phihalf, -gamma / norm * sin_phihalf);
3273
3274                DenseTensor::from_storage_data(
3275                    layout
3276                        .reorder_to_storage(vec![
3277                            // row 0
3278                            a_00, a_01, zeroc, zeroc, // row 1
3279                            a_10, a_11, zeroc, zeroc, // row 2
3280                            zeroc, zeroc, a_00, a_01, // row 3
3281                            zeroc, zeroc, a_10, a_11,
3282                        ])
3283                        .unwrap(),
3284                    structure.into_canonical(),
3285                )
3286                .unwrap()
3287                .into()
3288            }
3289        }
3290    }
3291}
3292
3293pub trait Rotatable {
3294    fn rotate(&self, rotation: &Rotation) -> Self;
3295}
3296
3297impl<T: FloatLike> Rotatable for ThreeMomentum<F<T>> {
3298    fn rotate(&self, rotation: &Rotation) -> Self {
3299        match rotation.method {
3300            RotationMethod::Identity => self.clone(),
3301            RotationMethod::Pi2X => ThreeMomentum::perform_pi2_rotation_x(self),
3302            RotationMethod::Pi2Y => ThreeMomentum::perform_pi2_rotation_y(self),
3303            RotationMethod::Pi2Z => ThreeMomentum::perform_pi2_rotation_z(self),
3304            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3305                let mut result = self.clone();
3306                result.rotate_mut(&F::from_f64(alpha), &F::from_f64(beta), &F::from_f64(gamma));
3307                result
3308            }
3309        }
3310    }
3311}
3312
3313impl<T: FloatLike> Rotatable for ThreeMomentum<HyperDual<F<T>>> {
3314    fn rotate(&self, rotation: &Rotation) -> Self {
3315        match rotation.method {
3316            RotationMethod::Identity => self.clone(),
3317            RotationMethod::Pi2X => ThreeMomentum::perform_pi2_rotation_x(self),
3318            RotationMethod::Pi2Y => ThreeMomentum::perform_pi2_rotation_y(self),
3319            RotationMethod::Pi2Z => ThreeMomentum::perform_pi2_rotation_z(self),
3320            RotationMethod::EulerAngles(alpha, beta, gamma) => {
3321                let mut result = self.clone();
3322                result.rotate_mut(
3323                    &new_constant(&self.px, &F::from_f64(alpha)),
3324                    &new_constant(&self.py, &F::from_f64(beta)),
3325                    &new_constant(&self.pz, &F::from_f64(gamma)),
3326                );
3327                result
3328            }
3329        }
3330    }
3331}
3332
3333impl<T: FloatLike> ThreeMomentum<HyperDual<F<T>>> {
3334    fn rotate_mut(
3335        &mut self,
3336        alpha: &HyperDual<F<T>>,
3337        beta: &HyperDual<F<T>>,
3338        gamma: &HyperDual<F<T>>,
3339    ) {
3340        let sin_alpha = alpha.sin();
3341        let cos_alpha = alpha.cos();
3342        let sin_beta = beta.sin();
3343        let cos_beta = beta.cos();
3344        let sin_gamma = gamma.sin();
3345        let cos_gamma = gamma.cos();
3346
3347        let px = self.px.clone();
3348        let py = self.py.clone();
3349        let pz = self.pz.clone();
3350
3351        self.px = cos_gamma.clone() * &cos_beta * &px
3352            + (-(cos_alpha.clone()) * &sin_gamma + sin_alpha.clone() * &sin_beta * &cos_gamma)
3353                * &py
3354            + (sin_alpha.clone() * &sin_gamma + cos_alpha.clone() * &sin_beta * &cos_gamma) * &pz;
3355
3356        self.py = sin_gamma.clone() * &cos_beta * &px
3357            + (cos_alpha.clone() * &cos_gamma + sin_alpha.clone() * &sin_beta * &sin_gamma) * &py
3358            + (-sin_alpha.clone() * &cos_gamma + cos_alpha.clone() * &sin_beta * &sin_gamma) * &pz;
3359
3360        self.pz =
3361            -sin_beta * &px + cos_beta.clone() * &sin_alpha * &py + cos_alpha * &cos_beta * &pz;
3362    }
3363}
3364
3365impl<T: FloatLike> Rotatable for FourMomentum<F<T>> {
3366    fn rotate(&self, rotation: &Rotation) -> Self {
3367        Self {
3368            temporal: self.temporal.clone(),
3369            spatial: self.spatial.rotate(rotation),
3370        }
3371    }
3372}
3373
3374impl<T: FloatLike + EvaluationDomain> Rotatable for Polarization<Complex<F<T>>> {
3375    fn rotate(&self, rotation: &Rotation) -> Self {
3376        let rotated = match self.pol_type {
3377            PolType::Epsilon | PolType::EpsilonBar => rotation
3378                .lorentz_rotation
3379                .clone()
3380                .map_coeff(&|t| t.map(|f| F::from_ff64(f)))
3381                .evaluate(&self.tensor.data)
3382                .try_into_dense()
3383                .unwrap()
3384                .cast_structure(),
3385            PolType::U | PolType::UBar | PolType::V | PolType::VBar | PolType::Scalar => {
3386                self.tensor.clone()
3387            }
3388        };
3389
3390        Polarization {
3391            tensor: rotated,
3392            pol_type: self.pol_type,
3393        }
3394    }
3395}
3396
3397#[cfg(test)]
3398mod tests {
3399
3400    use core::f64;
3401
3402    use eyre::Context;
3403    use schemars::schema_for;
3404
3405    use crate::utils::F;
3406
3407    use super::*;
3408    use crate::utils::ApproxEq;
3409
3410    #[test]
3411    fn polarization() {
3412        let mom = FourMomentum::from_args(F(1.), F(1.), F(0.), F(0.));
3413
3414        let pol: Polarization<F<f64>> = Polarization::lorentz(mom.pol_one());
3415        println!("{}", pol)
3416
3417        // let pol2 = pol.clone();
3418
3419        // print!("{}", pol.add_fallible(&pol2).unwrap());
3420
3421        // println!("pol_one: {:?}", pol);
3422
3423        // let structure = pol.tensor.structure.clone();
3424
3425        // println!("{}", structure.flat_index([2]).unwrap());
3426
3427        // pol.tensor
3428        //     .iter_flat()
3429        //     .for_each(|(i, d)| println!("{}{}", i, d));
3430    }
3431
3432    #[test]
3433    fn serialization() {
3434        #[derive(Debug, Serialize, Deserialize, PartialEq)]
3435        struct HelicityToml {
3436            helicities: Vec<Helicity>,
3437        }
3438
3439        let hels = vec![
3440            Helicity::PLUS,
3441            Helicity::MINUS,
3442            Helicity::ZERO,
3443            Helicity::Summed,
3444            Helicity::SummedAveraged,
3445        ];
3446        let serialized = serde_json::to_string(&hels).unwrap();
3447        assert_eq!(serialized, r#"[1,-1,0,"summed","summed_averaged"]"#);
3448
3449        let deserialized: Vec<Helicity> =
3450            serde_json::from_str(r#"[1,"minus","0","summed","summed-averaged"]"#).unwrap();
3451        assert_eq!(deserialized, hels);
3452
3453        let toml_value = toml::from_str::<HelicityToml>(
3454            r#"
3455helicities = [1, "minus", "zero", "summed", "summed_averaged"]
3456"#,
3457        )
3458        .unwrap();
3459        assert_eq!(toml_value.helicities, hels);
3460
3461        let schema = serde_json::to_value(schema_for!(Helicity)).unwrap();
3462        let allowed_strings = &schema["enum"];
3463        assert!(allowed_strings.is_null());
3464        assert!(
3465            schema["oneOf"][0]["enum"]
3466                .as_array()
3467                .unwrap()
3468                .contains(&(-1).into())
3469        );
3470        assert!(
3471            schema["oneOf"][1]["enum"]
3472                .as_array()
3473                .unwrap()
3474                .contains(&"summed_averaged".into())
3475        );
3476    }
3477
3478    #[test]
3479    fn eps() {
3480        let mom = FourMomentum::from_args(
3481            F(156.2565490076973),
3482            F(-108.59017233120495),
3483            F(-100.2097685717689),
3484            F(50.81619686346704),
3485        );
3486
3487        let pol = mom.eps_pol(SignOrZero::Plus);
3488        println!("{}", pol);
3489
3490        let mom = FourMomentum::from_args(
3491            F(441.7831721921727),
3492            F(237.7632083614734),
3493            F(368.76330416225863),
3494            F(-51.523329523343534),
3495        );
3496
3497        let pol = mom.eps_pol(SignOrZero::Plus);
3498        println!("{}", pol);
3499
3500        let mom = FourMomentum::from_args(F(485.0355), F(0.), F(0.), F(485.0355));
3501
3502        let pol = mom.eps_pol(SignOrZero::Plus);
3503
3504        println!("{}", pol);
3505
3506        println!("pt{}", mom.pt());
3507
3508        let mom = FourMomentum::from_args(F(485.0355), F(-107.6044), F(-431.5174), F(193.5805));
3509
3510        let pol = mom.eps_pol(SignOrZero::Plus).bar();
3511
3512        // Helicity=           1           1           1           1
3513        //  W(*,1)=               (5.4707403520912967,0.0000000000000000)               (0.0000000000000000,0.0000000000000000)               (31.622776601683793,0.0000000000000000)               (0.0000000000000000,0.0000000000000000)
3514        //  W(*,2)=               (0.0000000000000000,0.0000000000000000)             (-0.70710678118654757,0.0000000000000000)              (0.0000000000000000,0.70710678118654757)               (0.0000000000000000,0.0000000000000000)
3515        //  W(*,3)=               (17.333409072341226,0.0000000000000000)              (6.3994499859138338,-25.663202641304650)               (2.9986797695150327,0.0000000000000000)              (1.1071048475630934,-4.4397340569457056)
3516        //  W(*,4)=               (0.0000000000000000,0.0000000000000000)        (6.82818793416064135E-002,0.68609707126238750)            (0.27382536157480858,-0.17108713804717862)              (0.64834964048112464,0.0000000000000000)
3517
3518        println!("{}", pol);
3519        println!("pt{}", mom.pt());
3520    }
3521
3522    #[test]
3523    fn spinors_degenerate() {
3524        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3525
3526        assert_eq!(
3527            [Complex::new_zero(), Complex::new_re(F(-1.))],
3528            mom.xi(Sign::Positive)
3529        );
3530
3531        assert_eq!(
3532            [Complex::new_re(F(1.)), Complex::new_re(F(0.))],
3533            mom.xi(Sign::Negative)
3534        );
3535
3536        let u_p = mom.u(Sign::Positive);
3537        let u_p_target: Polarization<Complex<F<_>>> =
3538            Polarization::bispinor_u([F(0.), F(0.), F(0.), F(-2.)]).cast();
3539
3540        u_p.approx_eq_res(&u_p_target, &F(0.001))
3541            .wrap_err("u+((2,0,0,-2)) does not match target: (0,0,0,-2)")
3542            .unwrap();
3543
3544        let mut u_p_bar_target: Polarization<Complex<F<_>>> =
3545            Polarization::bispinor_u([F(0.), F(-2.), F(0.), F(0.)]).cast();
3546
3547        u_p_bar_target.pol_type = PolType::UBar;
3548
3549        u_p.bar()
3550            .approx_eq_res(&u_p_bar_target, &F(0.001))
3551            .wrap_err("u+bar((2,0,0,-2)) does not match target: (0,-2,0,0)")
3552            .unwrap();
3553
3554        let u_m = mom.u(Sign::Negative);
3555        let u_m_target: Polarization<Complex<F<_>>> =
3556            Polarization::bispinor_u([F(2.), F(0.), F(0.), F(0.)]).cast();
3557
3558        u_m.approx_eq_res(&u_m_target, &F(0.001))
3559            .wrap_err("u-((2,0,0,-2)) does not match target: (2,0,0,0)")
3560            .unwrap();
3561
3562        let mut u_m_bar_target: Polarization<Complex<F<_>>> =
3563            Polarization::bispinor_u([F(0.), F(0.), F(2.), F(0.)]).cast();
3564
3565        u_m_bar_target.pol_type = PolType::UBar;
3566
3567        u_m.bar()
3568            .approx_eq_res(&u_m_bar_target, &F(0.001))
3569            .wrap_err("u-bar((2,0,0,-2)) does not match target: (0,0,2,0)")
3570            .unwrap();
3571
3572        let v_p = mom.v(Sign::Positive);
3573        let v_p_target: Polarization<Complex<F<_>>> =
3574            Polarization::bispinor_v([F(-2.), F(0.), F(0.), F(0.)]).cast();
3575
3576        v_p.approx_eq_res(&v_p_target, &F(0.001))
3577            .wrap_err("v+((2,0,0,-2)) does not match target: (-2,0,0,0)")
3578            .unwrap();
3579
3580        let mut v_p_bar_target: Polarization<Complex<F<_>>> =
3581            Polarization::bispinor_v([F(0.), F(0.), F(-2.), F(0.)]).cast();
3582
3583        v_p_bar_target.pol_type = PolType::VBar;
3584
3585        v_p.bar()
3586            .approx_eq_res(&v_p_bar_target, &F(0.001))
3587            .wrap_err("v+bar((2,0,0,-2)) does not match target: (0,0,-2,0)")
3588            .unwrap();
3589
3590        let v_m = mom.v(Sign::Negative);
3591        let v_m_target: Polarization<Complex<F<_>>> =
3592            Polarization::bispinor_v([F(0.), F(0.), F(0.), F(2.)]).cast();
3593
3594        v_m.approx_eq_res(&v_m_target, &F(0.001))
3595            .wrap_err("v-((2,0,0,-2)) does not match target: (0,0,0,2)")
3596            .unwrap();
3597
3598        let mut v_m_bar_target: Polarization<Complex<F<_>>> =
3599            Polarization::bispinor_v([F(0.), F(2.), F(0.), F(0.)]).cast();
3600
3601        v_m_bar_target.pol_type = PolType::VBar;
3602
3603        v_m.bar()
3604            .approx_eq_res(&v_m_bar_target, &F(0.001))
3605            .wrap_err("v-bar((2,0,0,-2)) does not match target: (0,2,0,0)")
3606            .unwrap();
3607    }
3608
3609    #[test]
3610    fn spinors() {
3611        let mom = FourMomentum::from_args(F(4.), F(0.), F(4.), F(0.));
3612
3613        assert!(
3614            Complex::approx_eq_slice(
3615                &[
3616                    Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3617                    Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3618                ],
3619                &mom.xi(Sign::Positive),
3620                &F(0.001),
3621            ),
3622            "xi+((4,0,4,0)) does not match target: (1/sqrt(2),i/sqrt(2))"
3623        );
3624
3625        assert!(
3626            Complex::approx_eq_slice(
3627                &[
3628                    Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3629                    Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3630                ],
3631                &mom.xi(Sign::Negative),
3632                &F(0.001),
3633            ),
3634            "xi-((4,0,4,0)) does not match target: (i/sqrt(2),1/sqrt(2))"
3635        );
3636
3637        let zero: Complex<_> = F(0.).into();
3638        let u_p = mom.u(Sign::Positive);
3639        let u_p_target: Polarization<Complex<F<_>>> =
3640            Polarization::bispinor_u([zero, zero, Complex::new_re(F(2.)), Complex::new_im(F(2.))])
3641                .cast();
3642
3643        u_p.approx_eq_res(&u_p_target, &F(0.001))
3644            .wrap_err("u+((4,0,4,0)) does not match target: (0,0,2,i2)")
3645            .unwrap();
3646
3647        let mut u_p_bar_target: Polarization<Complex<F<_>>> =
3648            Polarization::bispinor_u([Complex::new_re(F(2.)), Complex::new_im(-F(2.)), zero, zero])
3649                .cast();
3650
3651        u_p_bar_target.pol_type = PolType::UBar;
3652
3653        u_p.bar()
3654            .approx_eq_res(&u_p_bar_target, &F(0.001))
3655            .wrap_err("u+bar((4,0,4,0)) does not match target: (2,-2i,0,0)")
3656            .unwrap();
3657
3658        let u_m = mom.u(Sign::Negative);
3659        let u_m_target: Polarization<Complex<F<_>>> =
3660            Polarization::bispinor_u([Complex::new_im(F(2.)), Complex::new_re(F(2.)), zero, zero])
3661                .cast();
3662
3663        u_m.approx_eq_res(&u_m_target, &F(0.001))
3664            .wrap_err("u-((4,0,4,0)) does not match target: (2i,2,0,0)")
3665            .unwrap();
3666
3667        let mut u_m_bar_target: Polarization<Complex<F<_>>> =
3668            Polarization::bispinor_u([zero, zero, Complex::new_im(-F(2.)), Complex::new_re(F(2.))])
3669                .cast();
3670
3671        u_m_bar_target.pol_type = PolType::UBar;
3672
3673        u_m.bar()
3674            .approx_eq_res(&u_m_bar_target, &F(0.001))
3675            .wrap_err("u-bar((4,0,4,0)) does not match target: (0,0,-2i,2)")
3676            .unwrap();
3677
3678        let v_p = mom.v(Sign::Positive);
3679        let v_p_target: Polarization<Complex<F<_>>> = Polarization::bispinor_v([
3680            Complex::new_im(-F(2.)),
3681            Complex::new_re(-F(2.)),
3682            zero,
3683            zero,
3684        ])
3685        .cast();
3686
3687        v_p.approx_eq_res(&v_p_target, &F(0.001))
3688            .wrap_err("v+((4,0,4,0)) does not match target: (-2i,-2,0,0)")
3689            .unwrap();
3690
3691        let mut v_p_bar_target: Polarization<Complex<F<_>>> =
3692            Polarization::bispinor_v([zero, zero, Complex::new_im(F(2.)), Complex::new_re(F(-2.))])
3693                .cast();
3694
3695        v_p_bar_target.pol_type = PolType::VBar;
3696
3697        v_p.bar()
3698            .approx_eq_res(&v_p_bar_target, &F(0.001))
3699            .wrap_err("v+bar((4,0,4,0)) does not match target: (0,0,2i,-2)")
3700            .unwrap();
3701
3702        let v_m = mom.v(Sign::Negative);
3703        let v_m_target: Polarization<Complex<F<_>>> = Polarization::bispinor_v([
3704            zero,
3705            zero,
3706            Complex::new_re(-F(2.)),
3707            Complex::new_im(-F(2.)),
3708        ])
3709        .cast();
3710
3711        v_m.approx_eq_res(&v_m_target, &F(0.001))
3712            .wrap_err("v-((4,0,4,0)) does not match target: (0,0,-2,-2i)")
3713            .unwrap();
3714
3715        let mut v_m_bar_target: Polarization<Complex<F<_>>> =
3716            Polarization::bispinor_v([Complex::new_re(-F(2.)), Complex::new_im(F(2.)), zero, zero])
3717                .cast();
3718
3719        v_m_bar_target.pol_type = PolType::VBar;
3720
3721        v_m.bar()
3722            .approx_eq_res(&v_m_bar_target, &F(0.001))
3723            .wrap_err("v-bar((4,0,4,0)) does not match target: (-2,2i,0,0)")
3724            .unwrap();
3725    }
3726
3727    #[test]
3728    fn vectors_degenerate() {
3729        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3730
3731        assert!(
3732            F::approx_eq_slice(&mom.pol_one(), &[0., 1., 0., 0.].map(F), &F(0.001)),
3733            "pol_one((2,0,0,-2)) does not match target: (0,1,0,0)"
3734        );
3735
3736        assert!(
3737            F::approx_eq_slice(&mom.pol_two(), &[0., 0., -1., 0.].map(F), &F(0.001)),
3738            "pol_two((2,0,0,-2)) does not match target: (0,0,-1,0)"
3739        );
3740
3741        let e_p = mom.eps_pol(SignOrZero::Plus);
3742
3743        let zero = F(0.).into();
3744        let e_p_target = Polarization::lorentz([
3745            zero,
3746            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3747            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3748            zero,
3749        ]);
3750
3751        let mut e_p_bar_target = Polarization::lorentz([
3752            zero,
3753            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3754            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3755            zero,
3756        ]);
3757
3758        e_p_bar_target.pol_type = PolType::EpsilonBar;
3759
3760        e_p.approx_eq_res(&e_p_target, &F(0.0001))
3761            .wrap_err("e+((2,0,0,-2)) does not match target: (0,-1/sqrt(2),i/sqrt(2),0)")
3762            .unwrap();
3763
3764        e_p.bar()
3765            .approx_eq_res(&e_p_bar_target, &F(0.0001))
3766            .wrap_err("e+bar((2,0,0,-2)) does not match target: (0,-1/sqrt(2),-i/sqrt(2),0)")
3767            .unwrap();
3768
3769        let e_m = mom.eps_pol(SignOrZero::Minus);
3770
3771        let e_m_target = Polarization::lorentz([
3772            zero,
3773            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3774            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3775            zero,
3776        ]);
3777
3778        let mut e_m_bar_target = Polarization::lorentz([
3779            zero,
3780            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3781            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3782            zero,
3783        ]);
3784
3785        e_m_bar_target.pol_type = PolType::EpsilonBar;
3786
3787        e_m.approx_eq_res(&e_m_target, &F(0.0001))
3788            .wrap_err("e-((2,0,0,-2)) does not match target: (0,1/sqrt(2),i/sqrt(2),0)")
3789            .unwrap();
3790
3791        e_m.bar()
3792            .approx_eq_res(&e_m_bar_target, &F(0.0001))
3793            .wrap_err("e-bar((2,0,0,-2)) does not match target: (0,1/sqrt(2),-i/sqrt(2),0)")
3794            .unwrap();
3795    }
3796
3797    #[test]
3798    fn vectors() {
3799        let mom = FourMomentum::from_args(F(2.), F(0.), F(0.), F(-2.));
3800
3801        assert!(
3802            F::approx_eq_slice(&mom.pol_one(), &[0., 1., 0., 0.].map(F), &F(0.001)),
3803            "pol_one((2,0,0,-2)) does not match target: (0,1,0,0)"
3804        );
3805
3806        assert!(
3807            F::approx_eq_slice(&mom.pol_two(), &[0., 0., -1., 0.].map(F), &F(0.001)),
3808            "pol_two((2,0,0,-2)) does not match target: (0,0,-1,0)"
3809        );
3810
3811        let e_p = mom.eps_pol(SignOrZero::Plus);
3812
3813        let zero = F(0.).into();
3814        let e_p_target = Polarization::lorentz([
3815            zero,
3816            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3817            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3818            zero,
3819        ]);
3820
3821        let mut e_p_bar_target = Polarization::lorentz([
3822            zero,
3823            Complex::new_re(F(-f64::consts::FRAC_1_SQRT_2)),
3824            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3825            zero,
3826        ]);
3827
3828        e_p_bar_target.pol_type = PolType::EpsilonBar;
3829
3830        e_p.approx_eq_res(&e_p_target, &F(0.0001))
3831            .wrap_err("e+((2,0,0,-2)) does not match target: (0,-1/sqrt(2),i/sqrt(2),0)")
3832            .unwrap();
3833
3834        e_p.bar()
3835            .approx_eq_res(&e_p_bar_target, &F(0.0001))
3836            .wrap_err("e+bar((2,0,0,-2)) does not match target: (0,-1/sqrt(2),-i/sqrt(2),0)")
3837            .unwrap();
3838
3839        let e_m = mom.eps_pol(SignOrZero::Minus);
3840
3841        let e_m_target = Polarization::lorentz([
3842            zero,
3843            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3844            Complex::new_im(F(f64::consts::FRAC_1_SQRT_2)),
3845            zero,
3846        ]);
3847
3848        let mut e_m_bar_target = Polarization::lorentz([
3849            zero,
3850            Complex::new_re(F(f64::consts::FRAC_1_SQRT_2)),
3851            Complex::new_im(F(-f64::consts::FRAC_1_SQRT_2)),
3852            zero,
3853        ]);
3854
3855        e_m_bar_target.pol_type = PolType::EpsilonBar;
3856
3857        e_m.approx_eq_res(&e_m_target, &F(0.0001))
3858            .wrap_err("e-((2,0,0,-2)) does not match target: (0,1/sqrt(2),i/sqrt(2),0)")
3859            .unwrap();
3860
3861        e_m.bar()
3862            .approx_eq_res(&e_m_bar_target, &F(0.0001))
3863            .wrap_err("e-bar((2,0,0,-2)) does not match target: (0,1/sqrt(2),-i/sqrt(2),0)")
3864            .unwrap();
3865    }
3866
3867    #[test]
3868    fn rotations() {
3869        let mom = FourMomentum::from_args(F(2.), F(3.), F(1.), F(2.));
3870
3871        let momc: [F<f64>; 4] = mom.into();
3872        let e = Polarization::lorentz(momc.map(Complex::new_re));
3873        let u = mom.u(Sign::Negative);
3874
3875        let rot: Rotation = RotationMethod::EulerAngles(std::f64::consts::PI / 2., 0., 0.).into();
3876        let rotx = RotationMethod::Pi2X.into();
3877
3878        let rotye: Rotation = RotationMethod::EulerAngles(0., std::f64::consts::PI / 2., 0.).into();
3879        let roty = RotationMethod::Pi2Y.into();
3880
3881        let rotze: Rotation = RotationMethod::EulerAngles(0., 0., std::f64::consts::PI / 2.).into();
3882        let rotz = RotationMethod::Pi2Z.into();
3883
3884        let rotid = RotationMethod::Identity.into();
3885        // println!(
3886        //     "{}",
3887        //     rot.bispinor_tensor(
3888        //         Bispinor::new_slot_selfless(4, 2),
3889        //         Bispinor::new_slot_selfless(4, 1),
3890        //     )
3891        // );
3892
3893        let mom_rot = mom.rotate(&rot);
3894        let mom_id = mom.rotate(&rotid);
3895        let other_mom_rot = mom.rotate(&rotx);
3896        let mom_rotye = mom.rotate(&rotye);
3897        let mom_roty = mom.rotate(&roty);
3898        let mom_rotze = mom.rotate(&rotze);
3899        let mom_rotz = mom.rotate(&rotz);
3900
3901        println!("id:\t{}", mom_id);
3902        mom_id
3903            .approx_eq_res(&mom, &F(0.001))
3904            .wrap_err("mom is not identical to mom transformed with identity")
3905            .unwrap();
3906        println!("orig:\t{}", mom);
3907
3908        println!("rotxe:\t{}", mom_rot);
3909        println!("pi2x:\t{}", other_mom_rot);
3910        mom_rot.approx_eq_res(&other_mom_rot, &F(0.001)).unwrap();
3911
3912        println!("rotye:\t{}", mom_rotye);
3913        println!("pi2y:\t{}", mom_roty);
3914
3915        mom_rotye.approx_eq_res(&mom_roty, &F(0.001)).unwrap();
3916        println!("rotze:\t{}", mom_rotze);
3917        println!("pi2z:\t{}", mom_rotz);
3918        mom_rotze.approx_eq_res(&mom_rotz, &F(0.001)).unwrap();
3919
3920        let e_rot = e.rotate(&rot);
3921        println!("rotxe:{}", e_rot);
3922        mom_rot.approx_eq_res(&e_rot, &F(0.001)).unwrap();
3923
3924        let e_id = e.rotate(&rotid);
3925        mom_id.approx_eq_res(&e_id, &F(0.001)).unwrap();
3926        let e_rotye = e.rotate(&rotye);
3927
3928        println!("rotye:{}", e_rotye);
3929        mom_rotye.approx_eq_res(&e_rotye, &F(0.001)).unwrap();
3930        let e_roty = e.rotate(&roty);
3931        mom_roty.approx_eq_res(&e_roty, &F(0.001)).unwrap();
3932        let e_rotze = e.rotate(&rotze);
3933        mom_rotze.approx_eq_res(&e_rotze, &F(0.001)).unwrap();
3934        let e_rotz = e.rotate(&rotz);
3935        mom_rotz.approx_eq_res(&e_rotz, &F(0.001)).unwrap();
3936        let u_rot = u.rotate(&rot);
3937        let u_id = u.rotate(&rotid);
3938
3939        let other_e_rot = e.rotate(&rotx);
3940
3941        let other_u_rot = u.rotate(&rotx);
3942
3943        println!("id:{}", e_id);
3944        println!("orig:{}", e);
3945        println!("euler:{}", e_rot);
3946        println!("Pi2X:{}", other_e_rot);
3947        println!("rotye:{}", e_rotye);
3948        println!("pi2y:{}", e_roty);
3949        println!("rotze:{}", e_rotze);
3950        println!("pi2z:{}", e_rotz);
3951
3952        println!("id:{}", u_id);
3953        println!("orig:{}", u);
3954        println!("euler:{}", u_rot);
3955        println!("Pi2X {}", other_u_rot);
3956    }
3957
3958    // #[test]
3959    // fn omega() {
3960    //     let mu = PhysReps::new_slot(Minkowski {}.into(), 4, 0);
3961
3962    //     let nu = PhysReps::new_slot(Minkowski {}.into(), 4, 1);
3963
3964    //     let mud = mu.dual();
3965
3966    //     let nud = nu.dual();
3967
3968    //     let i = PhysReps::new_slot(GR.bis.into(), 4, 2);
3969
3970    //     let j = PhysReps::new_slot(GR.bis.into(), 4, 3);
3971
3972    //     let k = PhysReps::new_slot(GR.bis.into(), 4, 4);
3973
3974    //     let zero = Atom::num(0);
3975    //     let theta_x = parse!("theta_x").unwrap();
3976    //     let theta_y = parse!("theta_y").unwrap();
3977    //     let theta_z = parse!("theta_z").unwrap();
3978
3979    //     let omega = DenseTensor::from_storage_data(
3980    //         vec![
3981    //             zero.clone(),
3982    //             zero.clone(),
3983    //             zero.clone(),
3984    //             zero.clone(),
3985    //             //
3986    //             zero.clone(),
3987    //             zero.clone(),
3988    //             theta_z.clone(),
3989    //             -theta_y.clone(),
3990    //             //
3991    //             zero.clone(),
3992    //             -theta_z.clone(),
3993    //             zero.clone(),
3994    //             theta_x.clone(),
3995    //             //
3996    //             zero.clone(),
3997    //             theta_y.clone(),
3998    //             -theta_x.clone(),
3999    //             zero.clone(),
4000    //         ],
4001    //         OrderedStructure::from_iter([mu, nu]),
4002    //     )
4003    //     .unwrap();
4004
4005    //     println!("{}", omega);
4006
4007    //     let gammamujk: SparseTensor<Complex<f64>> =
4008    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([mud, j, k]));
4009
4010    //     let gammamukj: SparseTensor<Complex<f64>> =
4011    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([mud, k, i]));
4012
4013    //     let gammanuki: SparseTensor<Complex<f64>> =
4014    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([nud, k, i]));
4015
4016    //     let gammanujk: SparseTensor<Complex<f64>> =
4017    //         ufo::gamma_data_weyl(OrderedStructure::from_iter([nud, j, k]));
4018
4019    //     let sigma = (gammamujk
4020    //         .contract(&gammanuki)
4021    //         .unwrap()
4022    //         .sub_fallible(&gammanujk.contract(&gammamukj).unwrap())
4023    //         .unwrap())
4024    //     .scalar_mul(&Complex::new_im(0.25))
4025    //     .unwrap();
4026
4027    //     println!("{}", sigma);
4028
4029    //     println!("{}", omega.contract(&sigma).unwrap());
4030    // }
4031}