1use std::{cell::RefCell, collections::BTreeSet, ops::Deref};
2
3use ahash::{AHashMap, AHashSet};
4use idenso::shorthands::schoonschip::Schoonschip;
5use linnet::half_edge::{
6 HedgeGraph, PowersetIterator,
7 involution::{Flow, Hedge, HedgePair},
8 subgraph::{
9 Cycle, InternalSubGraph, ModifySubSet, PairwiseSubSetOps, SuBitGraph, SubGraphLike,
10 SubGraphOps, SubSetLike, SubSetOps, subset::SubSet,
11 },
12};
13use symbolica::{
14 atom::{Atom, AtomCore, Symbol},
15 domains::atom::AtomField,
16 function,
17 poly::series::Series,
18};
19use tracing::debug;
20
21use crate::{
22 graph::{Edge, FeynmanGraph, Graph, HedgeData, LMBext, LoopMomentumBasis, Vertex},
23 integrands::process::param_builder::ParamBuilderGraph,
24 momentum::sample::LoopIndex,
25 numerator::{AppliedFeynmanRule, Numerator},
26 utils::{GS, W_, symbolica_ext::DOD},
27 uv::{ApproximationType, UVgenerationSettings, settings::CTIdentifier},
28};
29
30use super::{Spinney, Wood, spenso_lor_atom};
31
32pub trait UltravioletGraph: LMBext + FeynmanGraph + ParamBuilderGraph {
33 fn n_loops<S: SubGraphLike, E, V, H>(&self, subgraph: &S) -> usize
34 where
35 Self: AsRef<HedgeGraph<E, V, H>>,
36 {
37 self.as_ref().cyclotomatic_number(subgraph)
38 }
39
40 fn dummy_less_full_crown<S: SubGraphLike>(&self, subgraph: &S) -> S::Base
43 where
44 S::Base: ModifySubSet<Hedge> + SubGraphOps;
45
46 fn numerator<S: SubGraphLike + SubSetOps>(
49 &self,
50 subgraph: &S,
51 without: &S,
52 ) -> Numerator<AppliedFeynmanRule>;
53 fn denominator<S: SubGraphLike, T: Fn(&Edge) -> isize>(
54 &self,
55 subgraph: &S,
56 edge_powers: T,
57 ) -> Atom;
58
59 fn boundary_pdg_set<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
60 &self,
61 subgraph: &S,
62 ) -> BTreeSet<isize>
63 where
64 Self: AsRef<HedgeGraph<E, V, H>>,
65 {
66 let graph = self.as_ref();
67 graph
68 .full_crown(subgraph)
69 .included_iter()
70 .filter_map(|hedge| {
71 let edge_id = graph[&hedge];
72 graph[edge_id].particle_pdg_code().map(|pdg| {
73 if graph.flow(hedge) == Flow::Source {
74 -pdg
75 } else {
76 pdg
77 }
78 })
79 })
80 .collect()
81 }
82
83 fn internal_pdg_set<E: UVE, V, H, S: SubGraphLike>(&self, subgraph: &S) -> BTreeSet<isize>
84 where
85 Self: AsRef<HedgeGraph<E, V, H>>,
86 {
87 self.as_ref()
88 .iter_edges_of(subgraph)
89 .filter_map(|(pair, edge_id, _)| {
90 pair.is_paired()
91 .then(|| self.as_ref()[edge_id].particle_pdg_code())
92 .flatten()
93 })
94 .collect()
95 }
96
97 fn has_massive_boundary_external<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
98 &self,
99 subgraph: &S,
100 ) -> bool
101 where
102 Self: AsRef<HedgeGraph<E, V, H>>,
103 {
104 let graph = self.as_ref();
105 graph.full_crown(subgraph).included_iter().any(|hedge| {
106 let edge_id = graph[&hedge];
107 graph[edge_id].is_massive()
108 })
109 }
110
111 fn ct_identifier<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
112 &self,
113 subgraph: &S,
114 ) -> CTIdentifier
115 where
116 Self: AsRef<HedgeGraph<E, V, H>>,
117 {
118 CTIdentifier::new(
119 self.boundary_pdg_set(subgraph),
120 Some(self.internal_pdg_set(subgraph)),
121 )
122 }
123
124 fn approximation_scheme<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
125 &self,
126 subgraph: &S,
127 settings: &UVgenerationSettings,
128 dod: i32,
129 ) -> ApproximationType
130 where
131 Self: AsRef<HedgeGraph<E, V, H>>,
132 {
133 settings.approximation_scheme_for(
134 &self.ct_identifier(subgraph),
135 dod,
136 self.has_massive_boundary_external(subgraph),
137 )
138 }
139
140 fn classify_spinney<E: UVE, V, H>(
141 &self,
142 spinney: InternalSubGraph,
143 settings: &UVgenerationSettings,
144 lmb: &LoopMomentumBasis,
145 ) -> Option<Spinney>
146 where
147 Self: AsRef<HedgeGraph<E, V, H>>,
148 {
149 let dod = self.compute_dod(&spinney.filter);
150 if dod < 0 {
151 return None;
152 }
153
154 let renormalization_scheme = self.approximation_scheme(&spinney.filter, settings, dod);
155
156 if renormalization_scheme != ApproximationType::Unsubtracted {
157 Spinney::with_scheme(spinney, self, lmb, renormalization_scheme, dod)
158 } else {
159 None
160 }
161 }
162
163 fn classified_spinneys<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
164 &self,
165 subgraph: &S,
166 settings: &UVgenerationSettings,
167 lmb: &LoopMomentumBasis,
168 ) -> Vec<Spinney>
169 where
170 Self: AsRef<HedgeGraph<E, V, H>>,
171 {
172 if !settings.subtract_uv {
173 return vec![Spinney::empty(self)];
174 }
175
176 self.spinneys(subgraph)
177 .into_iter()
178 .filter_map(|spinney| self.classify_spinney(spinney, settings, lmb))
179 .collect()
180 }
181
182 fn all_cycle_unions<E, V, H, S: SubGraphLike<Base = SuBitGraph>>(
183 &self,
184 subgraph: &S,
185 ) -> AHashSet<InternalSubGraph>
186 where
187 Self: AsRef<HedgeGraph<E, V, H>>,
188 {
189 let ref_graph = self.as_ref();
190 let _init_node = ref_graph.iter_nodes_of(subgraph).next().unwrap().0;
191 let all_subcycles: Vec<_> =
192 Cycle::all_sum_powerset_filter_map(&ref_graph.cycle_basis_of(subgraph).0, &Some)
193 .map(|a| a.into_iter().map(|c| c.internal_graph(ref_graph)).collect())
194 .unwrap();
195
196 let spinneys: AHashSet<_> = InternalSubGraph::all_unions_iterative(&all_subcycles);
198
199 spinneys
200 }
201 fn all_limits<E, V, H, S: SubGraphLike>(
202 &self,
203 subgraph: &S,
204 expr: &Atom,
205 expansion: Symbol,
206 lmb: &LoopMomentumBasis,
207 ) -> Vec<(SubSet<LoopIndex>, Series<AtomField>)>
208 where
209 Self: AsRef<HedgeGraph<E, V, H>>,
210 {
211 let mom_reps = self.normal_emr_replacement(subgraph, lmb, &[W_.x___], |_s| true);
212
213 let ose_reps = self.get_ose_replacements();
214 let expr = expr
223 .replace(function!(GS.broadcasting_sqrt, W_.a_))
224 .with(Atom::var(W_.a_).sqrt())
225 .replace_multiple(&ose_reps)
226 .replace_multiple(&mom_reps);
227 let mut loops = PowersetIterator::<LoopIndex>::new(lmb.loop_edges.len() as u8);
229
230 let mut limits = Vec::new();
231
232 loops.next();
233
234 for ls in loops {
235 let mut expr = expr.clone();
236 for l in ls.included_iter() {
237 let e = usize::from(lmb.loop_edges[l]) as i64;
238 expr = expr
239 .replace(function!(GS.emr_mom, e, W_.x___))
240 .with(function!(GS.emr_mom, e, W_.x___) / expansion);
241
242 expr /= Atom::var(expansion).pow(3);
243 }
244
245 let series = expr.series(expansion, Atom::Zero, 0).unwrap();
246
247 limits.push((ls, series));
250 }
251 limits
252 }
253
254 fn wood<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(&self, subgraph: &S) -> Wood
255 where
256 Self: AsRef<HedgeGraph<E, V, H>>,
257 {
258 self.wood_with_settings(
259 subgraph,
260 &UVgenerationSettings::default(),
261 &self.as_ref().lmb_of(&self.as_ref().full_filter()),
262 )
263 }
264
265 fn wood_with_settings<E: UVE, V, H, S: SubGraphLike<Base = SuBitGraph>>(
266 &self,
267 subgraph: &S,
268 settings: &UVgenerationSettings,
269 lmb: &LoopMomentumBasis,
270 ) -> Wood
271 where
272 Self: AsRef<HedgeGraph<E, V, H>>,
273 {
274 Wood::from_spinneys(self.classified_spinneys(subgraph, settings, lmb), self)
275 }
276
277 fn compute_dod<S: SubGraphLike<Base = SuBitGraph> + SubSetOps>(&self, subgraph: &S) -> i32;
278 fn local_dod<S: SubGraphLike>(&self, subgraph: &S) -> i32;
279
280 fn spinneys<E, V, H, S: SubGraphLike<Base = SuBitGraph>>(
281 &self,
282 subgraph: &S,
283 ) -> AHashSet<InternalSubGraph>
284 where
285 Self: AsRef<HedgeGraph<E, V, H>>,
286 {
287 let ref_graph: &HedgeGraph<E, V, H> = self.as_ref();
288 debug!(subgraph=%ref_graph.dot(subgraph),"Spinneys of subgraph");
289 let _b: SuBitGraph = ref_graph.empty_subgraph();
290
291 if subgraph.is_empty() {
292 let mut spinneys = AHashSet::new();
293 spinneys.insert(ref_graph.empty_subgraph());
294 return spinneys;
295 }
296
297 let cycles = ref_graph.cycle_basis_of(subgraph).0;
298
299 let _init_node = ref_graph.iter_nodes_of(subgraph).next().unwrap().0;
300 let all_subcycles: Vec<_> = Cycle::all_sum_powerset_filter_map(&cycles, &Some)
301 .map(|a| a.into_iter().map(|c| c.internal_graph(ref_graph)).collect())
302 .unwrap();
303
304 let dod_cache = RefCell::new(AHashMap::new());
305
306 let mut spinneys: AHashSet<_> = InternalSubGraph::all_ops_iterative_filter_map(
307 &all_subcycles,
308 &|a, b| a.union(b),
309 &|union| {
310 let cached_dod = dod_cache.borrow().get(&union).copied();
312 let keep = match cached_dod {
313 Some(keep) => keep,
314 None => {
315 let keep = self.local_dod(&union) >= 0;
316 dod_cache.borrow_mut().insert(union.clone(), keep);
317 keep
318 }
319 };
320 if keep { Some(union) } else { None }
321 },
322 );
323
324 spinneys.insert(ref_graph.empty_subgraph());
325
326 spinneys
327 }
328}
329
330impl AsRef<HedgeGraph<Edge, Vertex, HedgeData>> for Graph {
331 fn as_ref(&self) -> &HedgeGraph<Edge, Vertex, HedgeData> {
332 &self.underlying
333 }
334}
335
336impl UltravioletGraph for Graph {
337 fn dummy_less_full_crown<S: SubGraphLike>(&self, subgraph: &S) -> S::Base
338 where
339 S::Base: ModifySubSet<Hedge>,
340 {
341 let a = self.full_crown(subgraph);
342 let mut ac = a.clone();
343
344 a.included_iter().for_each(|a| {
345 if self[self[&a]].is_dummy {
346 ac.sub(a);
347 }
348 });
349
350 ac
351 }
352
353 fn denominator<S: SubGraphLike, T: Fn(&Edge) -> isize>(
371 &self,
372 subgraph: &S,
373 edge_powers: T,
374 ) -> Atom {
375 let mut den = Atom::num(1);
376
377 for (pair, eid, d) in self.underlying.iter_edges_of(subgraph) {
378 if matches!(pair, HedgePair::Paired { .. }) {
379 let m2 = d.data.mass_atom().pow(2);
380 let edge_power = edge_powers(d.data);
381 let is_power_negative = edge_power < 0;
382 let prop_den = GS.den(
383 usize::from(eid),
384 function!(GS.emr_mom, usize::from(eid)),
385 &m2,
386 spenso_lor_atom(usize::from(eid) as i32, usize::from(eid), GS.dim)
387 .pow(2)
388 .to_dots()
389 - &m2,
390 );
391 for _i in 0..edge_power.abs() {
392 if is_power_negative {
393 den /= prop_den.clone();
394 } else {
395 den *= prop_den.clone();
396 }
397 }
398 }
399 }
400
401 den
402 }
403 fn numerator<S: SubGraphLike + SubSetOps>(
404 &self,
405 subgraph: &S,
406 without: &S,
407 ) -> Numerator<AppliedFeynmanRule> {
408 let num = Numerator::default();
409
410 num.fill_in_reduced(self, subgraph, without)
411 }
412
413 fn compute_dod<S: SubGraphLike<Base = SuBitGraph> + SubSetOps>(&self, subgraph: &S) -> i32 {
414 let lmb = self.lmb_of(subgraph);
415 let empty = self.underlying.empty_subgraph();
416 let integrand = self
417 .numerator(subgraph, &empty)
418 .to_d_dim(GS.dim)
419 .get_single_atom()
420 .unwrap()
421 / self.denominator(subgraph, |_| 1);
422 let nloops: usize = self.n_loops(subgraph);
423 self.uv_rescaled(subgraph.included(), nloops, &lmb, &integrand)
424 .trailing_exponent()
425 }
426
427 fn local_dod<S: SubGraphLike>(&self, subgraph: &S) -> i32 {
428 let mut dod: i32 = 4 * self.n_loops(subgraph) as i32;
429 for (p, _, e) in self.underlying.iter_edges_of(subgraph) {
430 if p.is_paired() {
431 dod += e.data.dod.deref();
432 }
433 }
434
435 for (_, _, n) in self.underlying.iter_nodes_of(subgraph) {
436 dod += n.dod.deref();
437 }
438
439 dod
440 }
441}
442
443pub trait UVE {
444 fn mass_atom(&self) -> Atom;
445 fn particle_pdg_code(&self) -> Option<isize>;
446 fn is_massive(&self) -> bool;
447}