1use std::{
2 collections::BTreeMap,
3 hash::Hash,
4 ops::{Mul, Neg},
5 sync::Arc,
6};
7
8use crate::{
9 GammaLoopContext, cff::CutCFFIndex, integrands::process::param_builder::FnMapEntry,
10 numerator::aind::Aind, utils::GS, uv::approx::Rooted,
11};
12use bincode_trait_derive::{Decode, Encode};
13use color_eyre::Result;
14use eyre::eyre;
15use itertools::{EitherOrBoth, Itertools};
16use spenso::{
17 network::parsing::ShadowedStructure,
18 structure::{
19 NamedStructure, ToSymbolic,
20 dimension::Dimension,
21 representation::{Minkowski, RepName},
22 },
23};
24use symbolica::{
25 atom::{Atom, AtomCore, AtomView, Symbol},
26 symbol,
27};
28
29use linnet::half_edge::involution::HedgePair;
30
31pub(crate) fn spenso_lor(
34 tag: i32,
35 ind: impl Into<Aind>,
36 dim: impl Into<Dimension>,
37) -> ShadowedStructure<Aind> {
38 let mink = Minkowski {}.new_slot(dim, ind);
39 NamedStructure::from_iter([mink], GS.emr_mom, Some(vec![Atom::num(tag)])).into_canonical()
40}
41
42pub(crate) fn spenso_lor_atom(tag: i32, ind: impl Into<Aind>, dim: impl Into<Dimension>) -> Atom {
43 spenso_lor(tag, ind, dim).to_symbolic(None).unwrap()
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Hash, Encode, Decode)]
50#[trait_decode(trait = GammaLoopContext)]
51pub struct Integrands {
52 atoms: BTreeMap<CutCFFIndex, Atom>,
53 numerators: Vec<Arc<FnMapEntry>>,
54 scalars: Vec<Arc<FnMapEntry>>,
55}
56
57impl Integrands {
58 pub fn map<F: FnMut(&Atom) -> Atom>(&self, mut f: F) -> Self {
59 Self {
60 atoms: self.iter().map(|(key, atom)| (*key, f(atom))).collect(),
61 numerators: self.numerators.clone(),
62 scalars: self.scalars.clone(),
63 }
64 }
65
66 pub fn fallible_map<F: FnMut(&Atom) -> Result<Atom>>(&self, mut f: F) -> Result<Self> {
67 Ok(Self {
68 atoms: self
69 .iter()
70 .map(|(key, atom)| Ok((*key, f(atom)?)))
71 .collect::<Result<_>>()?,
72 numerators: self.numerators.clone(),
73 scalars: self.scalars.clone(),
74 })
75 }
76
77 pub fn iter(&self) -> impl Iterator<Item = (&CutCFFIndex, &Atom)> {
79 self.atoms.iter()
80 }
81
82 pub(crate) fn numerators(&self) -> &[Arc<FnMapEntry>] {
83 &self.numerators
84 }
85
86 pub(crate) fn scalar_symbol() -> Symbol {
87 symbol!("gammalooprs::uv::scalar_coefficient"; Scalar)
88 }
89
90 pub(crate) fn scalar_definitions(&self) -> &[Arc<FnMapEntry>] {
91 &self.scalars
92 }
93
94 pub(crate) fn with_scalar_definitions(
98 mut self,
99 scalars: impl IntoIterator<Item = Arc<FnMapEntry>>,
100 ) -> Result<Self> {
101 let head = Self::scalar_symbol();
102 let mut definitions: BTreeMap<Vec<Atom>, Arc<FnMapEntry>> = BTreeMap::new();
103 for entry in scalars {
104 if let Some(existing) = definitions.get(&entry.tags) {
105 if Arc::ptr_eq(existing, &entry) || existing == &entry {
106 continue;
107 }
108 return Err(eyre!(
109 "conflicting retained scalar definition for {}",
110 entry.lhs
111 ));
112 }
113 let parameters = entry
114 .args
115 .iter()
116 .cloned()
117 .map(Atom::from)
118 .collect::<Vec<_>>();
119 if entry.tags.is_empty()
120 || entry.is_alias
121 || entry.tags.iter().any(|tag| tag.contains_symbol(head))
122 || entry.lhs != head.call_args(entry.tags.iter().chain(¶meters))
123 || parameters.iter().enumerate().any(|(index, parameter)| {
124 !matches!(parameter.as_view(), AtomView::Var(_))
125 || entry.tags.iter().any(|tag| tag.contains(parameter))
126 || parameters[..index].contains(parameter)
127 })
128 {
129 return Err(eyre!("invalid retained scalar binding {}", entry.lhs));
130 }
131 let mut invalid = false;
132 entry.rhs.visitor(&mut |view| {
133 let valid = match view {
134 AtomView::Num(_) | AtomView::Add(_) | AtomView::Mul(_) | AtomView::Pow(_) => {
135 true
136 }
137 AtomView::Var(_) => parameters
138 .iter()
139 .any(|parameter| parameter.as_view() == view),
140 _ => false,
141 };
142 invalid |= !valid;
143 valid
144 });
145 if invalid {
146 return Err(eyre!(
147 "retained scalar bodies must contain only arithmetic in their formals: {}",
148 entry.lhs
149 ));
150 }
151 definitions.insert(entry.tags.clone(), entry);
152 }
153 self.scalars = definitions.into_values().collect();
154 Ok(self)
155 }
156
157 pub(crate) fn scalar_resolver(
160 scalars: impl IntoIterator<Item = Arc<FnMapEntry>>,
161 ) -> Result<impl FnMut(&Atom) -> Result<Atom>> {
162 let validated = Self::from_iter([]).with_scalar_definitions(scalars)?;
163 let mut replacements = BTreeMap::new();
164 for entry in &validated.scalars {
165 replacements
166 .entry(entry.tags.len())
167 .or_insert_with(BTreeMap::new)
168 .insert(
169 entry.tags.clone(),
170 (entry.tags.len() + entry.args.len(), entry.replacement()),
171 );
172 }
173 let head = Self::scalar_symbol();
174 let mut cache = BTreeMap::<Atom, Atom>::new();
175 Ok(move |atom: &Atom| -> Result<Atom> {
176 if !atom.contains_symbol(head) {
177 return Ok(atom.clone());
178 }
179 let mut error = None;
180 let result = atom.replace_map_bottom_up(|view, _, out| {
181 let AtomView::Fun(call) = view else {
182 return;
183 };
184 if call.get_symbol() != head || error.is_some() {
185 return;
186 }
187 let original = view.to_owned();
188 if let Some(cached) = cache.get(&original) {
189 **out = cached.clone();
190 return;
191 }
192 let mut matched = None;
193 for (tag_count, definitions) in &replacements {
194 let tags = call
195 .iter()
196 .take(*tag_count)
197 .map(|arg| arg.to_owned())
198 .collect::<Vec<_>>();
199 if let Some((arity, replacement)) = definitions.get(&tags)
200 && *arity == call.get_nargs()
201 {
202 if matched.is_some() {
203 error = Some(eyre!("ambiguous retained scalar call {view}"));
204 return;
205 }
206 matched = Some(replacement);
207 }
208 }
209 let Some(replacement) = matched else {
210 error = Some(eyre!("unresolved retained scalar call {view}"));
211 return;
212 };
213 let resolved = view.replace_multiple([replacement]);
214 if resolved.contains_symbol(head) {
215 error = Some(eyre!("unresolved or cyclic retained scalar call {view}"));
216 return;
217 }
218 cache.insert(original, resolved.clone());
219 **out = resolved;
220 });
221 if let Some(error) = error {
222 Err(error)
223 } else {
224 Ok(result)
225 }
226 })
227 }
228
229 #[cfg(test)]
230 pub(crate) fn resolve_scalar(&self, atom: &Atom) -> Result<Atom> {
231 Self::scalar_resolver(self.scalars.iter().cloned())?(atom)
232 }
233
234 pub(crate) fn resolved_scalars(&self) -> Result<Self> {
238 let mut resolve = Self::scalar_resolver(self.scalars.iter().cloned())?;
239 let mut resolved = self.fallible_map(&mut resolve)?.map_numerators(resolve)?;
240 resolved.scalars.clear();
241 Ok(resolved)
242 }
243
244 pub(crate) fn with_numerators(
247 mut self,
248 numerators: impl IntoIterator<Item = Arc<FnMapEntry>>,
249 ) -> Result<Self> {
250 let family = symbol!("gammalooprs::uv::numerator_family");
251 let mut definitions: BTreeMap<Vec<Atom>, Arc<FnMapEntry>> = BTreeMap::new();
252 for numerator in numerators {
253 let parameters = numerator
254 .args
255 .iter()
256 .cloned()
257 .map(Atom::from)
258 .collect::<Vec<_>>();
259 if numerator.tags.is_empty()
260 || numerator.lhs
261 != family.call_args(
262 numerator
263 .tags
264 .iter()
265 .cloned()
266 .chain(parameters.iter().cloned()),
267 )
268 || parameters.iter().enumerate().any(|(index, parameter)| {
271 numerator.tags.iter().any(|tag| tag.contains(parameter))
272 || parameters[..index].iter().any(|other| {
273 other.contains(parameter) || parameter.contains(other)
274 })
275 })
276 {
277 return Err(eyre!(
278 "invalid retained numerator binding {}",
279 numerator.lhs
280 ));
281 }
282 if let Some(existing) = definitions.get(&numerator.tags) {
283 if existing != &numerator {
284 return Err(eyre!(
285 "conflicting retained numerator definition for {}",
286 numerator.lhs
287 ));
288 }
289 continue;
290 }
291 if numerator.rhs.contains_symbol(family) {
292 return Err(eyre!(
293 "retained numerator definitions must be flat: {} contains a family call",
294 numerator.lhs
295 ));
296 }
297 definitions.insert(numerator.tags.clone(), numerator);
298 }
299 self.numerators = definitions.into_values().collect();
300 Ok(self)
301 }
302
303 pub(crate) fn map_numerators(
306 &self,
307 mut map: impl FnMut(&Atom) -> Result<Atom>,
308 ) -> Result<Self> {
309 let numerators = self
310 .numerators
311 .iter()
312 .map(|entry| {
313 let rhs = map(&entry.rhs)?;
314 Ok(if rhs == entry.rhs {
315 Arc::clone(entry)
316 } else {
317 Arc::new(FnMapEntry {
318 rhs,
319 ..entry.as_ref().clone()
320 })
321 })
322 })
323 .collect::<Result<Vec<_>>>()?;
324 self.clone().with_numerators(numerators)
325 }
326
327 pub(crate) fn resolved(&self) -> Result<Self> {
331 let validated = self.resolved_scalars()?;
332 let replacements = validated
333 .numerators
334 .iter()
335 .map(|entry| entry.replacement())
336 .collect::<Vec<_>>();
337 let mut resolved = validated.map(|atom| atom.replace_multiple(&replacements));
338 let family = symbol!("gammalooprs::uv::numerator_family");
339 if resolved
340 .iter()
341 .any(|(_, atom)| atom.contains_symbol(family))
342 {
343 return Err(eyre!("unresolved or cyclic retained numerator family call"));
344 }
345 resolved.numerators.clear();
346 Ok(resolved)
347 }
348
349 pub(crate) fn zero_like(&self) -> Self {
350 self.map(|_| Atom::Zero)
351 }
352
353 pub fn checked_zip(
354 &self,
355 other: &Integrands,
356 mut map: impl FnMut(&CutCFFIndex, &Atom, &Atom) -> Result<Atom>,
357 ) -> Result<Integrands> {
358 let atoms = self
359 .iter()
360 .merge_join_by(other.iter(), |(left_key, _), (right_key, _)| {
361 left_key.cmp(right_key)
362 })
363 .map(|pair| match pair {
364 EitherOrBoth::Both((key, left), (_, right)) => Ok((*key, map(key, left, right)?)),
365 EitherOrBoth::Left((key, _)) => {
366 Err(eyre!("right integrands are missing key {key:?}"))
367 }
368 EitherOrBoth::Right((key, _)) => {
369 Err(eyre!("left integrands are missing key {key:?}"))
370 }
371 })
372 .collect::<Result<_>>()?;
373 Self {
374 atoms,
375 numerators: Vec::new(),
376 scalars: Vec::new(),
377 }
378 .with_numerators(self.numerators.iter().chain(&other.numerators).cloned())?
379 .with_scalar_definitions(self.scalars.iter().chain(&other.scalars).cloned())
380 }
381
382 pub fn zip_mul(&self, other: &Integrands) -> Result<Integrands> {
383 self.checked_zip(other, |_, v1, v2| Ok(v1 * v2))
384 }
385
386 pub fn zip_add(self, others: impl IntoIterator<Item = Self>) -> Result<Self> {
389 let mut numerators = self.numerators;
390 let mut scalars = self.scalars;
391 let mut terms = self
392 .atoms
393 .into_iter()
394 .map(|(key, atom)| (key, vec![atom]))
395 .collect::<BTreeMap<_, _>>();
396 for other in others {
397 numerators.extend(other.numerators);
398 scalars.extend(other.scalars);
399 for pair in terms
400 .iter_mut()
401 .merge_join_by(other.atoms, |(left, _), (right, _)| (*left).cmp(right))
402 {
403 match pair {
404 EitherOrBoth::Both((_, terms), (_, atom)) => terms.push(atom),
405 EitherOrBoth::Left((key, _)) => {
406 return Err(eyre!("right integrands are missing key {key:?}"));
407 }
408 EitherOrBoth::Right((key, _)) => {
409 return Err(eyre!("left integrands are missing key {key:?}"));
410 }
411 }
412 }
413 }
414 Self {
415 atoms: terms
416 .into_iter()
417 .map(|(key, terms)| (key, Atom::add_many(terms)))
418 .collect(),
419 numerators: Vec::new(),
420 scalars: Vec::new(),
421 }
422 .with_numerators(numerators)?
423 .with_scalar_definitions(scalars)
424 }
425}
426
427impl FromIterator<(CutCFFIndex, Atom)> for Integrands {
428 fn from_iter<I: IntoIterator<Item = (CutCFFIndex, Atom)>>(iter: I) -> Self {
429 Self {
430 atoms: BTreeMap::from_iter(iter),
431 numerators: Vec::new(),
432 scalars: Vec::new(),
433 }
434 }
435}
436
437impl Mul<Atom> for Integrands {
438 type Output = Self;
439
440 fn mul(self, rhs: Atom) -> Self::Output {
441 self.map(|a| a * &rhs)
442 }
443}
444
445impl Neg for Integrands {
446 type Output = Self;
447
448 fn neg(self) -> Self::Output {
449 self.map(|a| a.neg())
450 }
451}
452
453impl Mul<&Atom> for Integrands {
454 type Output = Self;
455
456 fn mul(self, rhs: &Atom) -> Self::Output {
457 self.map(|a| a * rhs)
458 }
459}
460
461impl Rooted for Integrands {
462 fn root() -> Self {
463 [(CutCFFIndex::new_all_none(), Atom::num(1))]
464 .into_iter()
465 .collect()
466 }
467}
468
469#[allow(dead_code)]
470pub(crate) fn is_not_paired(pair: &HedgePair) -> bool {
471 !pair.is_paired()
472}
473
474pub mod hedge_poset;
475mod marker;
476mod orchestrator;
477#[cfg(test)]
478pub(crate) mod overlap_control;
479pub mod renormalization;
480pub use renormalization::{RenormalizationPart, RenormalizationStats};
481pub mod settings;
482pub use settings::{
483 ApproximationType, CTIdentifier, CTRenormalizationRule, RenormalizationPrescriptionSettings,
484 UVOrchestrator, UVgenerationSettings,
485};
486pub mod uv_graph;
487pub use uv_graph::UltravioletGraph;
488
489pub mod spinney;
490pub use spinney::Spinney;
491
492pub mod poset;
493pub use poset::Poset;
494
495pub mod wood;
496pub use wood::Wood;
497
498pub mod approx;
499
500pub mod export;
501
502pub mod forest;
503pub use forest::Forest;
504
505pub mod profile;
506pub use profile::{UVProfile, UVProfileAnalysis, UVProfilePassFail};
507
508#[cfg(test)]
509mod tests;