Skip to main content

gammalooprs/uv/
wood.rs

1use crate::{
2    debug_tags,
3    graph::{Graph, LoopMomentumBasis},
4    uv::{Spinney, UVgenerationSettings, approx::CutStructure, forest::CutForests},
5};
6use gammaloop_tracing_filter::{LogMessage, debug_instrument};
7use slotmap::SecondaryMap;
8use std::collections::VecDeque;
9
10use linnet::half_edge::{
11    HedgeGraph,
12    subgraph::{InternalSubGraph, SubSetOps},
13};
14
15// use vakint::{EvaluationOrder, LoopNormalizationFactor, Vakint, VakintSettings};
16
17use super::{
18    Forest, Poset, UltravioletGraph,
19    approx::Approximation,
20    poset::{DAG, DagNode, PosetNode},
21};
22
23pub struct CutWoods {
24    pub cuts: CutStructure,
25    pub woods: Vec<Wood>,
26    pub settings: Vec<vakint::VakintSettings>,
27}
28
29impl CutWoods {
30    #[debug_instrument(graph = %graph.log_display())]
31    pub(crate) fn new(cuts: CutStructure, graph: &Graph, settings: &UVgenerationSettings) -> Self {
32        let mut woods = vec![];
33        let mut vakint_settings = vec![];
34        for cut in cuts.cuts.iter() {
35            let mut subgraph = graph.full_filter();
36            subgraph.subtract_with(&graph.initial_state_cut.left);
37            subgraph.subtract_with(&cut.union);
38
39            let spinneys =
40                graph.classified_spinneys(&subgraph, settings, &graph.loop_momentum_basis);
41
42            for spinney in spinneys.iter() {
43                debug_tags!(#uv, #graph, #spinney,#generation;
44                    log.cutset = cut,
45                    local_dod = %graph.local_dod(&spinney.subgraph),
46                    log.spinney = spinney,
47                    "spinneys",
48                );
49            }
50
51            let wood = Wood::from_spinneys(spinneys, graph);
52
53            let mut lvk_settings = settings.vakint.true_settings();
54            // Keep the legacy wood path aligned with the hedge-poset path:
55            // the downstream integrand builder extracts the epsilon^0 term, so
56            // Vakint must provide one term beyond the maximal pole order.
57            lvk_settings.number_of_terms_in_epsilon_expansion = wood.max_loops as i64 + 1;
58            vakint_settings.push(lvk_settings);
59            woods.push(wood);
60        }
61        CutWoods {
62            cuts,
63            woods,
64            settings: vakint_settings,
65        }
66    }
67
68    pub(crate) fn unfold(self, graph: &Graph) -> CutForests {
69        CutForests {
70            cuts: self.cuts,
71            forests: self
72                .woods
73                .iter()
74                .map(|a| a.unfold(graph, &graph.loop_momentum_basis))
75                .collect(),
76            settings: self.settings,
77        }
78    }
79}
80
81pub struct Wood {
82    poset: Poset<Spinney, ()>,
83    pub max_loops: usize,
84    additional_unions: SecondaryMap<PosetNode, Vec<PosetNode>>,
85}
86
87impl Wood {
88    pub(crate) fn from_spinneys<E, V, H, I: IntoIterator<Item = Spinney>>(
89        s: I,
90        graph: impl AsRef<HedgeGraph<E, V, H>>,
91    ) -> Self {
92        let mut poset = Poset::from_iter(s.into_iter().map(|s| (s, ())));
93        let ref_graph = graph.as_ref();
94
95        poset.invert();
96        poset.compute_topological_order();
97        let mut max_loops = 0;
98
99        let mut unions = SecondaryMap::new();
100
101        for (i, sg) in poset.nodes.iter() {
102            let cs = ref_graph.connected_components(sg.data.filter());
103            max_loops = max_loops.max(sg.data.max_comp_loop_count());
104
105            if cs.len() > 1 {
106                // sg is a disjoint union of spinneys (at the level of half-edges) (strongly disjoint)
107                let mut union = vec![];
108
109                for &c in sg.parents.iter() {
110                    let mut is_in = 0;
111                    for comp in &cs {
112                        let comp =
113                            InternalSubGraph::cleaned_filter_optimist(comp.clone(), ref_graph);
114                        if comp == poset.nodes[c].data.subgraph {
115                            // find the components in the wood that this union is made of
116                            union.push(c);
117                            is_in += 1;
118                        }
119                    }
120
121                    if is_in > 1 {
122                        panic!("is in too many components")
123                    }
124                }
125
126                unions.insert(i, union.clone());
127                // sg.children = union;
128            }
129        }
130
131        // let coverset = poset.to_cover_set();
132        Wood {
133            max_loops,
134            poset,
135            additional_unions: unions,
136        }
137    }
138
139    #[allow(clippy::type_complexity)]
140    fn unfold_bfs<E, V, H, G>(
141        &self,
142        _graph: &G,
143        _lmb: &LoopMomentumBasis,
144        dag: &mut DAG<Approximation, DagNode, ()>,
145        unions: &mut SecondaryMap<PosetNode, Option<Vec<(PosetNode, Option<DagNode>)>>>,
146        root: PosetNode,
147    ) -> DagNode
148    where
149        G: UltravioletGraph + AsRef<HedgeGraph<E, V, H>>,
150    {
151        // let graph = graph.as_ref();
152        let mut search_front = VecDeque::new();
153
154        let tree_root = dag.add_node(Approximation::new(self.poset.data(root).clone()));
155        search_front.push_front((root, tree_root));
156
157        while let Some((node, parent)) = search_front.pop_front() {
158            for c in &self.poset.nodes[node].children {
159                if let Some(tagged_union) = unions.get_mut(*c) {
160                    // Is this node a disjoint union of spinneys
161                    if let Some(mut union) = tagged_union.take() {
162                        let mut all_supplied = true;
163                        for (p, d) in &mut union {
164                            if self.poset.nodes[*p].data == self.poset.nodes[node].data {
165                                *d = Some(parent);
166                            }
167                            if d.is_none() {
168                                all_supplied = false;
169                            }
170                        }
171                        if all_supplied {
172                            let child =
173                                dag.add_node(Approximation::new(self.poset.data(*c).clone()));
174                            for (_, d) in union {
175                                dag.add_edge(d.unwrap(), child);
176                            }
177                            search_front.push_front((*c, child));
178                        } else {
179                            *tagged_union = Some(union);
180                        }
181                    }
182                } else {
183                    let child = dag.add_node(Approximation::new(self.poset.data(*c).clone()));
184                    dag.add_edge(parent, child);
185                    search_front.push_front((*c, child));
186                }
187            }
188        }
189        tree_root
190    }
191
192    pub(crate) fn unfold<E, V, H, G>(&self, graph: &G, lmb: &LoopMomentumBasis) -> Forest
193    where
194        G: UltravioletGraph + AsRef<HedgeGraph<E, V, H>>,
195    {
196        let mut dag: DAG<Approximation, DagNode, ()> = DAG::new();
197
198        let root = self.poset.minimum().unwrap();
199
200        let mut unions = SecondaryMap::new();
201
202        for (p, u) in self.additional_unions.iter() {
203            let union: Vec<(PosetNode, Option<DagNode>)> = u.iter().map(|i| (*i, None)).collect();
204            unions.insert(p, Some(union));
205        }
206
207        let _ = self.unfold_bfs(graph, lmb, &mut dag, &mut unions, root);
208
209        Forest { dag }
210    }
211}