1use bincode_trait_derive::{Decode, Encode};
2use eyre::Result;
3use itertools::Itertools;
4use linnet::half_edge::involution::EdgeVec;
5use spenso::algebra::complex::Complex;
6use symbolica::{
7 atom::{Atom, AtomCore, Symbol},
8 domains::{dual::HyperDual, float::Real},
9 evaluate::{FunctionMap, OptimizationSettings},
10 function, parse, symbol,
11};
12use tracing::debug;
13use typed_index_collections::TiVec;
14
15use crate::{
16 GammaLoopContext,
17 cff::esurface::Esurface,
18 graph::{LmbIndex, LoopMomentumBasis},
19 integrands::process::GenericEvaluator,
20 momentum::{
21 Energy, FourMomentum,
22 sample::{MomentumSample, SubspaceData},
23 },
24 processes::EvaluatorSettings,
25 settings::runtime::{
26 IntegratedCounterTermRange, IntegratedCounterTermSettings, UVLocalisationSettings,
27 },
28 utils::{
29 self, F, FloatLike, GS,
30 hyperdual_utils::{DualOrNot, new_constant, simple_n_deriv_shape},
31 },
32};
33
34pub mod amplitude_counterterm;
35pub mod lu_counterterm;
36pub mod overlap;
37pub mod overlap_subspace;
38
39fn evaluate_uv_damper<T: FloatLike>(
40 radius: &F<T>,
41 radius_star: &F<T>,
42 e_cm: &F<T>,
43 settings: &UVLocalisationSettings,
44) -> F<T> {
45 if settings.force_uv_dampers_to_one {
46 return radius.one();
47 }
48
49 let normalizing_scale = match settings.dynamic_width {
50 true => radius_star,
51 false => e_cm,
52 };
53
54 let delta_r = radius - radius_star;
55
56 if delta_r.abs() > F::from_f64(settings.sliver_width) * normalizing_scale {
57 return radius.zero();
58 }
59
60 let delta_r_sq = &delta_r * &delta_r;
61 let width = F::from_f64(settings.gaussian_width) * normalizing_scale;
62 let width_sq = &width * &width;
63
64 (-delta_r_sq / width_sq).exp()
65}
66
67fn evaluate_uv_damper_dual<T: FloatLike>(
68 radius: &HyperDual<F<T>>,
69 radius_star: &HyperDual<F<T>>,
70 e_cm: &F<T>,
71 settings: &UVLocalisationSettings,
72) -> HyperDual<F<T>> {
73 if settings.force_uv_dampers_to_one {
74 return new_constant(radius, &radius.values[0].one());
75 }
76
77 let normalizing_scale = if settings.dynamic_width {
78 radius_star.clone()
79 } else {
80 new_constant(radius, e_cm)
81 };
82
83 let delta_r = radius.clone() - radius_star.clone();
84
85 if delta_r.values[0].abs() > F::from_f64(settings.sliver_width) * &normalizing_scale.values[0] {
86 return new_constant(radius, &radius.values[0].zero());
87 }
88
89 let delta_r_sq = delta_r.clone() * delta_r;
90 let width = new_constant(radius, &F::from_f64(settings.gaussian_width)) * normalizing_scale;
91 let width_sq = width.clone() * width;
92
93 (-delta_r_sq / width_sq).exp()
94}
95
96fn evaluate_integrated_ct_normalisation<T: FloatLike>(
97 radius: &F<T>,
98 radius_star: &F<T>,
99 _e_cm: &F<T>,
100 settings: &IntegratedCounterTermSettings,
101) -> F<T> {
102 match &settings.range {
103 IntegratedCounterTermRange::Infinite {
104 h_function_settings,
105 } => {
106 let h = utils::h(&(radius_star / radius), None, None, h_function_settings);
107 h * (radius_star).inv()
108 }
109 IntegratedCounterTermRange::Compact {} => {
110 todo!();
111 }
112 }
113}
114
115fn evaluate_integrated_ct_normalisation_dual<T: FloatLike>(
116 radius: &HyperDual<F<T>>,
117 radius_star: &HyperDual<F<T>>,
118 _e_cm: &F<T>,
119 settings: &IntegratedCounterTermSettings,
120) -> HyperDual<F<T>> {
121 match &settings.range {
122 IntegratedCounterTermRange::Infinite {
123 h_function_settings,
124 } => {
125 let h = utils::h_dual(
126 &(radius_star.clone() / radius.clone()),
127 None,
128 None,
129 h_function_settings,
130 );
131 h / radius_star.clone()
132 }
133 IntegratedCounterTermRange::Compact {} => {
134 todo!();
135 }
136 }
137}
138
139#[derive(Clone, Encode, Decode)]
140#[trait_decode(trait = GammaLoopContext)]
141pub(crate) struct RstarTDependenceEvaluator {
142 dual_shape_for_esurface_evaluation: Vec<Vec<usize>>,
143 implicit_function_theorem: Option<GenericEvaluator>,
144}
145
146impl RstarTDependenceEvaluator {
147 pub(crate) fn supports_t_derivatives(&self) -> bool {
148 self.implicit_function_theorem.is_some()
149 }
150
151 fn evaluate<T: FloatLike>(&mut self, input: RstarTDependenceInput<'_, T>) -> HyperDual<F<T>> {
152 let RstarTDependenceInput {
153 t_star,
154 radius_star,
155 overlap_center,
156 subspace,
157 unrescaled_momentum_sample,
158 masses,
159 threshold_esurface,
160 lmb,
161 all_lmbs,
162 } = input;
163 debug!("t-star: {}", t_star);
164 debug!("r-star: {}", radius_star);
165
166 let dual = HyperDual::new(self.dual_shape_for_esurface_evaluation.clone());
167 let dual_rstar = dual.variable(0, radius_star.clone());
168 let dual_t = dual.variable(1, t_star.clone());
169
170 let rescale_tstar = unrescaled_momentum_sample
171 .loop_moms()
172 .rescale_with_hyper_dual(&dual_t, None);
173
174 let dualized_externals_three_momenta = unrescaled_momentum_sample
175 .external_moms()
176 .iter()
177 .map(|fm: &FourMomentum<F<T>>| fm.spatial.map_ref(&|x| new_constant(&dual, x)))
178 .collect();
179
180 let lmb_transform = rescale_tstar.lmb_transform(
181 lmb,
182 subspace.get_lmb(all_lmbs),
183 &dualized_externals_three_momenta,
184 );
185
186 let dualized_overlap_center = overlap_center
187 .iter()
188 .map(|momentum| momentum.map_ref(&|x| new_constant(&dual, x)))
189 .collect::<crate::momentum::sample::LoopMomenta<_>>();
190
191 let shifted_t_dependent_momenta = lmb_transform
192 .iter()
193 .zip(dualized_overlap_center.iter())
194 .map(|(momentum, center)| momentum.clone() - center.clone())
195 .collect::<crate::momentum::sample::LoopMomenta<_>>();
196
197 let zero: HyperDual<F<T>> = new_constant(&dual, &radius_star.zero());
198
199 let shifted_radius_squared: HyperDual<F<T>> = match subspace.as_subspace_simple() {
200 None => shifted_t_dependent_momenta
201 .iter()
202 .map(|momentum| momentum.norm_squared())
203 .fold(zero.clone(), |acc, norm_squared| acc + norm_squared),
204 Some(indices) => shifted_t_dependent_momenta
205 .iter_enumerated()
206 .filter(|(loop_index, _)| indices.contains(loop_index))
207 .map(|(_, momentum)| momentum.norm_squared())
208 .fold(zero, |acc, norm_squared| acc + norm_squared),
209 };
210
211 let shifted_radius = shifted_radius_squared.sqrt();
212 let inverse_shifted_radius =
213 new_constant(&shifted_radius, &radius_star.one()) / shifted_radius.clone();
214
215 let unit_shifted_t_dependent_momenta = shifted_t_dependent_momenta
216 .rescale(&inverse_shifted_radius, subspace.as_subspace_simple());
217
218 let dualized_external_fourmomenta = dualized_externals_three_momenta
219 .into_iter()
220 .zip(unrescaled_momentum_sample.external_moms())
221 .map(|(spatial, fm)| FourMomentum {
222 spatial,
223 temporal: Energy {
224 value: new_constant(&dual, &fm.temporal.value),
225 },
226 })
227 .collect();
228
229 let rescale_rstar = unit_shifted_t_dependent_momenta
230 .rescale(&dual_rstar, subspace.as_subspace_simple())
231 .iter()
232 .zip(dualized_overlap_center.iter())
233 .map(|(momentum, center)| momentum.clone() + center.clone())
234 .collect::<crate::momentum::sample::LoopMomenta<_>>();
235
236 let dual_esurface = threshold_esurface.compute_from_dual_momenta(
237 subspace.get_lmb(all_lmbs),
238 masses,
239 &rescale_rstar,
240 &dualized_external_fourmomenta,
241 );
242
243 debug!("Dual e-surface: {}", dual_esurface);
244
245 let params = dual_esurface.values[1..]
246 .iter()
247 .map(|x| Complex::new_re(x.clone()))
248 .collect_vec();
249
250 debug!("Parameters for implicit function theorem: {:#?}", params);
251
252 let result = T::get_evaluator(
253 self.implicit_function_theorem
254 .as_mut()
255 .expect("r_star(t) evaluator requested without t-derivative support"),
256 )(¶ms)
257 .into_iter()
258 .map(DualOrNot::unwrap_real)
259 .collect_vec();
260
261 debug!("Result from implicit function theorem: {:#?}", result);
262
263 let mut dual_values = vec![radius_star.clone()];
264 let mut n_factorial = 1;
265
266 for (i, result) in result.into_iter().enumerate() {
267 if i > 0 {
268 n_factorial *= i;
269 dual_values.push(result.re / F::from_f64(n_factorial as f64));
270 } else {
271 dual_values.push(result.re);
272 }
273 }
274
275 HyperDual::from_values(simple_n_deriv_shape(dual_values.len() - 1), dual_values)
276 }
277}
278
279pub(crate) struct RstarTDependenceInput<'a, T: FloatLike> {
280 pub t_star: &'a F<T>,
281 pub radius_star: &'a F<T>,
282 pub overlap_center: &'a crate::momentum::sample::LoopMomenta<F<T>>,
283 pub subspace: &'a SubspaceData,
284 pub unrescaled_momentum_sample: &'a MomentumSample<T>,
285 pub masses: &'a EdgeVec<F<T>>,
286 pub threshold_esurface: &'a Esurface,
287 pub lmb: &'a LoopMomentumBasis,
288 pub all_lmbs: &'a TiVec<LmbIndex, LoopMomentumBasis>,
289}
290
291pub(crate) fn generate_rstar_t_dependence_evaluator(
293 num_t_derivatives: usize,
294) -> Result<RstarTDependenceEvaluator> {
295 if num_t_derivatives == 0 {
296 return Ok(RstarTDependenceEvaluator {
297 dual_shape_for_esurface_evaluation: Vec::new(),
298 implicit_function_theorem: None,
299 });
300 }
301
302 let t = symbol!("t");
303
304 let rstar = parse!("r_star(t)");
305 let e_surface = function!(GS.eta, rstar.clone(), Atom::var(t));
306
307 let mut rstar_derivatives = vec![];
308 for i in 0..num_t_derivatives {
309 if i == 0 {
310 rstar_derivatives.push(rstar.derivative(t));
311 } else {
312 rstar_derivatives.push(rstar_derivatives.last().unwrap().derivative(t));
313 }
314 }
315
316 let mut equations = vec![];
317 for i in 0..num_t_derivatives {
318 if i == 0 {
319 equations.push(e_surface.derivative(t));
320 } else {
321 equations.push(equations.last().unwrap().derivative(t));
322 }
323 }
324
325 let mut solutions = equations
326 .iter()
327 .zip(&rstar_derivatives)
328 .map(|(eq, variable)| {
329 Atom::solve_linear_system::<u8, _, _>(&[eq], &[variable])
330 .unwrap()
331 .pop()
332 .unwrap()
333 })
334 .collect_vec();
335
336 for i in 1..solutions.len() {
337 for j in 0..i {
338 solutions[i] = solutions[i]
339 .replace(rstar_derivatives[j].clone())
340 .with(solutions[j].clone());
341 }
342 }
343
344 let mut dual_shape = vec![vec![0, 0]];
346 let mut params = vec![];
347 for i in 1..=num_t_derivatives {
348 let mut eta_derivatives_at_this_order = vec![];
349
350 let mut current_r_derivative_counter = 0;
351 let mut current_t_derivative_counter = i;
352
353 loop {
354 dual_shape.push(vec![
355 current_r_derivative_counter,
356 current_t_derivative_counter,
357 ]);
358 let eta_derivative = function!(
359 Symbol::DERIVATIVE,
360 current_r_derivative_counter,
361 current_t_derivative_counter,
362 GS.eta,
363 rstar.clone(),
364 Atom::var(t)
365 );
366
367 eta_derivatives_at_this_order.push(eta_derivative);
368
369 if current_t_derivative_counter == 0 {
370 break;
371 }
372 current_r_derivative_counter += 1;
373 current_t_derivative_counter -= 1;
374 }
375
376 params.extend(eta_derivatives_at_this_order);
377 }
378
379 let fn_map = FunctionMap::new();
380 let fn_map_entries = vec![];
381 let implict_function_theorem = GenericEvaluator::new_from_raw_params(
382 solutions,
383 ¶ms,
384 &fn_map,
385 fn_map_entries,
386 OptimizationSettings::default(),
387 None,
388 &EvaluatorSettings::default(),
389 )?;
390
391 Ok(RstarTDependenceEvaluator {
392 dual_shape_for_esurface_evaluation: dual_shape,
393 implicit_function_theorem: Some(implict_function_theorem),
394 })
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use crate::utils::hyperdual_utils::new_from_values;
401
402 #[test]
403 fn uv_damper_can_be_forced_to_one() {
404 let settings = UVLocalisationSettings {
405 force_uv_dampers_to_one: true,
406 ..Default::default()
407 };
408
409 assert_eq!(
410 evaluate_uv_damper(&F(100.0_f64), &F(1.0_f64), &F(1.0_f64), &settings),
411 F(1.0_f64)
412 );
413 }
414
415 #[test]
416 fn uv_damper_dual_can_be_forced_to_constant_one() {
417 let settings = UVLocalisationSettings {
418 force_uv_dampers_to_one: true,
419 ..Default::default()
420 };
421 let shape = HyperDual::new(simple_n_deriv_shape(1));
422 let radius = new_from_values(&shape, &[F(100.0_f64), F(5.0_f64)]);
423 let radius_star = new_from_values(&shape, &[F(1.0_f64), F(3.0_f64)]);
424
425 let damper = evaluate_uv_damper_dual(&radius, &radius_star, &F(1.0_f64), &settings);
426
427 assert_eq!(damper.values, vec![F(1.0_f64), F(0.0_f64)]);
428 }
429
430 #[test]
431 fn test_rstar_t_dependence_evaluator() {
432 generate_rstar_t_dependence_evaluator(3).unwrap();
433 }
434
435 #[test]
436 fn test_rstar_t_dependence_evaluator_zero_derivatives() {
437 let evaluator = generate_rstar_t_dependence_evaluator(0).unwrap();
438 assert!(!evaluator.supports_t_derivatives());
439 }
440}