Skip to main content

gammalooprs/subtraction/
amplitude_counterterm.rs

1use std::{collections::BTreeMap, path::Path, slice};
2
3use bincode_trait_derive::{Decode, Encode};
4use color_eyre::Result;
5use eyre::eyre;
6use linnet::half_edge::involution::{EdgeVec, Orientation};
7use spenso::algebra::{algebraic_traits::IsZero, complex::Complex};
8use tracing::{debug, instrument, warn};
9use typed_index_collections::{TiVec, ti_vec};
10
11use crate::{
12    GammaLoopContext,
13    cff::{
14        CutCFFIndex,
15        esurface::{
16            Esurface, EsurfaceCollection, EsurfaceID, ExistingEsurfaceId, ExistingEsurfaces,
17            GroupEsurfaceId, RaisedEsurfaceData, RaisedEsurfaceId,
18            esurface_value_is_strictly_inside,
19        },
20        expression::OrientationID,
21    },
22    graph::{FeynmanGraph, Graph, GraphGroupPosition},
23    integrands::{
24        evaluation::EvaluationMetaData,
25        process::{
26            GenericEvaluator, ParamBuilder, ThresholdParams,
27            evaluators::{
28                EvaluatorStack, SingleOrAllOrientations, evaluate_evaluator,
29                evaluate_evaluator_single,
30            },
31        },
32    },
33    model::Model,
34    momentum::{
35        Energy, FourMomentum, Rotation,
36        sample::{LoopMomenta, MomentumSample},
37    },
38    processes::EvaluatorBuildTimings,
39    settings::{GlobalSettings, RuntimeSettings},
40    subtraction::{
41        evaluate_integrated_ct_normalisation, evaluate_uv_damper,
42        overlap::{OverlapGroup, OverlapStructure},
43    },
44    utils::{
45        F, FloatLike,
46        hyperdual_utils::{
47            DualOrNot, extract_t_derivatives, extract_t_derivatives_complex, new_constant,
48            shape_from_cut_cff_index, simple_n_deriv_shape,
49        },
50        newton_solver::{NewtonIterationResult, RadialRootDiagnostics, RadialRootIdentity},
51    },
52    uv::Integrands,
53};
54use symbolica::domains::dual::HyperDual;
55
56const MAX_ITERATIONS: usize = 40;
57
58fn multiply_dual_or_not_complex<T: FloatLike>(
59    lhs: DualOrNot<Complex<F<T>>>,
60    rhs: &DualOrNot<Complex<F<T>>>,
61) -> DualOrNot<Complex<F<T>>> {
62    match (lhs, rhs) {
63        (DualOrNot::NonDual(lhs), DualOrNot::NonDual(rhs)) => DualOrNot::NonDual(lhs * rhs),
64        (DualOrNot::Dual(lhs), DualOrNot::Dual(rhs)) => DualOrNot::Dual(lhs * rhs.clone()),
65        (DualOrNot::Dual(lhs), DualOrNot::NonDual(rhs)) => {
66            let rhs_dual = new_constant(&lhs, rhs);
67            DualOrNot::Dual(lhs * rhs_dual)
68        }
69        (DualOrNot::NonDual(lhs), DualOrNot::Dual(rhs)) => {
70            let lhs_dual = new_constant(rhs, &lhs);
71            DualOrNot::Dual(lhs_dual * rhs.clone())
72        }
73    }
74}
75
76#[derive(Clone, Encode, Decode)]
77#[trait_decode(trait = GammaLoopContext)]
78pub struct AmplitudeCountertermData {
79    pub overlap: OverlapStructure,
80    pub evaluators: TiVec<RaisedEsurfaceId, AmplitudeCountertermEvaluator>,
81    pub helper_evaluators: Vec<GenericEvaluator>,
82    // `generated_mask` tracks whether a threshold slot actually has symbolic content.
83    // This is independent of any orientation filtering and reflects the underlying
84    // threshold-generation logic itself.
85    pub generated_mask: TiVec<RaisedEsurfaceId, bool>,
86    // `active_mask` is an additional generation-time gate coming from the selected
87    // orientation subset. A slot can be generated in principle but inactive for the
88    // current generated evaluator set, in which case we keep its index but compile it
89    // to a zero/dummy evaluator and hard-fail if runtime ever reaches it.
90    pub active_mask: TiVec<RaisedEsurfaceId, bool>,
91    pub raised_data: RaisedEsurfaceData,
92    pub esurface_map: TiVec<GroupEsurfaceId, TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>>,
93    pub local_esurface_exists: TiVec<GroupEsurfaceId, bool>,
94    pub own_group_position: GraphGroupPosition,
95}
96
97#[derive(Clone, Encode, Decode)]
98#[trait_decode(trait = GammaLoopContext)]
99pub struct AmplitudeCountertermAtom {
100    pub parametric: Integrands,
101}
102
103impl AmplitudeCountertermAtom {
104    pub(crate) fn is_generated(&self) -> bool {
105        self.parametric.iter().next().is_some()
106    }
107
108    pub(crate) fn zero_like(&self) -> Self {
109        Self {
110            parametric: self.parametric.map(|_| symbolica::atom::Atom::Zero),
111        }
112    }
113
114    #[instrument(skip_all)]
115    pub(crate) fn to_evaluator_with_timings(
116        &self,
117        param_builder: &ParamBuilder,
118        orientations: &TiVec<OrientationID, EdgeVec<Orientation>>,
119        global_settings: &GlobalSettings,
120    ) -> (AmplitudeCountertermEvaluator, EvaluatorBuildTimings) {
121        let _progress_guard =
122            crate::processes::enter_detailed_progress_span("Building Threshold CT Evaluator");
123        let mut evaluator_stacks = BTreeMap::new();
124        let mut timings = EvaluatorBuildTimings::default();
125
126        for (index, integrand) in self.parametric.iter() {
127            let dual_shape = shape_from_cut_cff_index(index);
128
129            let (evaluator_stack, evaluator_timings) = EvaluatorStack::new_with_timings(
130                slice::from_ref(integrand),
131                param_builder,
132                orientations.as_slice().as_ref(),
133                dual_shape,
134                &global_settings.generation.evaluator,
135            )
136            .unwrap();
137            timings += evaluator_timings;
138            evaluator_stacks.insert(*index, evaluator_stack);
139        }
140
141        (AmplitudeCountertermEvaluator { evaluator_stacks }, timings)
142    }
143
144    pub(crate) fn new() -> Self {
145        Self {
146            parametric: std::iter::empty().collect(),
147        }
148    }
149}
150
151#[derive(Clone, Encode, Decode)]
152#[trait_decode(trait = GammaLoopContext)]
153pub struct AmplitudeCountertermEvaluator {
154    pub evaluator_stacks: BTreeMap<CutCFFIndex, EvaluatorStack>,
155}
156
157impl AmplitudeCountertermEvaluator {
158    pub(crate) fn generic_evaluator_count(&self) -> usize {
159        self.evaluator_stacks
160            .values()
161            .map(EvaluatorStack::generic_evaluator_count)
162            .sum()
163    }
164}
165
166#[derive(Debug, Clone)]
167pub struct AmplitudeLocalCountertermEvaluation<T: FloatLike> {
168    pub esurface_id: RaisedEsurfaceId,
169    pub overlap_group: usize,
170    pub value: Complex<F<T>>,
171}
172
173#[derive(Debug, Clone)]
174pub struct AmplitudeCountertermEvaluation<T: FloatLike> {
175    pub total: Complex<F<T>>,
176    pub local_counterterms: Vec<AmplitudeLocalCountertermEvaluation<T>>,
177}
178
179impl AmplitudeCountertermData {
180    fn radial_root_identity(
181        graph_name: &str,
182        overlap_group: usize,
183        raised_esurface_id: RaisedEsurfaceId,
184        rotation: &Rotation,
185    ) -> RadialRootIdentity {
186        RadialRootIdentity::new(format!(
187            "amplitude graph '{graph_name}' overlap group {overlap_group} raised E-surface {} probe rotation {}",
188            raised_esurface_id.0, rotation.method,
189        ))
190    }
191
192    pub(crate) fn generic_evaluator_count(&self) -> usize {
193        let stack_count = self
194            .evaluators
195            .iter()
196            .map(AmplitudeCountertermEvaluator::generic_evaluator_count)
197            .sum::<usize>();
198        let overlap_count = self
199            .overlap
200            .overlap_groups
201            .iter()
202            .map(|group| {
203                group
204                    .prefactor_evaluator
205                    .as_ref()
206                    .map(|evaluators| evaluators.len())
207                    .unwrap_or(0)
208            })
209            .sum::<usize>();
210        stack_count + overlap_count + self.helper_evaluators.len()
211    }
212
213    pub fn new_empty(own_group_position: GraphGroupPosition) -> Self {
214        Self {
215            overlap: OverlapStructure::new_empty(),
216            evaluators: TiVec::new(),
217            helper_evaluators: vec![],
218            generated_mask: TiVec::new(),
219            active_mask: TiVec::new(),
220            raised_data: RaisedEsurfaceData {
221                raised_groups: TiVec::new(),
222                pass_two_evaluator: None,
223            },
224            esurface_map: TiVec::new(),
225            local_esurface_exists: TiVec::new(),
226            own_group_position,
227        }
228    }
229
230    fn ensure_active_raised_esurface(&self, raised_esurface_id: RaisedEsurfaceId) -> Result<()> {
231        if self.active_mask[raised_esurface_id] {
232            return Ok(());
233        }
234
235        Err(color_eyre::eyre::eyre!(
236            "Amplitude threshold evaluator {} was reached at runtime even though generation marked it inactive for the selected orientation subset",
237            raised_esurface_id.0
238        ))
239    }
240
241    pub fn compile(
242        &mut self,
243        path: impl AsRef<Path>,
244        _override_existing: bool,
245        frozen_mode: &crate::settings::global::FrozenCompilationMode,
246    ) -> Result<()> {
247        for (i, e) in self.evaluators.iter_mut_enumerated() {
248            for (cff_index, evaluator_stack) in e.evaluator_stacks.iter_mut() {
249                let order_index = cff_index.left_threshold_order.unwrap() - 1;
250                evaluator_stack.compile(
251                    format!("esurface_{}_order_{}", i.0, order_index + 1),
252                    path.as_ref(),
253                    frozen_mode,
254                )?;
255            }
256        }
257
258        for (group_index, group) in self.overlap.overlap_groups.iter_mut().enumerate() {
259            if let Some(prefactor_evaluators) = group.prefactor_evaluator.as_mut() {
260                for (order_index, prefactor_evaluator) in
261                    prefactor_evaluators.iter_mut().enumerate()
262                {
263                    prefactor_evaluator.borrow_mut().compile_external(
264                        path.as_ref()
265                            .join(format!(
266                                "overlap_prefactor_{group_index}_order_{}",
267                                order_index + 1
268                            ))
269                            .with_extension("cpp"),
270                        format!("overlap_prefactor_{group_index}_order_{}", order_index + 1),
271                        path.as_ref()
272                            .join(format!(
273                                "overlap_prefactor_{group_index}_order_{}",
274                                order_index + 1
275                            ))
276                            .with_extension("so"),
277                        frozen_mode,
278                    )?;
279                }
280            }
281        }
282
283        for (order_index, evaluator) in self.helper_evaluators.iter_mut().enumerate() {
284            evaluator.compile_external(
285                path.as_ref()
286                    .join(format!("threshold_helper_{order_index}"))
287                    .with_extension("cpp"),
288                format!("threshold_helper_{order_index}"),
289                path.as_ref()
290                    .join(format!("threshold_helper_{order_index}"))
291                    .with_extension("so"),
292                frozen_mode,
293            )?;
294        }
295        Ok(())
296    }
297
298    pub(crate) fn for_each_generic_evaluator_mut(
299        &mut self,
300        mut f: impl FnMut(&mut crate::integrands::process::GenericEvaluator) -> Result<()>,
301    ) -> Result<()> {
302        for evaluator in self.evaluators.iter_mut() {
303            for evaluator_stack in evaluator.evaluator_stacks.values_mut() {
304                evaluator_stack.for_each_generic_evaluator_mut(&mut f)?;
305            }
306        }
307
308        for group in &mut self.overlap.overlap_groups {
309            if let Some(prefactor_evaluators) = group.prefactor_evaluator.as_mut() {
310                for prefactor_evaluator in prefactor_evaluators.iter_mut() {
311                    f(prefactor_evaluator.get_mut())?;
312                }
313            }
314        }
315
316        for evaluator in &mut self.helper_evaluators {
317            f(evaluator)?;
318        }
319
320        Ok(())
321    }
322
323    #[allow(clippy::too_many_arguments)]
324    pub fn evaluate<T: FloatLike>(
325        &mut self,
326        momentum_sample: &MomentumSample<T>,
327        graph: &Graph,
328        model: &Model,
329        esurfaces: &EsurfaceCollection,
330        rotation: &Rotation,
331        settings: &RuntimeSettings,
332        param_builder: &mut ParamBuilder<f64>,
333        orientation: SingleOrAllOrientations<'_, OrientationID>,
334        evaluation_metadata: &mut EvaluationMetaData,
335        record_primary_timing: bool,
336    ) -> Result<AmplitudeCountertermEvaluation<T>> {
337        debug!("start evaluate threshold counterterm");
338        let existing_esurfaces = self
339            .overlap
340            .existing_esurfaces
341            .iter()
342            .map(|e| e.0)
343            .collect::<Vec<_>>();
344        debug!("subtracting esurfaces: {:?}", existing_esurfaces);
345        debug!("overlap structure\n: {}", self.overlap);
346
347        let counter_term_builder = CounterTermBuilder::new(
348            graph,
349            model,
350            rotation,
351            settings,
352            esurfaces,
353            momentum_sample,
354            &self.overlap,
355            &self.raised_data,
356            self.own_group_position,
357            &self.esurface_map,
358        );
359
360        let mut result = Complex::new_re(momentum_sample.zero());
361        let mut local_counterterms = Vec::new();
362        let mut radial_root_failed = false;
363
364        for (overlap_group, group) in self.overlap.overlap_groups.iter().enumerate() {
365            let overlap_builder = counter_term_builder.new_overlap_builder(group);
366
367            for existing_esurface_id in group.existing_esurfaces.iter() {
368                let group_esurface_id = self.overlap.existing_esurfaces[*existing_esurface_id];
369                if self
370                    .local_esurface_exists
371                    .get(group_esurface_id)
372                    .is_some_and(|exists| !*exists)
373                {
374                    continue;
375                }
376
377                let Some(esurface_builder) =
378                    overlap_builder.new_esurface_builder(*existing_esurface_id)
379                else {
380                    continue;
381                };
382
383                let raised_esurface_id = esurface_builder.raised_esurface_id;
384                self.ensure_active_raised_esurface(raised_esurface_id)?;
385                let radial_root_identity = Self::radial_root_identity(
386                    &graph.name,
387                    overlap_group,
388                    raised_esurface_id,
389                    rotation,
390                );
391                let Some(rstar_solution) = esurface_builder.solve_rstar(
392                    &radial_root_identity,
393                    &mut evaluation_metadata.radial_root_diagnostics,
394                ) else {
395                    evaluation_metadata.record_threshold_counterterm_error(format!(
396                        "amplitude graph '{}' overlap group {} raised E-surface {} failed center or radial-root validation in probe rotation {}",
397                        graph.name,
398                        overlap_group,
399                        raised_esurface_id.0,
400                        rotation.method,
401                    ));
402                    radial_root_failed = true;
403                    continue;
404                };
405                if radial_root_failed {
406                    continue;
407                }
408                let single_result = rstar_solution.rstar_samples().evaluate(
409                    param_builder,
410                    orientation,
411                    evaluation_metadata,
412                    record_primary_timing,
413                    &mut self.evaluators[raised_esurface_id],
414                    &mut self.helper_evaluators,
415                )?;
416
417                if !single_result.is_zero() {
418                    //    debug!(
419                    //        "Param Builder for {}:\n{}",
420                    //        existing_esurface_id, param_builder
421                    //    );
422                    debug!(
423                        "Counterterm for esurface {}: {:+16e}",
424                        existing_esurface_id, single_result
425                    );
426                }
427
428                local_counterterms.push(AmplitudeLocalCountertermEvaluation {
429                    esurface_id: raised_esurface_id,
430                    overlap_group,
431                    value: single_result.clone(),
432                });
433                result += single_result;
434            }
435        }
436        if radial_root_failed {
437            return Ok(AmplitudeCountertermEvaluation {
438                total: Complex::new_re(F::from_f64(f64::NAN)),
439                local_counterterms,
440            });
441        }
442        Ok(AmplitudeCountertermEvaluation {
443            total: result,
444            local_counterterms,
445        })
446    }
447
448    #[allow(clippy::too_many_arguments)]
449    pub fn kinematics_for_approach<T: FloatLike>(
450        &mut self,
451        momentum_sample: &MomentumSample<T>,
452        graph: &Graph,
453        model: &Model,
454        esurfaces: &EsurfaceCollection,
455        rotation: &Rotation,
456        settings: &RuntimeSettings,
457    ) -> Result<OverlapStructureWithKinematics<T>> {
458        let counter_term_builder = CounterTermBuilder::new(
459            graph,
460            model,
461            rotation,
462            settings,
463            esurfaces,
464            momentum_sample,
465            &self.overlap,
466            &self.raised_data,
467            self.own_group_position,
468            &self.esurface_map,
469        );
470
471        let mut overlap_sturcture_with_kinematics = OverlapStructureWithKinematics {
472            existing_esurfaces: self.overlap.existing_esurfaces.clone(),
473            overlap_groups_with_kinematics: Vec::new(),
474        };
475        let mut radial_root_diagnostics = RadialRootDiagnostics::default();
476
477        for (overlap_group, group) in self.overlap.overlap_groups.iter().enumerate() {
478            let overlap_builder = counter_term_builder.new_overlap_builder(group);
479
480            let mut loop_momenta_at_esurface: TiVec<ExistingEsurfaceId, Option<MomentumSample<T>>> =
481                ti_vec![];
482            for existing_esurface_id in group.existing_esurfaces.iter() {
483                let single_result = overlap_builder
484                    .new_esurface_builder(*existing_esurface_id)
485                    .map(|esurface_builder| -> Result<_> {
486                        self.ensure_active_raised_esurface(esurface_builder.raised_esurface_id)?;
487                        let raised_esurface_id = esurface_builder.raised_esurface_id;
488                        let radial_root_identity = Self::radial_root_identity(
489                            &graph.name,
490                            overlap_group,
491                            raised_esurface_id,
492                            rotation,
493                        );
494                        let rstar_sample = esurface_builder
495                            .solve_rstar(
496                                &radial_root_identity,
497                                &mut radial_root_diagnostics,
498                            )
499                            .ok_or_else(|| {
500                                eyre!(
501                                    "Could not construct threshold-counterterm kinematics for raised E-surface {} in probe rotation {}",
502                                    raised_esurface_id.0,
503                                    rotation.method,
504                                )
505                            })?
506                            .rstar_samples();
507                        Result::Ok(rstar_sample.rstar_sample)
508                    })
509                    .transpose()?;
510
511                loop_momenta_at_esurface.push(single_result);
512            }
513
514            overlap_sturcture_with_kinematics
515                .overlap_groups_with_kinematics
516                .push(OverlapGroupWithKinematics {
517                    overlap_group: group.clone(),
518                    loop_momenta_at_esurface,
519                });
520        }
521
522        Ok(overlap_sturcture_with_kinematics)
523    }
524}
525
526pub struct OverlapGroupWithKinematics<T: FloatLike> {
527    pub overlap_group: OverlapGroup,
528    pub loop_momenta_at_esurface: TiVec<ExistingEsurfaceId, Option<MomentumSample<T>>>,
529}
530
531pub struct OverlapStructureWithKinematics<T: FloatLike> {
532    pub existing_esurfaces: ExistingEsurfaces,
533    pub overlap_groups_with_kinematics: Vec<OverlapGroupWithKinematics<T>>,
534}
535
536struct CounterTermBuilder<'a, T: FloatLike> {
537    overlap_structure: &'a OverlapStructure,
538    raised_data: &'a RaisedEsurfaceData,
539    own_group_position: GraphGroupPosition,
540    esurface_map: &'a TiVec<GroupEsurfaceId, TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>>,
541    real_mass_vector: EdgeVec<F<T>>,
542    e_cm: F<T>,
543    graph: &'a Graph,
544    rotation_for_overlap: &'a Rotation,
545    settings: &'a RuntimeSettings,
546    esurface_collection: &'a EsurfaceCollection,
547    sample: &'a MomentumSample<T>,
548}
549
550impl<'a, T: FloatLike> CounterTermBuilder<'a, T> {
551    #[allow(clippy::too_many_arguments)]
552    fn new(
553        graph: &'a Graph,
554        model: &'a Model,
555        rotation_for_overlap: &'a Rotation,
556        settings: &'a RuntimeSettings,
557        esurface_collection: &'a EsurfaceCollection,
558        sample: &'a MomentumSample<T>,
559        overlap_structure: &'a OverlapStructure,
560        raised_data: &'a RaisedEsurfaceData,
561        own_group_position: GraphGroupPosition,
562        esurface_map: &'a TiVec<
563            GroupEsurfaceId,
564            TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>,
565        >,
566    ) -> Self {
567        let real_mass_vector = graph.get_real_mass_vector(model);
568        let e_cm = F::from_f64(settings.kinematics.e_cm);
569
570        Self {
571            real_mass_vector,
572            e_cm,
573            graph,
574            rotation_for_overlap,
575            settings,
576            esurface_collection,
577            overlap_structure,
578            raised_data,
579            sample,
580            own_group_position,
581            esurface_map,
582        }
583    }
584
585    fn new_overlap_builder(&'a self, overlap_group: &'a OverlapGroup) -> OverlapBuilder<'a, T> {
586        let center = &overlap_group.center;
587
588        // Overlap construction stores amplitude centers in the identity frame. Rotate the center
589        // exactly once here, alongside the sample rotation used by this stability probe.
590        let (unrotated_center, rotated_center) = (
591            center.cast(),
592            center.rotate(self.rotation_for_overlap).cast(),
593        );
594
595        let shifted_loop_momenta = self.sample.loop_moms() - &rotated_center;
596        let radius = shifted_loop_momenta.hyper_radius_squared(None).sqrt();
597        let unit_shifted_momenta = shifted_loop_momenta.rescale(&radius.inv(), None);
598
599        OverlapBuilder {
600            counterterm_builder: self,
601            overlap_group,
602            rotated_center,
603            _unrotated_center: unrotated_center,
604            unit_shifted_momenta,
605            radius,
606        }
607    }
608}
609
610struct OverlapBuilder<'a, T: FloatLike> {
611    counterterm_builder: &'a CounterTermBuilder<'a, T>,
612    overlap_group: &'a OverlapGroup,
613    /// The center after its single identity-to-probe-frame rotation.
614    rotated_center: LoopMomenta<F<T>>,
615    /// The stored identity-frame center, retained for frame diagnostics.
616    _unrotated_center: LoopMomenta<F<T>>,
617    unit_shifted_momenta: LoopMomenta<F<T>>,
618    radius: F<T>,
619}
620
621impl<'a, T: FloatLike> OverlapBuilder<'a, T> {
622    fn new_esurface_builder(
623        &'a self,
624        existing_esurface_id: ExistingEsurfaceId,
625    ) -> Option<EsurfaceCTBuilder<'a, T>> {
626        let group_esurface_id = self
627            .counterterm_builder
628            .overlap_structure
629            .existing_esurfaces[existing_esurface_id];
630
631        let raised_esurface_id = self.counterterm_builder.esurface_map[group_esurface_id]
632            [self.counterterm_builder.own_group_position];
633
634        raised_esurface_id.map(|raised_esurface_id| {
635            let esurface_id = self.counterterm_builder.raised_data.raised_groups
636                [raised_esurface_id]
637                .esurface_ids[0];
638
639            EsurfaceCTBuilder {
640                overlap_builder: self,
641                _existing_esurface_id: existing_esurface_id,
642                group_esurface_id,
643                esurface: &self.counterterm_builder.esurface_collection[esurface_id],
644                esurface_id,
645                raised_esurface_id,
646            }
647        })
648    }
649}
650
651struct EsurfaceCTBuilder<'a, T: FloatLike> {
652    overlap_builder: &'a OverlapBuilder<'a, T>,
653    _existing_esurface_id: ExistingEsurfaceId,
654    group_esurface_id: GroupEsurfaceId,
655    esurface: &'a Esurface,
656    esurface_id: EsurfaceID,
657    raised_esurface_id: RaisedEsurfaceId,
658}
659
660impl<'a, T: FloatLike> EsurfaceCTBuilder<'a, T> {
661    fn solve_rstar(
662        self,
663        radial_root_identity: &RadialRootIdentity,
664        radial_root_diagnostics: &mut RadialRootDiagnostics,
665    ) -> Option<RstarSolution<'a, T>> {
666        let mut all_center_values_valid = true;
667        let center_surface_values = self
668            .overlap_builder
669            .overlap_group
670            .existing_esurfaces
671            .iter()
672            .map(|&existing_esurface_id| {
673                let group_esurface_id = self
674                    .overlap_builder
675                    .counterterm_builder
676                    .overlap_structure
677                    .existing_esurfaces[existing_esurface_id];
678
679                match self.overlap_builder.counterterm_builder.esurface_map[group_esurface_id]
680                    [self.overlap_builder.counterterm_builder.own_group_position]
681                {
682                    Some(raised_esurface_id) => {
683                        let esurface_id = self
684                            .overlap_builder
685                            .counterterm_builder
686                            .raised_data
687                            .raised_groups[raised_esurface_id]
688                            .esurface_ids[0];
689                        let esurface =
690                            &self.overlap_builder.counterterm_builder.esurface_collection
691                                [esurface_id];
692                        let value = esurface.compute_from_momenta(
693                            &self
694                                .overlap_builder
695                                .counterterm_builder
696                                .graph
697                                .loop_momentum_basis,
698                            &self.overlap_builder.counterterm_builder.real_mass_vector,
699                            &self.overlap_builder.rotated_center,
700                            self.overlap_builder
701                                .counterterm_builder
702                                .sample
703                                .external_moms(),
704                        );
705                        let is_valid = esurface_value_is_strictly_inside(
706                            &value,
707                            &self.overlap_builder.counterterm_builder.e_cm,
708                        );
709                        all_center_values_valid &= is_valid;
710                        format!(
711                            "existing={} group={} raised={} local={} edges={:?} value={:+16e} inside={}",
712                            usize::from(existing_esurface_id),
713                            group_esurface_id.0,
714                            raised_esurface_id.0,
715                            esurface_id.0,
716                            esurface.energies,
717                            value,
718                            is_valid
719                        )
720                    }
721                    None => {
722                        format!(
723                            "existing={} group={} absent_in_current_graph",
724                            usize::from(existing_esurface_id),
725                            group_esurface_id.0
726                        )
727                    }
728                }
729            })
730            .collect::<Vec<_>>();
731        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect, #center;
732            stage = "amplitude_threshold_center_values",
733            graph = %self.overlap_builder.counterterm_builder.graph.name,
734            selected_existing_esurface_id = usize::from(self._existing_esurface_id),
735            selected_group_esurface_id = self.group_esurface_id.0,
736            selected_raised_esurface_id = self.raised_esurface_id.0,
737            selected_esurface_id = self.esurface_id.0,
738            rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
739            center_provenance = "identity_frame_rotated_once",
740            overlap_group_size = self.overlap_builder.overlap_group.existing_esurfaces.len(),
741            radius = %format!("{:+16e}", self.overlap_builder.radius),
742            file.rotated_center = %format!("{}", self.overlap_builder.rotated_center),
743            file.surface_values = %center_surface_values.join("\n"),
744            "amplitude threshold center values"
745        );
746
747        if !all_center_values_valid {
748            warn!(
749                graph = %self.overlap_builder.counterterm_builder.graph.name,
750                selected_esurface_id = self.esurface_id.0,
751                rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
752                center_provenance = "identity_frame_rotated_once",
753                center = %self.overlap_builder.rotated_center,
754                surface_values = %center_surface_values.join("; "),
755                "refusing to evaluate an amplitude threshold counterterm with an invalid probe-frame overlap center"
756            );
757            return None;
758        }
759
760        let (raw_radius_guess, _) = self.esurface.get_radius_guess(
761            &self.overlap_builder.unit_shifted_momenta,
762            self.overlap_builder
763                .counterterm_builder
764                .sample
765                .external_moms(),
766            &self
767                .overlap_builder
768                .counterterm_builder
769                .graph
770                .loop_momentum_basis,
771        );
772
773        let function = |r: &_| {
774            self.esurface.compute_self_and_r_derivative(
775                r,
776                &self.overlap_builder.unit_shifted_momenta,
777                &self.overlap_builder.rotated_center,
778                self.overlap_builder
779                    .counterterm_builder
780                    .sample
781                    .external_moms(),
782                &self.overlap_builder.counterterm_builder.real_mass_vector,
783                &self
784                    .overlap_builder
785                    .counterterm_builder
786                    .graph
787                    .loop_momentum_basis,
788            )
789        };
790
791        let zero = raw_radius_guess.zero();
792        let mut radius_guess = raw_radius_guess.clone();
793        if radius_guess.is_nan() || radius_guess.is_infinite() || radius_guess <= zero {
794            radius_guess = self.overlap_builder.counterterm_builder.e_cm.clone();
795        }
796        let tolerance = F::from_f64(
797            self.overlap_builder
798                .counterterm_builder
799                .settings
800                .subtraction
801                .radial_root_residual_tolerance,
802        );
803        let solution = match radial_root_diagnostics.solve(
804            radial_root_identity,
805            &zero,
806            &radius_guess,
807            function,
808            &tolerance,
809            MAX_ITERATIONS,
810            64,
811            &self.overlap_builder.counterterm_builder.e_cm,
812        ) {
813            Ok(solution) => solution,
814            Err(error) => {
815                warn!(
816                    graph = %self.overlap_builder.counterterm_builder.graph.name,
817                    esurface_id = self.esurface_id.0,
818                    rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
819                    center_provenance = "identity_frame_rotated_once",
820                    raw_radius_guess = %raw_radius_guess,
821                    radius_guess = %radius_guess,
822                    error = %error,
823                    "refusing to evaluate an amplitude threshold counterterm with an invalid radial solution"
824                );
825                return None;
826            }
827        };
828        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
829            stage = "amplitude_threshold_rstar_solution",
830            graph = %self.overlap_builder.counterterm_builder.graph.name,
831            existing_esurface_id = %self._existing_esurface_id,
832            group_esurface_id = self.group_esurface_id.0,
833            raised_esurface_id = self.raised_esurface_id.0,
834            esurface_id = self.esurface_id.0,
835            rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
836            center_provenance = "identity_frame_rotated_once",
837            radius_guess = %format!("{:+16e}", radius_guess),
838            radius_star = %format!("{:+16e}", solution.solution),
839            derivative = %format!("{:+16e}", solution.derivative_at_solution),
840            error = %format!("{:+16e}", solution.error_of_function),
841            iterations = solution.num_iterations_used,
842            nonfinite = false,
843            "amplitude threshold rstar solution"
844        );
845
846        Some(RstarSolution {
847            esurface_ct_builder: self,
848            solution,
849        })
850    }
851}
852
853struct RstarSolution<'a, T: FloatLike> {
854    esurface_ct_builder: EsurfaceCTBuilder<'a, T>,
855    solution: NewtonIterationResult<T>,
856}
857
858impl<'a, T: FloatLike> RstarSolution<'a, T> {
859    fn rstar_samples(self) -> RstarSample<'a, T> {
860        let rstar_loop_momenta = &self
861            .esurface_ct_builder
862            .overlap_builder
863            .unit_shifted_momenta
864            .rescale(&self.solution.solution, None)
865            + &self.esurface_ct_builder.overlap_builder.rotated_center;
866
867        let mut rstar_sample = self
868            .esurface_ct_builder
869            .overlap_builder
870            .counterterm_builder
871            .sample
872            .clone();
873
874        rstar_sample.sample.loop_moms = rstar_loop_momenta;
875
876        RstarSample {
877            rstar_solution: self,
878            rstar_sample,
879        }
880    }
881}
882
883struct RstarSample<'a, T: FloatLike> {
884    rstar_solution: RstarSolution<'a, T>,
885    rstar_sample: MomentumSample<T>,
886}
887
888impl<'a, T: FloatLike> RstarSample<'a, T> {
889    fn evaluate<'b, 'c: 'b>(
890        self,
891        param_builder: &mut ParamBuilder<f64>,
892        orientations: SingleOrAllOrientations<'a, OrientationID>,
893        evaluation_metadata: &mut EvaluationMetaData,
894        record_primary_timing: bool,
895        ct_evaluator: &mut AmplitudeCountertermEvaluator,
896        helper_evaluators: &mut [GenericEvaluator],
897    ) -> Result<Complex<F<T>>> {
898        let esurface_ct_builder = &self.rstar_solution.esurface_ct_builder;
899        let ct_builder = esurface_ct_builder.overlap_builder.counterterm_builder;
900
901        let esurface_id = esurface_ct_builder.esurface_id;
902
903        let model_params = param_builder
904            .model_values()
905            .iter()
906            .map(|c| Complex::new(F::from_ff64(c.re), F::from_ff64(c.im)))
907            .collect::<Vec<_>>();
908
909        let radius = self
910            .rstar_solution
911            .esurface_ct_builder
912            .overlap_builder
913            .radius
914            .clone();
915
916        let radius_star = self.rstar_solution.solution.solution.clone();
917        let e_cm = &ct_builder.e_cm;
918        let settings = &ct_builder
919            .settings
920            .subtraction
921            .local_ct_settings
922            .uv_localisation;
923
924        let integrated_settings = &ct_builder.settings.subtraction.integrated_ct_settings;
925
926        let uv_damp_plus = evaluate_uv_damper(&radius, &radius_star, e_cm, settings);
927        let uv_damp_minus = evaluate_uv_damper(&-&radius, &radius_star, e_cm, settings);
928
929        debug!("uv_damp_plus: {:?}", uv_damp_plus);
930        debug!("uv_damp_minus: {:?}", uv_damp_minus);
931        debug!("radius: {:?}", radius);
932        debug!("radius_star: {:?}", radius_star);
933
934        let h_function =
935            evaluate_integrated_ct_normalisation(&radius, &radius_star, e_cm, integrated_settings);
936        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
937            stage = "amplitude_threshold_prefactors",
938            graph = %ct_builder.graph.name,
939            esurface_id = esurface_id.0,
940            raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
941            radius = %format!("{:+16e}", radius),
942            radius_star = %format!("{:+16e}", radius_star),
943            derivative = %format!(
944                "{:+16e}",
945                self.rstar_solution.solution.derivative_at_solution
946            ),
947            uv_damp_plus = %format!("{:+16e}", uv_damp_plus),
948            uv_damp_minus = %format!("{:+16e}", uv_damp_minus),
949            h_function = %format!("{:+16e}", h_function),
950            "amplitude threshold prefactors"
951        );
952
953        let debug_diagnostics_enabled = tracing::event_enabled!(tracing::Level::DEBUG);
954        if debug_diagnostics_enabled {
955            let coincidence_tolerance = F::from_f64(1.0e-8) * e_cm;
956            for (candidate_esurface_id, candidate_esurface) in
957                ct_builder.esurface_collection.iter_enumerated()
958            {
959                let value = candidate_esurface.compute_from_momenta(
960                    &ct_builder.graph.loop_momentum_basis,
961                    &ct_builder.real_mass_vector,
962                    self.rstar_sample.loop_moms(),
963                    self.rstar_sample.external_moms(),
964                );
965                if value.abs() < coincidence_tolerance {
966                    let candidate_raised_esurface_id = ct_builder
967                        .raised_data
968                        .raised_groups
969                        .iter_enumerated()
970                        .find_map(|(raised_esurface_id, raised_group)| {
971                            raised_group
972                                .esurface_ids
973                                .contains(&candidate_esurface_id)
974                                .then_some(raised_esurface_id.0)
975                        });
976                    let candidate_atom = candidate_esurface.to_atom(&[]).to_string();
977                    crate::debug_tags!(#integration, #subtraction, #threshold, #inspect, #esurface;
978                        stage = "amplitude_threshold_rstar_coincident_esurface",
979                        graph = %ct_builder.graph.name,
980                        selected_esurface_id = esurface_id.0,
981                        selected_raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
982                        candidate_esurface_id = candidate_esurface_id.0,
983                        candidate_raised_esurface_id = ?candidate_raised_esurface_id,
984                        selected = candidate_esurface_id == esurface_id,
985                        value = %format!("{:+16e}", value),
986                        tolerance = %format!("{:+16e}", coincidence_tolerance),
987                        file.atom = %candidate_atom,
988                        "threshold rstar coincident esurface graph={} selected={} raised={} candidate={} candidate_raised={:?} value={} tol={} atom={}",
989                        ct_builder.graph.name,
990                        esurface_id.0,
991                        esurface_ct_builder.raised_esurface_id.0,
992                        candidate_esurface_id.0,
993                        candidate_raised_esurface_id,
994                        format!("{:+16e}", value),
995                        format!("{:+16e}", coincidence_tolerance),
996                        candidate_atom
997                    );
998                }
999            }
1000        }
1001
1002        let mut total_ct = Complex::new_re(self.rstar_sample.zero());
1003
1004        for (cut_cff_index, evaluator_stack) in ct_evaluator.evaluator_stacks.iter_mut() {
1005            let order_index = cut_cff_index.left_threshold_order.unwrap() - 1;
1006            let (sample_for_order, threshold_params) = if order_index == 0 {
1007                debug!(
1008                    "rescaled loop momenta at rstar:\n{}",
1009                    self.rstar_sample.loop_moms()
1010                );
1011                (
1012                    self.rstar_sample.clone(),
1013                    ThresholdParams {
1014                        radius: DualOrNot::NonDual(radius.clone()),
1015                        radius_star: DualOrNot::NonDual(radius_star.clone()),
1016                        esurface_derivative: DualOrNot::NonDual(
1017                            self.rstar_solution.solution.derivative_at_solution.clone(),
1018                        ),
1019                        uv_damp_plus: DualOrNot::NonDual(uv_damp_plus.clone()),
1020                        uv_damp_minus: DualOrNot::NonDual(uv_damp_minus.clone()),
1021                        h_function: DualOrNot::NonDual(h_function.clone()),
1022                    },
1023                )
1024            } else {
1025                let dual_shape = HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index));
1026                let dual_radius_star = dual_shape.variable(0, radius_star.clone());
1027
1028                let dualized_center = self
1029                    .rstar_solution
1030                    .esurface_ct_builder
1031                    .overlap_builder
1032                    .rotated_center
1033                    .iter()
1034                    .map(|momentum| {
1035                        momentum.map_ref(&|value| new_constant(&dual_radius_star, value))
1036                    })
1037                    .collect::<LoopMomenta<_>>();
1038                let dual_loop_momenta = self
1039                    .rstar_solution
1040                    .esurface_ct_builder
1041                    .overlap_builder
1042                    .unit_shifted_momenta
1043                    .rescale_with_hyper_dual(&dual_radius_star, None)
1044                    .iter()
1045                    .zip(dualized_center.iter())
1046                    .map(|(momentum, center)| momentum.clone() + center.clone())
1047                    .collect::<LoopMomenta<_>>();
1048                debug!("rescaled loop momenta at rstar:\n{}", dual_loop_momenta);
1049                let mut sample_with_duals = self.rstar_sample.clone();
1050                sample_with_duals.sample.dual_loop_moms = Some(dual_loop_momenta);
1051
1052                (
1053                    sample_with_duals,
1054                    ThresholdParams {
1055                        radius: DualOrNot::Dual(new_constant(&dual_radius_star, &radius)),
1056                        radius_star: DualOrNot::Dual(dual_radius_star.clone()),
1057                        esurface_derivative: DualOrNot::Dual(new_constant(
1058                            &dual_radius_star,
1059                            &self.rstar_solution.solution.derivative_at_solution.clone(),
1060                        )),
1061                        uv_damp_plus: DualOrNot::Dual(new_constant(
1062                            &dual_radius_star,
1063                            &uv_damp_plus,
1064                        )),
1065                        uv_damp_minus: DualOrNot::Dual(new_constant(
1066                            &dual_radius_star,
1067                            &uv_damp_minus,
1068                        )),
1069                        h_function: DualOrNot::Dual(new_constant(&dual_radius_star, &h_function)),
1070                    },
1071                )
1072            };
1073
1074            let esurface_derivatives = if order_index == 0 {
1075                DualOrNot::NonDual(self.rstar_solution.solution.derivative_at_solution.clone())
1076            } else {
1077                let dual_shape_for_esurface =
1078                    HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index + 1));
1079                let dual_radius_star_for_esurface =
1080                    dual_shape_for_esurface.variable(0, radius_star.clone());
1081                let dualized_center_for_esurface = self
1082                    .rstar_solution
1083                    .esurface_ct_builder
1084                    .overlap_builder
1085                    .rotated_center
1086                    .iter()
1087                    .map(|momentum| {
1088                        momentum
1089                            .map_ref(&|value| new_constant(&dual_radius_star_for_esurface, value))
1090                    })
1091                    .collect::<LoopMomenta<_>>();
1092                let dual_loop_momenta_for_esurface = self
1093                    .rstar_solution
1094                    .esurface_ct_builder
1095                    .overlap_builder
1096                    .unit_shifted_momenta
1097                    .rescale_with_hyper_dual(&dual_radius_star_for_esurface, None)
1098                    .iter()
1099                    .zip(dualized_center_for_esurface.iter())
1100                    .map(|(momentum, center)| momentum.clone() + center.clone())
1101                    .collect::<LoopMomenta<_>>();
1102
1103                let dualized_externals = self
1104                    .rstar_sample
1105                    .external_moms()
1106                    .iter()
1107                    .map(|momentum| FourMomentum {
1108                        temporal: Energy {
1109                            value: new_constant(
1110                                &dual_radius_star_for_esurface,
1111                                &momentum.temporal.value,
1112                            ),
1113                        },
1114                        spatial: momentum
1115                            .spatial
1116                            .map_ref(&|value| new_constant(&dual_radius_star_for_esurface, value)),
1117                    })
1118                    .collect();
1119
1120                DualOrNot::Dual(
1121                    self.rstar_solution
1122                        .esurface_ct_builder
1123                        .esurface
1124                        .compute_from_dual_momenta(
1125                            &ct_builder.graph.loop_momentum_basis,
1126                            &ct_builder.real_mass_vector,
1127                            &dual_loop_momenta_for_esurface,
1128                            &dualized_externals,
1129                        ),
1130                )
1131            };
1132
1133            let params = T::get_parameters(
1134                param_builder,
1135                (false, false),
1136                ct_builder.graph,
1137                &sample_for_order,
1138                ct_builder.settings.kinematics.externals.get_helicities(),
1139                &ct_builder.settings.additional_params(),
1140                Some(&threshold_params),
1141                None,
1142                None,
1143            );
1144            let params_slice = params.as_slice();
1145            let params_nonfinite_count = params_slice
1146                .iter()
1147                .filter(|value| {
1148                    value.re.is_nan()
1149                        || value.re.is_infinite()
1150                        || value.im.is_nan()
1151                        || value.im.is_infinite()
1152                })
1153                .count();
1154            let first_params_nonfinite_index = params_slice.iter().position(|value| {
1155                value.re.is_nan()
1156                    || value.re.is_infinite()
1157                    || value.im.is_nan()
1158                    || value.im.is_infinite()
1159            });
1160            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
1161                stage = "amplitude_threshold_pass_one_params",
1162                graph = %ct_builder.graph.name,
1163                esurface_id = esurface_id.0,
1164                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
1165                order = order_index + 1,
1166                params_len = params_slice.len(),
1167                params_nonfinite_count,
1168                first_params_nonfinite_index = ?first_params_nonfinite_index,
1169                "amplitude threshold pass one params"
1170            );
1171
1172            let pass_one_result = evaluator_stack
1173                .evaluate(
1174                    params,
1175                    orientations,
1176                    ct_builder.settings,
1177                    evaluation_metadata,
1178                    record_primary_timing,
1179                )
1180                .expect("Amplitude counterterm evaluator stack failed")
1181                .pop()
1182                .unwrap();
1183
1184            let prefactor = self.evaluate_multichanneling_prefactor(
1185                &sample_for_order,
1186                &model_params,
1187                evaluation_metadata,
1188                record_primary_timing,
1189                order_index,
1190            );
1191
1192            let raw_pass_one_is_nonfinite = match &pass_one_result {
1193                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
1194                    value.re.is_nan()
1195                        || value.re.is_infinite()
1196                        || value.im.is_nan()
1197                        || value.im.is_infinite()
1198                }),
1199                DualOrNot::NonDual(non_dual_result) => {
1200                    non_dual_result.re.is_nan()
1201                        || non_dual_result.re.is_infinite()
1202                        || non_dual_result.im.is_nan()
1203                        || non_dual_result.im.is_infinite()
1204                }
1205            };
1206            let prefactor_is_nonfinite = match &prefactor {
1207                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
1208                    value.re.is_nan()
1209                        || value.re.is_infinite()
1210                        || value.im.is_nan()
1211                        || value.im.is_infinite()
1212                }),
1213                DualOrNot::NonDual(non_dual_result) => {
1214                    non_dual_result.re.is_nan()
1215                        || non_dual_result.re.is_infinite()
1216                        || non_dual_result.im.is_nan()
1217                        || non_dual_result.im.is_infinite()
1218                }
1219            };
1220            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
1221                stage = "amplitude_threshold_pass_one_raw",
1222                graph = %ct_builder.graph.name,
1223                esurface_id = esurface_id.0,
1224                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
1225                order = order_index + 1,
1226                raw_result = %format!("{pass_one_result}"),
1227                prefactor = %format!("{prefactor}"),
1228                raw_nonfinite = raw_pass_one_is_nonfinite,
1229                prefactor_nonfinite = prefactor_is_nonfinite,
1230                "amplitude threshold pass one raw"
1231            );
1232
1233            let pass_one_result = multiply_dual_or_not_complex(pass_one_result, &prefactor);
1234            let pass_one_is_nonfinite = match &pass_one_result {
1235                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
1236                    value.re.is_nan()
1237                        || value.re.is_infinite()
1238                        || value.im.is_nan()
1239                        || value.im.is_infinite()
1240                }),
1241                DualOrNot::NonDual(non_dual_result) => {
1242                    non_dual_result.re.is_nan()
1243                        || non_dual_result.re.is_infinite()
1244                        || non_dual_result.im.is_nan()
1245                        || non_dual_result.im.is_infinite()
1246                }
1247            };
1248            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
1249                stage = "amplitude_threshold_pass_one",
1250                graph = %ct_builder.graph.name,
1251                esurface_id = esurface_id.0,
1252                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
1253                order = order_index + 1,
1254                prefactor = %format!("{prefactor}"),
1255                result = %format!("{pass_one_result}"),
1256                nonfinite = pass_one_is_nonfinite,
1257                "amplitude threshold pass one"
1258            );
1259
1260            debug!(
1261                "Pass one result for esurface {} order {}: {}",
1262                esurface_id.0,
1263                order_index + 1,
1264                pass_one_result
1265            );
1266
1267            let mut params_for_pass_two = vec![];
1268            match pass_one_result {
1269                DualOrNot::Dual(dual_result) => {
1270                    params_for_pass_two
1271                        .extend_from_slice(&extract_t_derivatives_complex(dual_result));
1272                }
1273                DualOrNot::NonDual(non_dual_result) => {
1274                    params_for_pass_two.push(non_dual_result);
1275                }
1276            }
1277
1278            match esurface_derivatives {
1279                DualOrNot::Dual(dual_e_surface) => {
1280                    extract_t_derivatives(dual_e_surface)[1..]
1281                        .iter()
1282                        .for_each(|value| params_for_pass_two.push(Complex::new_re(value.clone())));
1283                }
1284                DualOrNot::NonDual(non_dual_e_surface) => {
1285                    params_for_pass_two.push(Complex::new_re(non_dual_e_surface));
1286                }
1287            }
1288
1289            params_for_pass_two.push(Complex::new_re(radius.clone()));
1290            params_for_pass_two.push(Complex::new_re(radius_star.clone()));
1291            params_for_pass_two.push(Complex::new_re(uv_damp_plus.clone()));
1292            params_for_pass_two.push(Complex::new_re(uv_damp_minus.clone()));
1293            params_for_pass_two.push(Complex::new_re(h_function.clone()));
1294
1295            let pass_two_result = evaluate_evaluator_single(
1296                &mut helper_evaluators[order_index],
1297                &params_for_pass_two,
1298                evaluation_metadata,
1299                record_primary_timing,
1300            );
1301
1302            debug!(
1303                "Pass two result for esurface {} order {}: {}",
1304                esurface_id.0,
1305                order_index + 1,
1306                pass_two_result
1307            );
1308            let pass_two_is_nonfinite = pass_two_result.re.is_nan()
1309                || pass_two_result.re.is_infinite()
1310                || pass_two_result.im.is_nan()
1311                || pass_two_result.im.is_infinite();
1312            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
1313                stage = "amplitude_threshold_pass_two",
1314                graph = %ct_builder.graph.name,
1315                esurface_id = esurface_id.0,
1316                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
1317                order = order_index + 1,
1318                result = %format!("{:+16e}", pass_two_result),
1319                nonfinite = pass_two_is_nonfinite,
1320                "amplitude threshold pass two"
1321            );
1322
1323            total_ct += pass_two_result;
1324        }
1325
1326        debug!(
1327            ct_eval = format!("{:+16e}", total_ct),
1328            "esurface {}", esurface_id.0
1329        );
1330        let total_ct_is_nonfinite = total_ct.re.is_nan()
1331            || total_ct.re.is_infinite()
1332            || total_ct.im.is_nan()
1333            || total_ct.im.is_infinite();
1334        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
1335            stage = "amplitude_threshold_total",
1336            graph = %ct_builder.graph.name,
1337            esurface_id = esurface_id.0,
1338            raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
1339            result = %format!("{:+16e}", total_ct),
1340            nonfinite = total_ct_is_nonfinite,
1341            "amplitude threshold total"
1342        );
1343
1344        Ok(total_ct)
1345    }
1346
1347    fn evaluate_multichanneling_prefactor(
1348        &self,
1349        momentum_sample: &MomentumSample<T>,
1350        model_params: &[Complex<F<T>>],
1351        evaluation_metadata: &mut EvaluationMetaData,
1352        record_primary_timing: bool,
1353        order_index: usize,
1354    ) -> DualOrNot<Complex<F<T>>> {
1355        let overlap_builder = self.rstar_solution.esurface_ct_builder.overlap_builder;
1356        let overlap = overlap_builder.counterterm_builder.overlap_structure;
1357
1358        if overlap.overlap_groups.len() < 2 {
1359            return DualOrNot::NonDual(Complex::new_re(momentum_sample.one()));
1360        }
1361
1362        let multiplicative_offset = momentum_sample
1363            .sample
1364            .dual_loop_moms
1365            .as_ref()
1366            .map(|dual_loop_moms| dual_loop_moms.first().unwrap().px.values.len())
1367            .unwrap_or(1);
1368        let zero = Complex::new_re(momentum_sample.zero());
1369        let mut params = if let Some(dual_loop_moms) = &momentum_sample.sample.dual_loop_moms {
1370            dual_loop_moms
1371                .iter()
1372                .flat_map(|mom| {
1373                    [
1374                        mom.px.values.clone(),
1375                        mom.py.values.clone(),
1376                        mom.pz.values.clone(),
1377                    ]
1378                    .into_iter()
1379                    .flatten()
1380                    .map(Complex::new_re)
1381                    .collect::<Vec<_>>()
1382                })
1383                .collect::<Vec<_>>()
1384        } else {
1385            momentum_sample
1386                .loop_moms()
1387                .iter()
1388                .flat_map(|momentum| {
1389                    [
1390                        momentum.px.clone(),
1391                        momentum.py.clone(),
1392                        momentum.pz.clone(),
1393                    ]
1394                    .into_iter()
1395                    .map(Complex::new_re)
1396                    .collect::<Vec<_>>()
1397                })
1398                .collect::<Vec<_>>()
1399        };
1400
1401        params.extend(momentum_sample.external_moms().iter().flat_map(|momentum| {
1402            [
1403                momentum.temporal.value.clone(),
1404                momentum.spatial.px.clone(),
1405                momentum.spatial.py.clone(),
1406                momentum.spatial.pz.clone(),
1407            ]
1408            .into_iter()
1409            .flat_map(|value| {
1410                std::iter::once(Complex::new_re(value))
1411                    .chain((1..multiplicative_offset).map(|_| zero.clone()))
1412                    .collect::<Vec<_>>()
1413            })
1414            .collect::<Vec<_>>()
1415        }));
1416        params.extend(model_params.iter().cloned().flat_map(|value| {
1417            std::iter::once(value)
1418                .chain((1..multiplicative_offset).map(|_| zero.clone()))
1419                .collect::<Vec<_>>()
1420        }));
1421
1422        let evaluator = overlap_builder
1423            .overlap_group
1424            .prefactor_evaluator
1425            .as_ref()
1426            .unwrap()
1427            .get(order_index)
1428            .expect("missing overlap prefactor evaluator for amplitude threshold order");
1429
1430        evaluate_evaluator(
1431            &mut evaluator.borrow_mut(),
1432            &params,
1433            evaluation_metadata,
1434            record_primary_timing,
1435        )
1436        .pop()
1437        .expect("overlap prefactor evaluator should return exactly one value")
1438    }
1439}
1440
1441#[cfg(test)]
1442mod tests {
1443    use super::{AmplitudeCountertermAtom, AmplitudeCountertermData};
1444    use crate::{
1445        cff::{CutCFFIndex, esurface::RaisedEsurfaceId},
1446        graph::GraphGroupPosition,
1447        uv::Integrands,
1448    };
1449    use symbolica::{atom::Atom, symbol};
1450    use typed_index_collections::ti_vec;
1451
1452    #[test]
1453    fn empty_amplitude_counterterm_atom_is_not_generated() {
1454        let atom = AmplitudeCountertermAtom {
1455            parametric: std::iter::empty().collect(),
1456        };
1457
1458        assert!(!atom.is_generated());
1459    }
1460
1461    #[test]
1462    fn non_empty_amplitude_counterterm_atom_is_generated() {
1463        let atom = AmplitudeCountertermAtom {
1464            parametric: Integrands::from_iter([(
1465                CutCFFIndex::new_all_none(),
1466                Atom::var(symbol!("x")),
1467            )]),
1468        };
1469
1470        assert!(atom.is_generated());
1471    }
1472
1473    #[test]
1474    fn zero_like_amplitude_counterterm_atom_is_zero() {
1475        let atom = AmplitudeCountertermAtom {
1476            parametric: Integrands::from_iter([(
1477                CutCFFIndex::new_all_none(),
1478                Atom::var(symbol!("x")),
1479            )]),
1480        };
1481
1482        let zeroed = atom.zero_like();
1483
1484        assert_eq!(
1485            zeroed.parametric,
1486            Integrands::from_iter([(CutCFFIndex::new_all_none(), Atom::Zero)])
1487        );
1488    }
1489
1490    #[test]
1491    fn inactive_amplitude_esurface_guard_reports_runtime_access() {
1492        let mut data = AmplitudeCountertermData::new_empty(GraphGroupPosition(0));
1493        data.active_mask = ti_vec![false];
1494
1495        let error = data
1496            .ensure_active_raised_esurface(RaisedEsurfaceId(0))
1497            .unwrap_err();
1498        assert!(error.to_string().contains("generation marked it inactive"));
1499    }
1500}