Skip to main content

gammalooprs/graph/
feynman_graph.rs

1use std::{borrow::Borrow, ops::Deref};
2
3use itertools::Itertools;
4use linnet::half_edge::{
5    HedgeGraph, NodeIndex,
6    involution::{EdgeData, EdgeIndex, EdgeVec, Flow, HedgePair},
7    subgraph::{
8        HedgeNode, InternalSubGraph, ModifySubSet, OrientedCut, SuBitGraph, SubGraphLike,
9        SubSetLike, SubSetOps,
10    },
11};
12
13use spenso::{
14    algebra::{algebraic_traits::IsZero, complex::Complex},
15    structure::concrete_index::ExpandedIndex,
16};
17// use petgraph::Direction::Outgoing;
18use symbolica::{
19    atom::{Atom, AtomCore},
20    id::Replacement,
21};
22use typed_index_collections::TiVec;
23
24use crate::{
25    cff::generation::ShiftRewrite,
26    integrands::process::param_builder::{ParamBuilderGraph, SplitPolarizations},
27    model::{ArcParticle, Model},
28    momentum::sample::{ExternalFourMomenta, ExternalIndex, LoopMomenta},
29    momentum::signature::{ExternalSignature, SignatureLike},
30    momentum::{PolDef, SignOrZero},
31    numerator::graph::ReversibleEdge,
32    utils::{F, FloatLike, GS, external_energy_atom_from_index, ose_atom_from_index},
33    uv::uv_graph::UVE,
34};
35
36use super::{
37    Edge, Graph, HedgeData, LoopMomentumBasis, Vertex, get_cff_inverse_energy_product_impl,
38};
39
40pub trait FeynmanGraph {
41    fn get_emr_vec_cache<T: FloatLike>(
42        &self,
43        loop_moms: &LoopMomenta<F<T>>,
44        external_moms: &ExternalFourMomenta<F<T>>,
45        lmb: &LoopMomentumBasis,
46    ) -> Vec<F<T>>;
47
48    fn num_virtual_edges(&self, subgraph: SuBitGraph) -> usize;
49    fn is_incoming_to(&self, edge: EdgeIndex, vertex: NodeIndex) -> bool;
50    // fn denominator(&self, edge: EdgeIndex) -> (Atom, Atom);
51    // fn get_local_edge_position(
52    //     &self,
53    //     node_id: NodeIndex,
54    //     edge_id: EdgeIndex,
55    //     skip_one: bool,
56    // ) -> usize;
57    fn add_signs_to_edges(&self, node_id: NodeIndex) -> Vec<isize>;
58    fn get_cff_inverse_energy_product(&self) -> Atom;
59    fn get_loop_number(&self) -> usize;
60    fn get_real_mass_vector<T: FloatLike>(&self, model: &Model) -> EdgeVec<F<T>>;
61    fn get_external_masses<T: FloatLike>(&self, model: &Model) -> TiVec<ExternalIndex, F<T>>;
62    fn get_energy_cache<T: FloatLike>(
63        &self,
64        model: &Model,
65
66        loop_moms: &LoopMomenta<F<T>>,
67        external_moms: &ExternalFourMomenta<F<T>>,
68        lmb: &LoopMomentumBasis,
69    ) -> EdgeVec<F<T>>;
70    fn get_esurface_canonization(&self, lmb: &LoopMomentumBasis) -> Option<ShiftRewrite>;
71    fn external_in_or_out_signature(&self) -> ExternalSignature;
72
73    fn get_external_partcles(&self) -> Vec<ArcParticle>;
74    fn get_external_signature(&self) -> SignatureLike<ExternalIndex>;
75    fn get_energy_atoms(&self) -> Vec<Atom>;
76    // fn get_external_energy_atoms(&self) -> Vec<Atom>;
77    // fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom;
78    // fn get_ose_replacements(&self) -> Vec<Replacement>;
79    fn expected_scale(&self, e_cm: F<f64>, model: &Model) -> F<f64>;
80    fn dummy_list(&self) -> Vec<EdgeIndex>;
81    fn no_dummy(&self) -> SuBitGraph;
82    fn all_st_cuts_for_cs(
83        &self,
84        source_nodes: HedgeNode,
85        target_nodes: HedgeNode,
86        initial_state_tree: &SuBitGraph,
87    ) -> Vec<(SuBitGraph, OrientedCut, SuBitGraph)>;
88}
89
90impl Deref for Graph {
91    type Target = HedgeGraph<Edge, Vertex, HedgeData>;
92
93    fn deref(&self) -> &Self::Target {
94        &self.underlying
95    }
96}
97
98impl SplitPolarizations for Graph {
99    fn polarizations(&self) -> Vec<Atom> {
100        self.polarizations.iter().map(|a| a.1.clone()).collect()
101    }
102}
103
104impl<'a, V, E: UVE> SplitPolarizations
105    for (&'a Vec<(PolDef, Atom)>, &'a HedgeGraph<E, V, HedgeData>)
106{
107    fn polarizations(&self) -> Vec<Atom> {
108        self.0.iter().map(|a| a.1.clone()).collect()
109    }
110}
111
112impl<'a, V, E: UVE> ParamBuilderGraph for (&'a Vec<(PolDef, Atom)>, &'a HedgeGraph<E, V, HedgeData>)
113where
114    for<'b> EdgeData<&'b E>: ReversibleEdge,
115{
116    fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
117        self.1.iter_edge_ids()
118    }
119
120    fn get_external_energy_atoms(&self) -> Vec<Atom> {
121        self.1.get_external_energy_atoms()
122    }
123
124    fn get_ose_replacements(&self) -> Vec<Replacement> {
125        self.1.get_ose_replacements()
126    }
127
128    fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
129        self.1.explicit_ose_atom(edge)
130    }
131
132    fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
133        lmb.loop_edges
134            .iter()
135            .flat_map(|edge_id| {
136                vec![
137                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
138                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
139                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
140                ]
141            })
142            .collect()
143    }
144
145    fn external_spatial_params(&self) -> Vec<Atom> {
146        self.1.external_spatial_params()
147    }
148}
149
150impl<V, E: UVE> ParamBuilderGraph for HedgeGraph<E, V, HedgeData>
151where
152    for<'a> EdgeData<&'a E>: ReversibleEdge,
153{
154    fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
155        self.iter_edges().map(|(_, e, _)| e)
156    }
157
158    fn get_external_energy_atoms(&self) -> Vec<Atom> {
159        let external_filter: SuBitGraph = self.external_filter();
160
161        external_filter
162            .included_iter()
163            .map(|hedge| external_energy_atom_from_index(self[&hedge]))
164            .collect()
165    }
166
167    fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
168        let mass = self[edge].mass_atom();
169        let mass2 = &mass * &mass;
170
171        // println!("{}", mass2);
172
173        let dot = GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([1])))
174            * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([1])))
175            + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
176                * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
177            + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])))
178                * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])));
179
180        (dot + mass2).sqrt()
181    }
182
183    fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
184        lmb.loop_edges
185            .iter()
186            .flat_map(|edge_id| {
187                vec![
188                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
189                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
190                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
191                ]
192            })
193            .collect()
194    }
195
196    fn external_spatial_params(&self) -> Vec<Atom> {
197        let external_filter: SuBitGraph = self.external_filter();
198
199        external_filter
200            .included_iter()
201            .flat_map(|hedge| {
202                let edge_id = self[&hedge];
203                vec![
204                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
205                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
206                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
207                ]
208            })
209            .collect()
210    }
211
212    fn get_ose_replacements(&self) -> Vec<Replacement> {
213        self.iter_edges()
214            .filter(|(pair, _, _)| matches!(pair, HedgePair::Paired { .. }))
215            .map(|(_, edge_id, _)| {
216                let ose_atom = ose_atom_from_index(edge_id);
217                let explicit = self.explicit_ose_atom(edge_id);
218                Replacement::new(ose_atom.to_pattern(), explicit.to_pattern())
219            })
220            .collect()
221    }
222}
223
224impl ParamBuilderGraph for Graph {
225    // fn polarizations(&self) -> Vec<Atom> {
226    //     self.polarizations.iter().map(|a| a.1.clone()).collect()
227    // }
228
229    fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
230        self.underlying.iter_edge_ids()
231    }
232
233    fn get_external_energy_atoms(&self) -> Vec<Atom> {
234        self.external_momentum_edge_order()
235            .into_iter()
236            .map(external_energy_atom_from_index)
237            .collect()
238    }
239
240    fn get_ose_replacements(&self) -> Vec<Replacement> {
241        if self.initial_state_cut.nedges(&self.underlying) == 0 {
242            self.underlying.get_ose_replacements()
243        } else {
244            let underlying_without_is_cut = self
245                .underlying
246                .full_filter()
247                .subtract(&self.initial_state_cut.left)
248                .subtract(&self.initial_state_cut.right);
249
250            self.underlying
251                .iter_edges_of(&underlying_without_is_cut)
252                .map(|(_, edge_id, _)| {
253                    let ose_atom = ose_atom_from_index(edge_id);
254                    let explicit = self.underlying.explicit_ose_atom(edge_id);
255                    Replacement::new(ose_atom.to_pattern(), explicit.to_pattern())
256                })
257                .collect()
258        }
259    }
260
261    fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
262        self.underlying.explicit_ose_atom(edge)
263    }
264
265    #[allow(unused_variables)]
266    fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
267        self.loop_momentum_basis
268            .loop_edges
269            .iter()
270            .flat_map(|edge_id| {
271                vec![
272                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
273                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
274                    GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
275                ]
276            })
277            .collect()
278    }
279
280    fn external_spatial_params(&self) -> Vec<Atom> {
281        self.external_momentum_edge_order()
282            .into_iter()
283            .flat_map(|edge_id| {
284                vec![
285                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
286                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
287                    GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
288                ]
289            })
290            .collect()
291    }
292}
293
294// impl FeynmanGraph for Graph {
295//     fn add_signs_to_edges(&self, node_id: NodeIndex) -> Vec<isize> {
296//         self.underlying.add_signs_to_edges(node_id)
297//     }
298
299//     fn dummy_list(&self) -> Vec<EdgeIndex> {
300//         self.underlying.dummy_list()
301//     }
302
303//     fn expected_scale(&self, e_cm: F<f64>, model: &Model) -> F<f64> {
304//         self.underlying.expected_scale(e_cm, model)
305//     }
306
307//     // fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
308//     //     self.underlying.explicit_ose_atom(edge)
309//     // }
310
311//     fn external_in_or_out_signature(&self) -> ExternalSignature {
312//         self.underlying.external_in_or_out_signature()
313//     }
314
315//     fn get_cff_inverse_energy_product(&self) -> Atom {
316//         self.underlying.get_cff_inverse_energy_product()
317//     }
318
319//     fn get_emr_vec_cache<T: FloatLike>(
320//         &self,
321//         loop_moms: &LoopMomenta<F<T>>,
322//         external_moms: &ExternalFourMomenta<F<T>>,
323//         lmb: &LoopMomentumBasis,
324//     ) -> Vec<F<T>> {
325//         self.underlying
326//             .get_emr_vec_cache(loop_moms, external_moms, lmb)
327//     }
328
329//     fn get_energy_atoms(&self) -> Vec<Atom> {
330//         self.underlying.get_energy_atoms()
331//     }
332
333//     fn get_energy_cache<T: FloatLike>(
334//         &self,
335//         model: &Model,
336//         paramb: &ParamBuilder,
337//         loop_moms: &LoopMomenta<F<T>>,
338//         external_moms: &ExternalFourMomenta<F<T>>,
339//         lmb: &LoopMomentumBasis,
340//     ) -> EdgeVec<F<T>> {
341//         self.underlying
342//             .get_energy_cache(model, paramb, loop_moms, external_moms, lmb)
343//     }
344
345//     fn get_esurface_canonization(&self, lmb: &LoopMomentumBasis) -> Option<ShiftRewrite> {
346//         self.underlying.get_esurface_canonization(lmb)
347//     }
348
349//     // fn get_external_energy_atoms(&self) -> Vec<Atom> {
350//     //     self.underlying.get_external_energy_atoms()
351//     // }
352
353//     fn get_external_partcles(&self) -> Vec<ArcParticle> {
354//         self.underlying.get_external_partcles()
355//     }
356
357//     fn get_external_signature(&self) -> SignatureLike<ExternalIndex> {
358//         self.underlying.get_external_signature()
359//     }
360
361//     // fn get_local_edge_position(
362//     //     &self,
363//     //     node_id: NodeIndex,
364//     //     edge_id: EdgeIndex,
365//     //     skip_one: bool,
366//     // ) -> usize {
367//     //     self.underlying
368//     //         .get_local_edge_position(node_id, edge_id, skip_one)
369//     // }
370
371//     fn get_loop_number(&self) -> usize {
372//         self.underlying.get_loop_number()
373//     }
374
375//     fn get_real_mass_vector<T: FloatLike>(
376//         &self,
377//         model: &Model,
378//         paramb: &ParamBuilder,
379//     ) -> EdgeVec<F<T>> {
380//         self.underlying.get_real_mass_vector(model, paramb)
381//     }
382//     fn is_incoming_to(&self, edge: EdgeIndex, vertex: NodeIndex) -> bool {
383//         self.underlying.is_incoming_to(edge, vertex)
384//     }
385
386//     fn no_dummy(&self) -> SuBitGraph {
387//         self.underlying.no_dummy()
388//     }
389
390//     fn num_virtual_edges(&self, subgraph: SuBitGraph) -> usize {
391//         self.underlying.num_virtual_edges(subgraph)
392//     }
393
394//     fn substitute_lmb(&self, edge: EdgeIndex, atom: Atom, lmb: &LoopMomentumBasis) -> Atom {
395//         self.underlying.substitute_lmb(edge, atom, lmb)
396//     }
397// }
398
399impl FeynmanGraph for Graph {
400    fn num_virtual_edges(&self, subgraph: SuBitGraph) -> usize {
401        let internal_subgraph = InternalSubGraph::cleaned_filter_pessimist(subgraph, self);
402        self.count_internal_edges(&internal_subgraph)
403    }
404
405    fn is_incoming_to(&self, edge: EdgeIndex, vertex: NodeIndex) -> bool {
406        let (_, pair) = self[&edge];
407        match pair {
408            HedgePair::Unpaired { hedge, flow } => {
409                self.node_id(hedge) == vertex && matches!(flow, Flow::Sink)
410            }
411            HedgePair::Paired { source: _, sink } => self.node_id(sink) == vertex,
412            HedgePair::Split {
413                source: _,
414                sink,
415                split: _,
416            } => self.node_id(sink) == vertex,
417        }
418    }
419
420    // fn denominator(&self, edge: EdgeIndex) -> (Atom, Atom) {
421    //     let mom = parse!(&format!("Q{}", Into::<usize>::into(edge)));
422    //     let mass = self[edge]
423    //         .particle
424    //         .0
425    //         .mass
426    //         .expression
427    //         .clone()
428    //         .unwrap_or(Atom::num(0));
429
430    //     (mom, mass)
431    // }
432
433    // fn get_local_edge_position(
434    //     &self,
435    //     node_id: NodeIndex,
436    //     edge_id: EdgeIndex,
437    //     skip_one: bool,
438    // ) -> usize {
439    //     unimplemented!()
440    // }
441
442    fn add_signs_to_edges(&self, node_id: NodeIndex) -> Vec<isize> {
443        let node_hairs: SuBitGraph = self.iter_crown(node_id).into();
444
445        self.iter_edges_of(&node_hairs)
446            .map(|(_, edge_index, _)| {
447                if !self.is_incoming_to(edge_index, node_id) {
448                    -(Into::<usize>::into(edge_index) as isize)
449                } else {
450                    Into::<usize>::into(edge_index) as isize
451                }
452            })
453            .collect()
454    }
455
456    /// This includes the factor 2 for each edge, inversion already performed
457    fn get_cff_inverse_energy_product(&self) -> Atom {
458        let full_subgraph = self.full_filter();
459        get_cff_inverse_energy_product_impl(self, &full_subgraph, &[])
460    }
461
462    fn get_loop_number(&self) -> usize {
463        let internal_subgraph =
464            InternalSubGraph::cleaned_filter_pessimist(self.full_filter(), self);
465        let n_initial = self.initial_state_cut.nedges(self);
466        self.cyclotomatic_number(&internal_subgraph) - n_initial
467    }
468
469    fn get_real_mass_vector<T: FloatLike>(&self, model: &Model) -> EdgeVec<F<T>> {
470        self.new_edgevec(|edge, _edge_id, _| {
471            let c = edge
472                .mass_value(model, &self.param_builder)
473                .unwrap_or(Complex {
474                    re: F::from_f64(0.0),
475                    im: F::from_f64(0.0),
476                });
477
478            if c.im.is_zero() {
479                c.re
480            } else {
481                panic!(
482                    "Complex masses not yet supported in gammaLoop for {}:{}",
483                    edge.mass_atom(),
484                    c
485                )
486            }
487        })
488    }
489
490    fn get_external_masses<T: FloatLike>(&self, model: &Model) -> TiVec<ExternalIndex, F<T>> {
491        let external_filter: SuBitGraph = self.external_filter();
492
493        self.iter_edges_of(&external_filter)
494            .sorted_by_key(|(pair, _, _)| match pair {
495                HedgePair::Unpaired { hedge, .. } => *hedge,
496                _ => unreachable!(),
497            })
498            .map(|(_, _, edge)| {
499                let c = edge
500                    .data
501                    .mass_value(model, &self.param_builder)
502                    .unwrap_or(Complex {
503                        re: F::from_f64(0.0),
504                        im: F::from_f64(0.0),
505                    });
506
507                if c.im.is_zero() {
508                    c.re
509                } else {
510                    panic!(
511                        "Complex masses not yet supported in gammaLoop for {}:{}",
512                        edge.data.mass_atom(),
513                        c
514                    )
515                }
516            })
517            .collect()
518    }
519
520    fn get_energy_cache<T: FloatLike>(
521        &self,
522        model: &Model,
523        loop_moms: &LoopMomenta<F<T>>,
524        external_moms: &ExternalFourMomenta<F<T>>,
525        lmb: &LoopMomentumBasis,
526    ) -> EdgeVec<F<T>> {
527        self.new_edgevec_from_iter(
528            lmb.edge_signatures
529                .borrow()
530                .into_iter()
531                .map(|(_, sig)| sig.compute_four_momentum_from_three(loop_moms, external_moms))
532                .zip(self.iter_edges())
533                .map(|(emr_mom, (p, _, edge))| {
534                    if p.is_paired() {
535                        emr_mom
536                            .spatial
537                            .on_shell_energy(edge.data.mass_value(model, &self.param_builder).map(
538                                |m| {
539                                    if m.im.is_non_zero() {
540                                        panic!("Complex masses not yet supported in gammaLoop")
541                                    }
542                                    F::<T>::from_ff64(m.re)
543                                },
544                            ))
545                            .value
546                    } else {
547                        emr_mom.temporal.value // a wierd way of just obtaining the energy of the external particles
548                    }
549                }),
550        )
551        .unwrap()
552    }
553
554    fn get_emr_vec_cache<T: FloatLike>(
555        &self,
556        loop_moms: &LoopMomenta<F<T>>,
557        external_moms: &ExternalFourMomenta<F<T>>,
558        lmb: &LoopMomentumBasis,
559    ) -> Vec<F<T>> {
560        if self.initial_state_cut.nedges(&self.underlying) == 0 {
561            self.iter_edges()
562                .flat_map(|(pair, edge_id, _)| {
563                    if let HedgePair::Paired { .. } = pair {
564                        let emr_vec = lmb.edge_signatures[edge_id]
565                            .compute_three_momentum_from_four(loop_moms, external_moms);
566                        vec![emr_vec.px, emr_vec.py, emr_vec.pz]
567                    } else {
568                        vec![]
569                    }
570                })
571                .collect()
572        } else {
573            let underlying_without_is_cut = self
574                .underlying
575                .full_filter()
576                .subtract(&self.initial_state_cut.left)
577                .subtract(&self.initial_state_cut.right);
578
579            self.underlying
580                .iter_edges_of(&underlying_without_is_cut)
581                .sorted_by(|a, b| a.1.cmp(&b.1))
582                .flat_map(|(_pair, edge_id, _)| {
583                    let emr_vec = lmb.edge_signatures[edge_id]
584                        .compute_three_momentum_from_four(loop_moms, external_moms);
585                    vec![emr_vec.px, emr_vec.py, emr_vec.pz]
586                })
587                .collect()
588        }
589    }
590
591    fn get_esurface_canonization(&self, lmb: &LoopMomentumBasis) -> Option<ShiftRewrite> {
592        // let external_edges: TiVec<ExternalIndex, _> =
593        // self
594        // .iter_edges()
595        // .filter(|(pair, _, _)| matches!(pair, HedgePair::Unpaired { .. }))
596        // .collect();
597
598        // find the external leg which does not appear in it's own signature
599        lmb.ext_edges
600            .iter_enumerated()
601            .find(|(external_index, edge_id)| {
602                lmb.edge_signatures[**edge_id].external[*external_index] == SignOrZero::Zero
603            })
604            .map(|(_, dep_mom_edge_id)| {
605                let dep_mom_signatrue = &lmb.edge_signatures[*dep_mom_edge_id].external;
606
607                let external_shift = lmb
608                    .ext_edges
609                    .iter()
610                    .zip(dep_mom_signatrue)
611                    .filter(|(_, dep_mom_sign)| dep_mom_sign.is_sign())
612                    .map(|(external_edge, dep_mom_sign)| (*external_edge, dep_mom_sign as i64))
613                    .collect_vec();
614
615                ShiftRewrite {
616                    dependent_momentum: *dep_mom_edge_id,
617                    dependent_momentum_expr: external_shift,
618                }
619            })
620    }
621
622    fn external_in_or_out_signature(&self) -> ExternalSignature {
623        let external_filter: SuBitGraph = self.external_filter();
624
625        self.iter_edges_of(&external_filter)
626            .sorted_by_key(|(pair, _, _)| match pair {
627                HedgePair::Unpaired { hedge, .. } => *hedge,
628                _ => unreachable!(),
629            })
630            .map(|(pair, _, _)| match pair {
631                HedgePair::Unpaired {
632                    flow: Flow::Sink, ..
633                } => 1i8,
634                HedgePair::Unpaired {
635                    flow: Flow::Source, ..
636                } => -1i8,
637                _ => unreachable!(),
638            })
639            .collect()
640    }
641
642    fn get_external_partcles(&self) -> Vec<ArcParticle> {
643        let external_filter: SuBitGraph = self.external_filter();
644
645        self.iter_edges_of(&external_filter)
646            .sorted_by_key(|(pair, _, _)| match pair {
647                HedgePair::Unpaired { hedge, .. } => *hedge,
648                _ => unreachable!(),
649            })
650            .filter_map(|(_, _, data)| data.data.particle())
651            .collect()
652    }
653
654    fn get_external_signature(&self) -> SignatureLike<ExternalIndex> {
655        let externals: SuBitGraph = self.external_filter();
656
657        SignatureLike::from_iter(
658            self.iter_edges_of(&externals)
659                .sorted_by_key(|(pair, _, _)| match pair {
660                    HedgePair::Unpaired { hedge, .. } => *hedge,
661                    _ => unreachable!(),
662                })
663                .map(|(pair, _, _)| match pair {
664                    HedgePair::Unpaired {
665                        flow: Flow::Source, ..
666                    } => SignOrZero::Minus,
667                    HedgePair::Unpaired {
668                        flow: Flow::Sink, ..
669                    } => SignOrZero::Plus,
670                    _ => unreachable!(),
671                }),
672        )
673    }
674
675    fn get_energy_atoms(&self) -> Vec<Atom> {
676        self.iter_edges()
677            .map(|(pair, edge_id, _)| match pair {
678                HedgePair::Paired { .. } => ose_atom_from_index(edge_id),
679                HedgePair::Unpaired { .. } => external_energy_atom_from_index(edge_id),
680                _ => unreachable!(),
681            })
682            .collect_vec()
683    }
684
685    fn expected_scale(&self, e_cm: F<f64>, model: &Model) -> F<f64> {
686        let mut scale = Complex::new_re(F(1.0));
687        for (_, _, vertex) in self.iter_nodes() {
688            // include the values of all couplings
689            let coupling_value = vertex
690                .vertex_rule
691                .as_ref()
692                .map(|r| {
693                    r.couplings
694                        .iter()
695                        .flat_map(|couplings| {
696                            couplings.iter().map(|coupling| {
697                                coupling
698                                    .as_ref()
699                                    .map(|coupling| {
700                                        model.couplings[coupling]
701                                            .value
702                                            .map(|x| Complex::new(F(x.re), F(x.im)))
703                                            .unwrap_or(Complex::new_re(F(1.0)))
704                                    })
705                                    .unwrap_or(Complex::new_re(F(1.0)))
706                            })
707                        })
708                        .fold(Complex::new_re(F(1.0)), |product, term| product * term)
709                })
710                .unwrap_or(Complex::new_re(F(1.0)));
711
712            scale *= Complex::new_re(e_cm.powi(vertex.dod.value)) * coupling_value;
713        }
714
715        for (_, _, edge_data) in self.iter_edges_of(
716            &self
717                .full_filter()
718                .subtract(&self.external_filter::<SuBitGraph>()),
719        ) {
720            scale *= Complex::new_re(e_cm.powi(edge_data.data.dod.value))
721        }
722
723        scale.norm_squared().sqrt()
724    }
725
726    fn no_dummy(&self) -> SuBitGraph {
727        let mut subgraph = self.full_filter();
728        for (hedge_pair, _, edge) in self.iter_edges() {
729            if edge.data.is_dummy {
730                subgraph.sub(hedge_pair)
731            }
732        }
733        subgraph
734    }
735
736    fn dummy_list(&self) -> Vec<EdgeIndex> {
737        self.iter_edges()
738            .filter_map(|(_, edge_index, edge_data)| {
739                if edge_data.data.is_dummy {
740                    Some(edge_index)
741                } else {
742                    None
743                }
744            })
745            .collect()
746    }
747
748    fn all_st_cuts_for_cs(
749        &self,
750        source_nodes: HedgeNode,
751        target_nodes: HedgeNode,
752        initial_state_tree: &SuBitGraph,
753    ) -> Vec<(SuBitGraph, OrientedCut, SuBitGraph)> {
754        self.underlying
755            .all_cuts(source_nodes, target_nodes)
756            .into_iter()
757            .map(|(mut l, mut c, mut r)| {
758                // remove initial state cut edges from cut
759                c.left.subtract_with(&self.initial_state_cut.left);
760                c.right.subtract_with(&self.initial_state_cut.left);
761                c.left.subtract_with(&self.initial_state_cut.right);
762                c.right.subtract_with(&self.initial_state_cut.right);
763                l.subtract_with(initial_state_tree);
764                r.subtract_with(initial_state_tree);
765
766                (l, c, r)
767            })
768            .collect_vec()
769    }
770}