Skip to main content

gammalooprs/uv/
wood.rs

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