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
18use 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 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 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 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 }
145 }
146
147 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 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 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}