Skip to main content

gammalooprs/subtraction/
mod.rs

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        )(&params)
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
291// use the chain rule to express the t-derivatives of r_star in terms of the t and r derivatives of η(r_star(t), t)
292pub(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    // dual shape is for e-surface derivatives, implict function theorem should NOT be dualized with this
345    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        &params,
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}