Skip to main content

gammalooprs/numerator/
graph.rs

1use linnet::half_edge::{
2    HedgeGraph,
3    involution::{EdgeData, Flow, Orientation},
4    subgraph::{SuBitGraph, SubSetLike},
5};
6use spenso::{
7    iterators::IteratableTensor,
8    network::parsing::ShadowedStructure,
9    shadowing::Concretize,
10    structure::{PermutedStructure, TensorStructure},
11    tensors::parametric::ParamTensor,
12};
13use symbolica::atom::{Atom, AtomOrView, FunctionBuilder, Symbol};
14
15use crate::{
16    graph::{Edge, Graph, HedgeData, edge::ParseEdge},
17    model::ArcParticle,
18    utils::GS,
19};
20
21use super::aind::Aind;
22
23pub trait HedgePolarizationData {
24    fn polarization(&self, builder: FunctionBuilder) -> Atom;
25
26    fn edge_spin_slots<'a>(
27        &'a self,
28    ) -> impl Iterator<Item = spenso::structure::representation::LibrarySlot<Aind>> + 'a;
29}
30
31impl HedgePolarizationData for HedgeData {
32    fn polarization(&self, builder: FunctionBuilder) -> Atom {
33        HedgeData::polarization(self, builder)
34    }
35
36    fn edge_spin_slots<'a>(
37        &'a self,
38    ) -> impl Iterator<Item = spenso::structure::representation::LibrarySlot<Aind>> + 'a {
39        HedgeData::edge_spin_slots(self)
40    }
41}
42
43pub trait GeneratePolarizations {
44    /// Returns the polarizations of the given subgraph. One polarization per half-edge.
45    /// If you only want those that are dangling first get the crown of the subgraph.
46    fn generate_polarizations_of<S: SubSetLike>(&self, subgraph: &S) -> Atom;
47    fn generate_polarization_parameters_of<S: SubSetLike>(&self, subgraph: &S) -> Vec<Atom>;
48    // fn generate_polarizations(&self) -> Atom;
49    // fn generate_polarization_params(&self) -> Vec<Atom>;
50}
51
52impl Graph {
53    pub fn generate_polarizations(&self) -> Atom {
54        self.generate_polarizations_of(&self.underlying.external_filter::<SuBitGraph>())
55    }
56    pub fn generate_polarization_params(&self) -> Vec<Atom> {
57        self.generate_polarization_parameters_of(&self.underlying.external_filter::<SuBitGraph>())
58    }
59}
60
61impl GeneratePolarizations for Graph {
62    fn generate_polarizations_of<S: SubSetLike>(&self, subgraph: &S) -> Atom {
63        self.underlying.generate_polarizations_of(subgraph)
64    }
65
66    fn generate_polarization_parameters_of<S: SubSetLike>(&self, subgraph: &S) -> Vec<Atom> {
67        self.underlying
68            .generate_polarization_parameters_of(subgraph)
69    }
70
71    // fn generate_polarizations(&self) -> Atom {
72    //     self.generate_polarizations_of(&self.underlying.external_filter())
73    // }
74    // fn generate_polarization_params(&self) -> Vec<Atom> {
75    //     self.generate_polarization_parameters_of(&self.underlying.external_filter())
76    // }
77
78    // pub(crate) fn polarizations_values(&self) -> Vec<Atom> {
79    //     self.polarizations_of(&self.underlying.external_filter())
80    // }
81}
82
83impl<V, E> GeneratePolarizations for HedgeGraph<E, V, HedgeData>
84where
85    for<'a> EdgeData<&'a E>: ReversibleEdge,
86{
87    fn generate_polarizations_of<S: SubSetLike>(&self, subgraph: &S) -> Atom {
88        let mut pols = Atom::num(1);
89
90        for h in subgraph.included_iter() {
91            let eid = self[&h];
92            pols *= self.get_edge_data_full(h).polarization(
93                &[Atom::num(eid.0)],
94                &self[h],
95                self.flow(h),
96            );
97        }
98
99        pols
100    }
101
102    fn generate_polarization_parameters_of<S: SubSetLike>(&self, subgraph: &S) -> Vec<Atom> {
103        let mut pols = Vec::new();
104
105        for h in subgraph.included_iter() {
106            let eid = self[&h];
107
108            let Some(pol) = self.get_edge_data_full(h).polarization_structure(
109                &[Atom::num(eid.0)],
110                &self[h],
111                self.flow(h),
112            ) else {
113                continue;
114            };
115
116            let concrete: ParamTensor<_> = pol
117                .structure
118                .to_shell()
119                .concretize(Some(pol.index_permutation));
120
121            for (_, i) in concrete.iter_flat() {
122                pols.push(i.to_owned());
123            }
124        }
125
126        pols
127    }
128
129    // fn generate_polarizations(&self) -> Atom {
130    //     self.generate_polarizations_of(&self.external_filter())
131    // }
132    // fn generate_polarization_params(&self) -> Vec<Atom> {
133    //     self.generate_polarization_parameters_of(&self.external_filter())
134    // }
135
136    // pub(crate) fn polarizations_values(&self) -> Vec<Atom> {
137    //     self.polarizations_of(&self.underlying.external_filter())
138    // }
139}
140
141pub trait ReversibleEdge {
142    fn pdg(&self) -> isize;
143
144    fn pol_symbol(&self, flow: Flow) -> Option<Symbol>;
145
146    fn polarization<'a, T, H>(&self, add_args: &'a [T], hedge_data: &H, flow: Flow) -> Atom
147    where
148        &'a T: Into<AtomOrView<'a>>,
149        H: HedgePolarizationData;
150
151    fn polarization_structure<'a, T, H>(
152        &self,
153        add_args: &'a [T],
154        hedge_data: &H,
155        flow: Flow,
156    ) -> Option<PermutedStructure<ShadowedStructure<Aind>>>
157    where
158        &'a T: Into<AtomOrView<'a>>,
159        H: HedgePolarizationData;
160}
161
162impl ReversibleEdge for EdgeData<&Edge> {
163    fn pdg(&self) -> isize {
164        if let Some(p) = self.data.particle() {
165            match self.orientation {
166                Orientation::Default | Orientation::Undirected => p.pdg_code,
167                Orientation::Reversed => -p.pdg_code,
168            }
169        } else {
170            0
171        }
172    }
173
174    fn pol_symbol(&self, flow: Flow) -> Option<Symbol> {
175        if let Some(p) = self.data.particle() {
176            // println!("{}{:?}", p.spin, flow);
177            match (p.spin, flow) {
178                (2, Flow::Sink) => {
179                    if self.pdg() > 0 {
180                        Some(GS.u)
181                    } else {
182                        Some(GS.vbar)
183                    }
184                }
185                (2, Flow::Source) => {
186                    if self.pdg() > 0 {
187                        Some(GS.ubar)
188                    } else {
189                        Some(GS.v)
190                    }
191                }
192                (3, Flow::Sink) => Some(GS.epsilon),
193                (3, Flow::Source) => Some(GS.epsilonbar),
194                _ => None,
195            }
196        } else {
197            None
198        }
199    }
200
201    fn polarization<'a, T, H>(&self, add_args: &'a [T], hedge_data: &H, flow: Flow) -> Atom
202    where
203        &'a T: Into<AtomOrView<'a>>,
204        H: HedgePolarizationData,
205    {
206        if let Some(name) = self.pol_symbol(flow) {
207            hedge_data.polarization(FunctionBuilder::new(name).add_args(add_args))
208        } else {
209            Atom::num(1)
210        }
211    }
212
213    fn polarization_structure<'a, T, H>(
214        &self,
215        add_args: &'a [T],
216        hedge_data: &H,
217        flow: Flow,
218    ) -> Option<PermutedStructure<ShadowedStructure<Aind>>>
219    where
220        &'a T: Into<AtomOrView<'a>>,
221        H: HedgePolarizationData,
222    {
223        self.pol_symbol(flow).map(|name| {
224            ShadowedStructure::from_iter(
225                hedge_data.edge_spin_slots(),
226                name,
227                Some(add_args.iter().map(|arg| arg.into().into_owned()).collect()),
228            )
229        })
230    }
231}
232
233impl ReversibleEdge for EdgeData<&ParseEdge> {
234    fn pdg(&self) -> isize {
235        if let Some(p) = self.data.particle.particle() {
236            match self.orientation {
237                Orientation::Default | Orientation::Undirected => p.pdg_code,
238                Orientation::Reversed => p.pdg_code,
239            }
240        } else {
241            0
242        }
243    }
244
245    fn pol_symbol(&self, flow: Flow) -> Option<Symbol> {
246        if let Some(p) = self.data.particle.particle() {
247            // println!(
248            //     "pdg:{},orientation_aware:{},spin:{},flow:{:?}",
249            //     p.pdg_code,
250            //     self.pdg(),
251            //     p.spin,
252            //     flow
253            // );
254            match (p.spin, flow) {
255                (2, Flow::Sink) => {
256                    if self.pdg() > 0 {
257                        // println!("u");
258                        Some(GS.u)
259                    } else {
260                        // println!("vbar");
261                        Some(GS.vbar)
262                    }
263                }
264                (2, Flow::Source) => {
265                    if self.pdg() > 0 {
266                        // println!("ubar");
267                        Some(GS.ubar)
268                    } else {
269                        // println!("v");
270                        Some(GS.v)
271                    }
272                }
273                (3, Flow::Sink) => Some(GS.epsilon),
274                (3, Flow::Source) => Some(GS.epsilonbar),
275                _ => None,
276            }
277        } else {
278            None
279        }
280    }
281
282    fn polarization<'a, T, H>(&self, add_args: &'a [T], hedge_data: &H, flow: Flow) -> Atom
283    where
284        &'a T: Into<AtomOrView<'a>>,
285        H: HedgePolarizationData,
286    {
287        if let Some(name) = self.pol_symbol(flow) {
288            hedge_data.polarization(FunctionBuilder::new(name).add_args(add_args))
289        } else {
290            Atom::num(1)
291        }
292    }
293
294    fn polarization_structure<'a, T, H>(
295        &self,
296        add_args: &'a [T],
297        hedge_data: &H,
298        flow: Flow,
299    ) -> Option<PermutedStructure<ShadowedStructure<Aind>>>
300    where
301        &'a T: Into<AtomOrView<'a>>,
302        H: HedgePolarizationData,
303    {
304        self.pol_symbol(flow).map(|name| {
305            ShadowedStructure::from_iter(
306                hedge_data.edge_spin_slots(),
307                name,
308                Some(add_args.iter().map(|arg| arg.into().into_owned()).collect()),
309            )
310        })
311    }
312}
313
314impl ReversibleEdge for EdgeData<(usize, isize)> {
315    fn pdg(&self) -> isize {
316        match self.orientation {
317            Orientation::Default | Orientation::Undirected => self.data.1,
318            Orientation::Reversed => -self.data.1,
319        }
320    }
321
322    fn pol_symbol(&self, flow: Flow) -> Option<Symbol> {
323        match (self.data.0, flow) {
324            (2, Flow::Sink) => {
325                if self.pdg() > 0 {
326                    Some(GS.u)
327                } else {
328                    Some(GS.vbar)
329                }
330            }
331            (2, Flow::Source) => {
332                if self.pdg() > 0 {
333                    Some(GS.ubar)
334                } else {
335                    Some(GS.v)
336                }
337            }
338            (3, Flow::Sink) => Some(GS.epsilon),
339            (3, Flow::Source) => Some(GS.epsilonbar),
340            _ => None,
341        }
342    }
343
344    fn polarization<'a, T, H>(&self, add_args: &'a [T], hedge_data: &H, flow: Flow) -> Atom
345    where
346        &'a T: Into<AtomOrView<'a>>,
347        H: HedgePolarizationData,
348    {
349        if let Some(name) = self.pol_symbol(flow) {
350            hedge_data.polarization(FunctionBuilder::new(name).add_args(add_args))
351        } else {
352            Atom::num(1)
353        }
354    }
355
356    fn polarization_structure<'a, T, H>(
357        &self,
358        add_args: &'a [T],
359        hedge_data: &H,
360        flow: Flow,
361    ) -> Option<PermutedStructure<ShadowedStructure<Aind>>>
362    where
363        &'a T: Into<AtomOrView<'a>>,
364        H: HedgePolarizationData,
365    {
366        self.pol_symbol(flow).map(|name| {
367            ShadowedStructure::from_iter(
368                hedge_data.edge_spin_slots(),
369                name,
370                Some(add_args.iter().map(|arg| arg.into().into_owned()).collect()),
371            )
372        })
373    }
374}
375
376impl ReversibleEdge for EdgeData<ArcParticle> {
377    fn pdg(&self) -> isize {
378        match self.orientation {
379            Orientation::Default | Orientation::Undirected => self.data.pdg_code,
380            Orientation::Reversed => -self.data.pdg_code,
381        }
382    }
383
384    fn pol_symbol(&self, flow: Flow) -> Option<Symbol> {
385        match (self.data.spin, flow) {
386            (2, Flow::Sink) => {
387                if self.pdg() > 0 {
388                    Some(GS.u)
389                } else {
390                    Some(GS.vbar)
391                }
392            }
393            (2, Flow::Source) => {
394                if self.pdg() > 0 {
395                    Some(GS.ubar)
396                } else {
397                    Some(GS.v)
398                }
399            }
400            (3, Flow::Sink) => Some(GS.epsilon),
401            (3, Flow::Source) => Some(GS.epsilonbar),
402            _ => None,
403        }
404    }
405
406    fn polarization<'a, T, H>(&self, add_args: &'a [T], hedge_data: &H, flow: Flow) -> Atom
407    where
408        &'a T: Into<AtomOrView<'a>>,
409        H: HedgePolarizationData,
410    {
411        if let Some(name) = self.pol_symbol(flow) {
412            hedge_data.polarization(FunctionBuilder::new(name).add_args(add_args))
413        } else {
414            Atom::num(1)
415        }
416    }
417
418    fn polarization_structure<'a, T, H>(
419        &self,
420        add_args: &'a [T],
421        hedge_data: &H,
422        flow: Flow,
423    ) -> Option<PermutedStructure<ShadowedStructure<Aind>>>
424    where
425        &'a T: Into<AtomOrView<'a>>,
426        H: HedgePolarizationData,
427    {
428        self.pol_symbol(flow).map(|name| {
429            ShadowedStructure::from_iter(
430                hedge_data.edge_spin_slots(),
431                name,
432                Some(add_args.iter().map(|arg| arg.into().into_owned()).collect()),
433            )
434        })
435    }
436}
437
438#[cfg(test)]
439mod test {
440
441    // use env_logger::WriteStyle;
442    use idenso::{color::ColorSimplifier, dirac::GammaSimplifier, tensor::SymbolicNetParse};
443
444    use spenso::network::parsing::ParseSettings;
445    use symbolica::{
446        atom::{Atom, AtomCore},
447        parse_lit,
448    };
449
450    use crate::{
451        dot,
452        graph::{Graph, parse::IntoGraph},
453        initialisation::{initialise, test_initialise},
454        numerator::aind::Aind,
455        processes::{Amplitude, AmplitudeGraph, DotExportSettings},
456        settings::{
457            GlobalSettings, RuntimeSettings,
458            global::{GenerationSettings, OrientationPattern},
459            runtime::{LockedRuntimeSettings, kinematic::KinematicsSettings},
460        },
461        uv::UltravioletGraph,
462    };
463
464    #[test]
465    fn qqx_aaa_tree() {
466        test_initialise().unwrap();
467
468        let mut graph:AmplitudeGraph = dot!(digraph qqx_aaa_tree_1 {
469                    num="spenso::g(spenso::dind(spenso::cof(3, hedge(1))), spenso::cof(3, hedge(2)))/3"
470                    ext    [style=invis]
471                    ext -> v1:1 [particle="d" id=1];
472                    ext -> v3:2 [particle="d~" id=2];
473                    v1:3 -> ext [particle="a" id=3];
474                    v2:4 -> ext [particle="a" id=4];
475                    v3:0 -> ext [particle="a" id=0];
476                    v1 -> v2 [particle="d" id=5];
477                    v2 -> v3 [particle="d" id=6];
478        }).unwrap();
479
480        let vk = crate::utils::vakint().unwrap();
481
482        // let model = crate::utils::load_generic_model("sm");
483
484        graph.generate_cff(&OrientationPattern::default()).unwrap();
485        graph
486            .build_integrands(&GenerationSettings::default(), vk)
487            .unwrap();
488
489        println!("{}", graph.derived_data.all_mighty_integrand);
490    }
491
492    #[test]
493    fn simplify() {
494        test_initialise().unwrap();
495        let expr = parse_lit!(
496            -GC_1 * GC_11
497                ^ 2 * Q(4, mink(dim, gammalooprs::edge(4, 1)))
498                    * Q(5, mink(dim, gammalooprs::edge(5, 1)))
499                    * g(
500                        mink(dim, gammalooprs::hedge(16)),
501                        mink(dim, gammalooprs::hedge(17))
502                    )
503                    * g(
504                        coad(8, gammalooprs::hedge(16)),
505                        coad(8, gammalooprs::hedge(17))
506                    )
507                    * g(
508                        dind(cof(3, gammalooprs::hedge(4))),
509                        cof(3, gammalooprs::hedge(6))
510                    )
511                    * g(
512                        dind(cof(3, gammalooprs::hedge(5))),
513                        cof(3, gammalooprs::hedge(4))
514                    )
515                    * g(
516                        dind(cof(3, gammalooprs::hedge(6))),
517                        cof(3, gammalooprs::hedge(7))
518                    )
519                    * gamma(
520                        bis(4, gammalooprs::hedge(4)),
521                        bis(4, gammalooprs::hedge(6)),
522                        mink(dim, gammalooprs::hedge(8))
523                    )
524                    * gamma(
525                        bis(4, gammalooprs::hedge(7)),
526                        bis(4, gammalooprs::hedge(13)),
527                        mink(dim, gammalooprs::hedge(17))
528                    )
529                    * gamma(
530                        bis(4, gammalooprs::hedge(11)),
531                        bis(4, gammalooprs::hedge(5)),
532                        mink(dim, gammalooprs::hedge(16))
533                    )
534                    * gamma(
535                        bis(4, gammalooprs::hedge(5)),
536                        bis(4, gammalooprs::hedge(4)),
537                        mink(dim, gammalooprs::edge(4, 1))
538                    )
539                    * gamma(
540                        bis(4, gammalooprs::hedge(6)),
541                        bis(4, gammalooprs::hedge(7)),
542                        mink(dim, gammalooprs::edge(5, 1))
543                    )
544                    * t(
545                        coad(8, gammalooprs::hedge(16)),
546                        cof(3, gammalooprs::hedge(5)),
547                        dind(cof(3, gammalooprs::hedge(11)))
548                    )
549                    * t(
550                        coad(8, gammalooprs::hedge(17)),
551                        cof(3, gammalooprs::hedge(13)),
552                        dind(cof(3, gammalooprs::hedge(7)))
553                    ),
554            default_namespace = "spenso"
555        );
556
557        let net = expr
558            .parse_to_symbolic_net::<Aind>(&ParseSettings::default())
559            .unwrap();
560        println!("{}", net.dot_pretty());
561
562        let _ = expr.simplify_gamma();
563    }
564
565    mod failing {
566        use super::*;
567
568        #[test]
569        fn evaluate_pols() {
570            initialise().unwrap();
571            let model = crate::utils::load_generic_model("sm");
572
573            let graphs: Vec<Graph> = dot!(
574            digraph bxatobx{
575                graph [
576                    // polarizations="1"
577                    color_num="spenso::g(spenso::dind(spenso::cof(3,hedge(0))),spenso::cof(3,hedge(2)))"
578                ]
579                bla    [style=invis]
580                bla -> A:1   [particle=a id=0]
581                bla -> A:2    [particle="b~" id=2]
582                A:0  -> bla  [particle="b~" id=1]
583            })
584            .unwrap();
585
586            let mut amp: Amplitude = Amplitude::from_graph_list("name", graphs.clone()).unwrap();
587            let mut settings = RuntimeSettings::default();
588
589            for g in graphs {
590                println!("{}", g.dot_serialize(&DotExportSettings::default()));
591                settings.kinematics = KinematicsSettings::random(&g, 42);
592
593                // Amplitude::new(name)
594            }
595
596            let proc_set = GenerationSettings::default();
597            let thread_pool = rayon::ThreadPoolBuilder::new()
598                .num_threads(1)
599                .build()
600                .unwrap();
601
602            let default_runtime_settings = RuntimeSettings::default();
603            let locked_runtime_settings = LockedRuntimeSettings::from(&default_runtime_settings);
604            amp.preprocess(&model, &proc_set, &locked_runtime_settings, &thread_pool)
605                .unwrap();
606
607            amp.build_integrand(
608                &model,
609                "test_process",
610                &GlobalSettings::default(),
611                (&RuntimeSettings::default()).into(),
612                &thread_pool,
613            )
614            .unwrap();
615
616            // integrand.evaluate_sample(sample, wgt, iter, use_f128, max_eval);
617        }
618
619        #[test]
620        fn pols() {
621            initialise().unwrap();
622            let mut graphs: Vec<Graph> = dot!(
623                digraph bxatobx{
624                    graph [
625                        num="v(0,spenso::bis(4,hedge(0)))*vbar(2,spenso::bis(4,hedge(2)))*ϵ(1,spenso::mink(4,hedge(1)))"
626                    ]
627                    ext    [style=invis]
628                    ext -> A:1   [particle=a id=1]
629                    ext -> A:2    [dir=back particle="b~" id=2]
630                    A:0  -> ext  [dir=back particle="b~" id=0]
631                }
632
633                digraph batob{
634                    graph [
635                        num="ubar(0,spenso::bis(4,hedge(0)))*u(2,spenso::bis(4,hedge(2)))*ϵ(1,spenso::mink(4,hedge(1)))"
636                    ]
637                    ext    [style=invis]
638                    ext -> A:1   [particle=a id=1]
639                    ext -> A:2    [particle="b" id=2]
640                    A:0  -> ext  [particle="b" id=0]
641                }
642
643                digraph bbato{
644                    graph [
645                        num="vbar(0,spenso::bis(4,hedge(0)))*u(2,spenso::bis(4,hedge(2)))*ϵ(1,spenso::mink(4,hedge(1)))"
646                    ]
647                    ext    [style=invis]
648                    ext -> A:1   [particle=a id=1]
649                    ext -> A:2    [particle="b" id=2]
650                    ext -> A:0  [dir=back pdg=-5 id=0]
651                }
652
653                digraph bbato{
654                    graph [
655                        num="vbar(0,spenso::bis(4,hedge(0)))*u(2,spenso::bis(4,hedge(2)))*ϵbar(1,spenso::mink(4,hedge(1)))"
656                    ]
657                    ext    [style=invis]
658                    A:1 -> ext  [particle=a id=1]
659                    ext -> A:2    [particle="b" id=2]
660                    ext -> A:0  [dir=back pdg=-5 id=0]
661                }
662
663            )
664            .unwrap();
665
666            for g in &mut graphs {
667                let pols = g.generate_polarizations();
668                println!("{pols}");
669                let expected = &g.global_prefactor.num;
670                println!("{expected}");
671                assert_eq!(
672                    expected, &pols,
673                    "\nExpected: {:}\nActual: {:} for graph {:}",
674                    expected, pols, g.name,
675                );
676            }
677        }
678
679        #[test]
680        fn vertex_rule() {
681            let mut graphs: Vec<Graph> = dot!(
682                digraph dxda{
683                    ext [style=invis]
684                    ext->v1:0[particle="d" id=0]
685                    ext->v1:1[particle="d~" id=1]
686                    v1:2->ext[particle="a" id=2]
687                }
688
689            )
690            .unwrap();
691
692            for g in &mut graphs {
693                let mut out = String::new();
694                g.dot_serialize_fmt(&mut out, &DotExportSettings::default())
695                    .unwrap();
696                println!("{}", out);
697
698                assert!(g.iter_nodes().all(|(_, _, v)| {
699                    let a = v.vertex_rule.as_ref().unwrap().name.as_str();
700                    a == "V_71"
701                }),);
702            }
703        }
704
705        #[test]
706        fn pslash() {
707            let mut graphs: Vec<Graph> = dot!(
708                digraph dxda{
709                    ext [style=invis]
710                    node[num=1]
711                    ext->v1:0[particle="d" id=0]
712                    v1:1->v2:2[particle="d" id=1]
713                    v2:3->ext[particle="d" id=2]
714                }
715
716            )
717            .unwrap();
718
719            for g in &mut graphs {
720                let mut out = String::new();
721                g.dot_serialize_fmt(&mut out, &DotExportSettings::default())
722                    .unwrap();
723                println!("{}", out);
724            }
725        }
726
727        #[test]
728        fn tree() {
729            initialise().unwrap();
730            let mut graphs: Vec<Graph> = dot!(
731                digraph qqx_aaa_tree_1 {
732                            num="spenso::g(spenso::dind(spenso::cof(3, hedge(1))), spenso::cof(3, hedge(2)))/3"
733                            ext    [style=invis]
734                            ext -> v1:1 [particle="d" id=1];
735                            ext -> v3:2 [dir=back particle="d~" id=2];
736                            v1:3 -> ext [particle="a" id=3];
737                            v2:4 -> ext [particle="a" id=4];
738                            v3:0 -> ext [particle="a" id=0];
739                            v1 -> v2 [particle="d" id=5];
740                            v2 -> v3 [particle="d" id=6];
741                }
742
743                digraph qqx_aaa_tree_1 {
744                            ext    [style=invis]
745                            ext -> v1:1 [particle="d" id=1];
746                            ext -> v3:2 [dir=back particle="d~" id=2];
747                            v1:3 -> ext [particle="a" id=3];
748                            v2:4 -> ext [particle="a" id=4];
749                            v3:0 -> ext [particle="a" id=0];
750                            v1 -> v2 [particle="d" id=5];
751                            v2 -> v3 [particle="d" id=6];
752                            num="spenso::g(spenso::dind(spenso::cof(3, hedge(1))), spenso::cof(3, hedge(2)))/3"
753                }
754
755
756                digraph qqx_aaa_tree_1_glob {
757                ext [style=invis];
758                ext -> v1:1 [particle="d", id=1];
759                ext -> v3:2 [dir=back particle="d~", id=2];
760                v1:3 -> ext [particle="a", id=3];
761                v2:4 -> ext [particle="a", id=4];
762                v3:0 -> ext [particle="a", id=0];
763                v1 -> v2 [particle="d", id=5];
764                v2 -> v3 [particle="d", id=6];
765                num=" UFO::GC_1^3
766                    *spenso::g(spenso::cof(3,hedge(1)),spenso::dind(spenso::cof(3,hedge(5))))
767                    *spenso::gamma(spenso::bis(4,hedge(5)),spenso::bis(4,hedge(1)),spenso::mink(4,hedge(3)))
768
769                    *spenso::g(spenso::cof(3,hedge(5)),spenso::dind(spenso::cof(3,hedge(6))))
770                    *Q(5,spenso::mink(4,edge(5,1)))
771                    *spenso::gamma(spenso::bis(4,hedge(6)),spenso::bis(4,hedge(5)),spenso::mink(4,edge(5,1)))
772
773
774                    *spenso::g(spenso::cof(3,hedge(6)),spenso::dind(spenso::cof(3,hedge(7))))
775                    *spenso::gamma(spenso::bis(4,hedge(7)),spenso::bis(4,hedge(6)),spenso::mink(4,hedge(4)))
776
777                    *spenso::g(spenso::cof(3,hedge(7)),spenso::dind(spenso::cof(3,hedge(8))))
778                    *spenso::gamma(spenso::bis(4,hedge(8)),spenso::bis(4,hedge(7)),spenso::mink(4,edge(6,1)))
779                    *Q(6,spenso::mink(4,edge(6,1)))
780
781                    *spenso::g(spenso::cof(3,hedge(8)),spenso::dind(spenso::cof(3,hedge(2))))
782                    *spenso::gamma(spenso::bis(4,hedge(2)),spenso::bis(4,hedge(8)),spenso::mink(4,hedge(0)))
783
784                       "
785                 overall_factor="1"
786                    projector="u(1,spenso::bis(4,hedge(1)))
787                *vbar(2,spenso::bis(4,hedge(2)))
788                *ϵbar(0,spenso::mink(4,hedge(0)))
789                *ϵbar(3,spenso::mink(4,hedge(3)))
790                *ϵbar(4,spenso::mink(4,hedge(4)))
791                *spenso::g(spenso::cof(3,hedge(2)),spenso::dind(spenso::cof(3,hedge(1))))/3"
792
793                edge [num="1"];
794                node [num="1"];
795                }
796
797
798            )
799            .unwrap();
800
801            let mut a: Option<Atom> = None;
802            for g in &mut graphs {
803                let mut out = String::new();
804
805                let new_a = (g
806                    .numerator(&g.full_filter(), &g.empty_subgraph())
807                    .state
808                    .expr
809                    * &g.global_prefactor.projector
810                    * &g.global_prefactor.num
811                    * &g.overall_factor)
812                    .simplify_color();
813                println!("New:{new_a}");
814                if let Some(a) = &a {
815                    println!("Old:{a}");
816                    assert_eq!(&new_a, a, "{}", (&new_a / a).expand().to_canonical_string());
817                } else {
818                    a = Some(new_a);
819                }
820                g.dot_serialize_fmt(&mut out, &DotExportSettings::default())
821                    .unwrap();
822                println!("{}", out);
823            }
824
825            let mut a = Amplitude::from_graph_list("test", graphs.clone()).unwrap();
826
827            let model = crate::utils::load_generic_model("sm");
828
829            let generation_pool = rayon::ThreadPoolBuilder::new()
830                .num_threads(1)
831                .build()
832                .unwrap();
833
834            let default_runtime_settings = RuntimeSettings::default();
835            let locked_runtime_settings = LockedRuntimeSettings::from(&default_runtime_settings);
836            a.preprocess(
837                &model,
838                &GenerationSettings::default(),
839                &locked_runtime_settings,
840                &generation_pool,
841            )
842            .unwrap();
843        }
844
845        #[test]
846        fn dod_override() {
847            let _gr:Vec<Graph> = dot!(digraph g{
848                node [num=1]
849                edge [num=1]
850                a->b[dod=-100]
851                b->c[dod="-100"]
852            }
853            digraph triangle_ct {
854            num="(-1*_gammaloop::P(1,spenso::cind(1))*_gammaloop::P(2,spenso::cind(1))+-1*_gammaloop::P(1,spenso::cind(2))*_gammaloop::P(2,spenso::cind(2))+-1*_gammaloop::P(1,spenso::cind(3))*_gammaloop::P(2,spenso::cind(3))+_gammaloop::P(1,spenso::cind(0))*_gammaloop::P(2,spenso::cind(0)))^-2*(-1*_gammaloop::P(2,spenso::mink(4,python::mu2))+-1*_gammaloop::Q(6,spenso::mink(4,python::mu2))+_gammaloop::P(1,spenso::mink(4,python::mu7)))*(-1*_gammaloop::Q(7,spenso::mink(4,python::mu2))+_gammaloop::P(2,spenso::mink(4,python::mu2)))*-1/4*_gammaloop::G^2*_gammaloop::P(1,spenso::mink(4,python::mu3))*_gammaloop::P(1,spenso::mink(4,python::mu5))*_gammaloop::P(2,spenso::mink(4,python::mu4))*_gammaloop::P(2,spenso::mink(4,python::mu6))*spenso::gamma(spenso::bis(4,_gammaloop::hedge(2)),spenso::bis(4,python::s1),spenso::mink(4,python::mu1))*spenso::gamma(spenso::bis(4,python::s1),spenso::bis(4,python::s2),spenso::mink(4,python::mu2))*spenso::gamma(spenso::bis(4,python::s2),spenso::bis(4,python::s3),spenso::mink(4,python::mu3))*spenso::gamma(spenso::bis(4,python::s3),spenso::bis(4,python::tree_form_factor_spinor_2),spenso::mink(4,python::mu4))*spenso::gamma(spenso::bis(4,python::s4),spenso::bis(4,python::s6),spenso::mink(4,python::mu7))*spenso::gamma(spenso::bis(4,python::s5),spenso::bis(4,python::s4),spenso::mink(4,python::mu6))*spenso::gamma(spenso::bis(4,python::s6),spenso::bis(4,_gammaloop::hedge(1)),spenso::mink(4,python::mu1))*spenso::gamma(spenso::bis(4,python::tree_form_factor_spinor_1),spenso::bis(4,python::s5),spenso::mink(4,python::mu5))";
855            overall_factor="1";
856            projector="((-1*_gammaloop::P(3,spenso::cind(0))+-1*_gammaloop::P(4,spenso::cind(0))+_gammaloop::P(1,spenso::cind(0)))^2+(-1*_gammaloop::P(3,spenso::cind(1))+-1*_gammaloop::P(4,spenso::cind(1))+_gammaloop::P(1,spenso::cind(1)))^2*-1+(-1*_gammaloop::P(3,spenso::cind(2))+-1*_gammaloop::P(4,spenso::cind(2))+_gammaloop::P(1,spenso::cind(2)))^2*-1+(-1*_gammaloop::P(3,spenso::cind(3))+-1*_gammaloop::P(4,spenso::cind(3))+_gammaloop::P(1,spenso::cind(3)))^2*-1)^-1*((-1*_gammaloop::P(3,spenso::cind(0))+_gammaloop::P(1,spenso::cind(0)))^2+(-1*_gammaloop::P(3,spenso::cind(1))+_gammaloop::P(1,spenso::cind(1)))^2*-1+(-1*_gammaloop::P(3,spenso::cind(2))+_gammaloop::P(1,spenso::cind(2)))^2*-1+(-1*_gammaloop::P(3,spenso::cind(3))+_gammaloop::P(1,spenso::cind(3)))^2*-1)^-1*(-1*_gammaloop::P(3,spenso::mink(4,_gammaloop::edge(5,1)))+_gammaloop::P(1,spenso::mink(4,_gammaloop::edge(5,1))))*(-1*_gammaloop::P(3,spenso::mink(4,_gammaloop::edge(6,1)))+-1*_gammaloop::P(4,spenso::mink(4,_gammaloop::edge(6,1)))+_gammaloop::P(1,spenso::mink(4,_gammaloop::edge(6,1))))*-1/27*_gammaloop::ee^3*_gammaloop::u(1,spenso::bis(4,_gammaloop::hedge(1)))*_gammaloop::vbar(2,spenso::bis(4,_gammaloop::hedge(2)))*_gammaloop::ϵbar(0,spenso::mink(4,_gammaloop::hedge(0)))*_gammaloop::ϵbar(3,spenso::mink(4,_gammaloop::hedge(3)))*_gammaloop::ϵbar(4,spenso::mink(4,_gammaloop::hedge(4)))*spenso::gamma(spenso::bis(4,_gammaloop::hedge(5)),spenso::bis(4,python::tree_form_factor_spinor_1),spenso::mink(4,_gammaloop::hedge(3)))*spenso::gamma(spenso::bis(4,_gammaloop::hedge(6)),spenso::bis(4,_gammaloop::hedge(5)),spenso::mink(4,_gammaloop::edge(5,1)))*spenso::gamma(spenso::bis(4,_gammaloop::hedge(7)),spenso::bis(4,_gammaloop::hedge(6)),spenso::mink(4,_gammaloop::hedge(4)))*spenso::gamma(spenso::bis(4,_gammaloop::hedge(8)),spenso::bis(4,_gammaloop::hedge(7)),spenso::mink(4,_gammaloop::edge(6,1)))*spenso::gamma(spenso::bis(4,python::tree_form_factor_spinor_2),spenso::bis(4,_gammaloop::hedge(8)),spenso::mink(4,_gammaloop::hedge(0)))";
857            edge [num="1", dod="-100"];
858            node [num="1", dod="-100"];
859            ext [style=invis];
860            ext -> vl1:1 [particle="d", id=1];
861            ext -> vl2:2 [dir=back particle="d~", id=2];
862            v1:3 -> ext [id=3];
863            v1:4 -> ext [id=4];
864            v1:0 -> ext [id=0];
865            vl1 -> v1 [particle="d", id=5];
866            v1 -> vl2 [particle="d", id=6];
867            vl1 -> vl2 [particle="g", id=7, lmb_id=0];
868            }
869    )
870            .unwrap();
871        }
872    }
873}