Skip to main content

gammalooprs/subtraction/
amplitude_counterterm.rs

1use std::{collections::BTreeMap, path::Path, sync::Arc};
2
3use bincode_trait_derive::{Decode, Encode};
4use color_eyre::Result;
5use eyre::{Context, 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            ExistingThresholds, GroupEsurfaceId, RaisedEsurfaceData, RaisedEsurfaceGroup,
18            RaisedEsurfaceId, esurface_value_is_strictly_inside,
19        },
20        expression::OrientationID,
21    },
22    graph::{FeynmanGraph, Graph, GraphGroupPosition, LmbIndex, LoopMomentumBasis},
23    integrands::{
24        evaluation::EvaluationMetaData,
25        process::{
26            GenericEvaluator, ParamBuilder, ThresholdParams,
27            evaluators::{
28                EvaluatorStack, SingleOrAllOrientations, evaluate_evaluator,
29                evaluate_evaluator_single,
30            },
31            threshold_multiplier::{
32                ThresholdMultiplierEvaluatorCollection, ThresholdMultiplierInputWorkspace,
33            },
34        },
35    },
36    model::Model,
37    momentum::{
38        Energy, FourMomentum, Rotation, ThreeMomentum,
39        sample::{
40            BareMomentumSample, ExternalFourMomenta, LoopIndex, LoopMomenta, MomentumSample,
41            SubspaceData,
42        },
43    },
44    processes::{
45        EvaluatorBuildTimings, ResolvedThresholdCountertermVariant, SingleThresholdPieces,
46        ThresholdCountertermComponentKind, ThresholdCountertermMetadataRegistry,
47        ThresholdCountertermSide, ThresholdCountertermVariantId,
48    },
49    settings::{GlobalSettings, RuntimeSettings},
50    subtraction::{
51        evaluate_integrated_ct_normalisation, evaluate_uv_damper,
52        overlap::{
53            OverlapGroup, OverlapInput, OverlapStructure, SingleGraphOverlapData,
54            find_maximal_overlap,
55        },
56        overlap_subspace::{
57            OverlapInput as SubspaceOverlapInput, OverlapKinematics,
58            OverlapStructure as SubspaceOverlapStructure,
59            find_maximal_overlap as find_maximal_subspace_overlap,
60        },
61    },
62    utils::{
63        F, FloatLike, RuntimeCache,
64        hyperdual_utils::{
65            DualOrNot, extract_t_derivatives, extract_t_derivatives_complex, new_constant,
66            shape_from_cut_cff_index, simple_n_deriv_shape,
67        },
68        newton_solver::{NewtonIterationResult, RadialRootDiagnostics, RadialRootIdentity},
69    },
70    uv::Integrands,
71};
72use symbolica::domains::dual::{DualNumberStructure, HyperDual};
73
74const MAX_ITERATIONS: usize = 40;
75
76fn multiply_dual_or_not_complex<T: FloatLike>(
77    lhs: DualOrNot<Complex<F<T>>>,
78    rhs: &DualOrNot<Complex<F<T>>>,
79) -> DualOrNot<Complex<F<T>>> {
80    match (lhs, rhs) {
81        (DualOrNot::NonDual(lhs), DualOrNot::NonDual(rhs)) => DualOrNot::NonDual(lhs * rhs),
82        (DualOrNot::Dual(lhs), DualOrNot::Dual(rhs)) => DualOrNot::Dual(lhs * rhs.clone()),
83        (DualOrNot::Dual(lhs), DualOrNot::NonDual(rhs)) => {
84            let rhs_dual = new_constant(&lhs, rhs);
85            DualOrNot::Dual(lhs * rhs_dual)
86        }
87        (DualOrNot::NonDual(lhs), DualOrNot::Dual(rhs)) => {
88            let lhs_dual = new_constant(rhs, &lhs);
89            DualOrNot::Dual(lhs_dual * rhs.clone())
90        }
91    }
92}
93
94fn combine_completed_threshold_pieces<T: FloatLike>(
95    local: Complex<F<T>>,
96    integrated: Complex<F<T>>,
97    local_multiplier: &F<T>,
98    integrated_multiplier: &F<T>,
99) -> Complex<F<T>> {
100    let mut result = Complex::new_re(local.re.zero());
101    if !local_multiplier.is_zero() {
102        result += local * Complex::new_re(local_multiplier.clone());
103    }
104    if !integrated_multiplier.is_zero() {
105        result += integrated * Complex::new_re(integrated_multiplier.clone());
106    }
107    result
108}
109
110struct EvaluatedAmplitudeThreshold<T: FloatLike> {
111    weighted: Complex<F<T>>,
112    bare_pieces: Option<SingleThresholdPieces<Complex<F<T>>>>,
113}
114
115impl<T: FloatLike> EvaluatedAmplitudeThreshold<T> {
116    fn new(zero: F<T>, record_components: bool) -> Self {
117        Self {
118            weighted: Complex::new_re(zero.clone()),
119            bare_pieces: record_components.then(|| SingleThresholdPieces {
120                local: Complex::new_re(zero.clone()),
121                integrated: Complex::new_re(zero),
122            }),
123        }
124    }
125
126    fn add_completed_pieces(
127        &mut self,
128        local: Complex<F<T>>,
129        integrated: Complex<F<T>>,
130        local_multiplier: &F<T>,
131        integrated_multiplier: &F<T>,
132    ) {
133        if let Some(bare_pieces) = &mut self.bare_pieces {
134            if !local_multiplier.is_zero() {
135                bare_pieces.local += local.clone();
136            }
137            if !integrated_multiplier.is_zero() {
138                bare_pieces.integrated += integrated.clone();
139            }
140        }
141        self.weighted += combine_completed_threshold_pieces(
142            local,
143            integrated,
144            local_multiplier,
145            integrated_multiplier,
146        );
147    }
148
149    fn finish(mut self, local_multiplier: &F<T>, integrated_multiplier: &F<T>) -> Self {
150        if let Some(bare_pieces) = &self.bare_pieces {
151            self.weighted = combine_completed_threshold_pieces(
152                bare_pieces.local.clone(),
153                bare_pieces.integrated.clone(),
154                local_multiplier,
155                integrated_multiplier,
156            );
157        }
158        self
159    }
160}
161
162fn append_amplitude_component_evaluations<T: FloatLike>(
163    components: &mut Option<Vec<AmplitudeCountertermComponentEvaluation<T>>>,
164    evaluation: &EvaluatedAmplitudeThreshold<T>,
165    variant_id: ThresholdCountertermVariantId,
166    esurface_id: RaisedEsurfaceId,
167    overlap_group: usize,
168    local_multiplier: &F<T>,
169    integrated_multiplier: &F<T>,
170) {
171    let Some(components) = components else {
172        return;
173    };
174    let bare_pieces = evaluation
175        .bare_pieces
176        .as_ref()
177        .expect("recorded amplitude components require completed bare pieces");
178    for (kind, multiplier, bare_piece) in [
179        (
180            ThresholdCountertermComponentKind::Local,
181            local_multiplier,
182            &bare_pieces.local,
183        ),
184        (
185            ThresholdCountertermComponentKind::Integrated,
186            integrated_multiplier,
187            &bare_pieces.integrated,
188        ),
189    ] {
190        let evaluation_skipped = multiplier.is_zero();
191        let (bare, weighted) = if evaluation_skipped {
192            (None, Complex::new_re(multiplier.zero()))
193        } else {
194            (
195                Some(bare_piece.clone()),
196                bare_piece.clone() * Complex::new_re(multiplier.clone()),
197            )
198        };
199        components.push(AmplitudeCountertermComponentEvaluation {
200            variant_id,
201            kind,
202            esurface_id,
203            overlap_group,
204            multiplier_value: multiplier.clone(),
205            bare,
206            weighted,
207            evaluation_skipped,
208        });
209    }
210}
211
212#[derive(Clone, Encode, Decode)]
213#[trait_decode(trait = GammaLoopContext)]
214pub struct AmplitudeCountertermData {
215    /// True retains the historical grouped raised-surface runtime without generalized allocation.
216    pub legacy_equivalent: bool,
217    pub overlap: OverlapStructure,
218    pub evaluators: TiVec<RaisedEsurfaceId, AmplitudeCountertermEvaluator>,
219    pub helper_evaluators: Vec<GenericEvaluator>,
220    // `generated_mask` tracks whether a threshold slot actually has symbolic content.
221    // This is independent of any orientation filtering and reflects the underlying
222    // threshold-generation logic itself.
223    pub generated_mask: TiVec<RaisedEsurfaceId, bool>,
224    // `active_mask` is an additional generation-time gate coming from the selected
225    // orientation subset. A slot can be generated in principle but inactive for the
226    // current generated evaluator set, in which case we keep its index but compile it
227    // to a zero/dummy evaluator and hard-fail if runtime ever reaches it.
228    pub active_mask: TiVec<RaisedEsurfaceId, bool>,
229    /// Generalized data indexed exactly like the graph's resolved variant registry.
230    pub variant_evaluators: TiVec<ThresholdCountertermVariantId, AmplitudeCountertermEvaluator>,
231    pub variant_helper_evaluators: TiVec<ThresholdCountertermVariantId, Vec<GenericEvaluator>>,
232    pub variant_generated_mask: TiVec<ThresholdCountertermVariantId, bool>,
233    pub variant_active_mask: TiVec<ThresholdCountertermVariantId, bool>,
234    pub variant_raised_esurfaces: TiVec<ThresholdCountertermVariantId, RaisedEsurfaceId>,
235    pub variant_subspaces: TiVec<ThresholdCountertermVariantId, SubspaceData>,
236    /// Stable graph-local metadata registry for diagnostics, events, and archives.
237    pub variant_metadata: TiVec<ThresholdCountertermVariantId, ResolvedThresholdCountertermVariant>,
238    /// Eager-only opaque multiplier evaluators. `None` is the identity/no-allocation path.
239    pub threshold_multipliers: Option<ThresholdMultiplierEvaluatorCollection>,
240    pub metadata_registry: Option<ThresholdCountertermMetadataRegistry>,
241    pub lmbs: TiVec<LmbIndex, LoopMomentumBasis>,
242    pub raised_data: RaisedEsurfaceData,
243    pub esurface_map: TiVec<GroupEsurfaceId, TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>>,
244    pub local_esurface_exists: TiVec<GroupEsurfaceId, bool>,
245    pub own_group_position: GraphGroupPosition,
246    /// One immutable catalogue is shared by all members, including foreign MC complements.
247    pub(crate) group_catalogue: RuntimeCache<Arc<AmplitudeOverlapCatalogue>>,
248}
249
250#[derive(Clone)]
251pub(crate) struct AmplitudeOverlapCatalogue {
252    pub graphs: TiVec<GraphGroupPosition, Graph>,
253    pub lmbs: TiVec<LmbIndex, LoopMomentumBasis>,
254    pub variants: Vec<(
255        GraphGroupPosition,
256        ThresholdCountertermVariantId,
257        ResolvedThresholdCountertermVariant,
258        Esurface,
259    )>,
260}
261
262struct PreparedAmplitudeOverlap<T: FloatLike> {
263    thresholds: EsurfaceCollection,
264    threshold_subspaces: Vec<SubspaceData>,
265    threshold_masses: Vec<EdgeVec<F<T>>>,
266    synthetic_variants: TiVec<EsurfaceID, usize>,
267    sample_in_common_lmb: MomentumSample<T>,
268    lmbs: TiVec<LmbIndex, LoopMomentumBasis>,
269    parent_lmb: LmbIndex,
270    overlap: SubspaceOverlapStructure,
271}
272
273impl AmplitudeOverlapCatalogue {
274    fn prepare_group<T: FloatLike>(
275        &self,
276        allowed_instances: &[usize],
277        momentum_sample: &MomentumSample<T>,
278        model: &Model,
279        settings: &RuntimeSettings,
280        rotation: &Rotation,
281    ) -> Result<PreparedAmplitudeOverlap<T>> {
282        // Explicit physical cycles have one canonical chart on the group master. An implicit
283        // full-space member instead keeps the established shared generation variables, with
284        // each member's native propagator routing; its local edge IDs need not match the master.
285        // Each independent instance retains its own native mass data and fixed complement.
286        let topology = &self.graphs[GraphGroupPosition::from(0)];
287        let native_full_space = allowed_instances
288            .iter()
289            .any(|&instance| self.variants[instance].2.requested_parent_lmb.is_none());
290        let mut lmbs = self.lmbs.clone();
291        let common_subspace = if native_full_space {
292            let parent = LmbIndex::from(lmbs.len());
293            lmbs.push(topology.loop_momentum_basis.clone());
294            SubspaceData::new_with_user_selected_lmb(topology.no_dummy(), parent, topology, &lmbs)?
295        } else {
296            self.variants[allowed_instances[0]].2.subspace.clone()
297        };
298        let common_lmb = common_subspace.get_lmb(&lmbs).clone();
299        let sample_in_common_lmb = if native_full_space {
300            momentum_sample.clone()
301        } else {
302            momentum_sample.lmb_transform(&topology.loop_momentum_basis, &common_lmb)
303        };
304        let e_cm = F::from_f64(settings.kinematics.e_cm);
305        let existence_tolerance = F::from_f64(settings.subtraction.esurface_existence_threshold);
306
307        let mut thresholds = EsurfaceCollection::new();
308        let mut threshold_subspaces = Vec::new();
309        let mut threshold_masses = Vec::new();
310        let mut surface_kinematics = Vec::new();
311        let mut synthetic_variants = TiVec::<EsurfaceID, usize>::new();
312        for &instance in allowed_instances {
313            let (owner, _, metadata, esurface) = &self.variants[instance];
314            let subspace = &metadata.subspace;
315            let native_lmb = subspace.get_lmb(&self.lmbs);
316            let member = &self.graphs[*owner];
317            let native_geometry = metadata.requested_parent_lmb.is_none();
318            if !native_geometry
319                && esurface
320                    .energies
321                    .iter()
322                    .chain(esurface.external_shift.iter().map(|(edge, _)| edge))
323                    .chain(member.loop_momentum_basis.loop_edges.iter())
324                    .any(|edge| native_lmb.edge_signatures.get(*edge).is_none())
325            {
326                return Err(eyre!(
327                    "Amplitude graph '{}' threshold variant '{}' refers to edges absent from master graph '{}' coordinates",
328                    member.name,
329                    metadata.name,
330                    topology.name,
331                ));
332            }
333            let native_subspace = if native_geometry {
334                let parent = LmbIndex::from(lmbs.len());
335                lmbs.push(member.loop_momentum_basis.clone());
336                SubspaceData::new_with_user_selected_lmb(member.no_dummy(), parent, member, &lmbs)?
337            } else {
338                subspace.clone()
339            };
340            let native_sample = if native_geometry {
341                momentum_sample.clone()
342            } else {
343                momentum_sample.lmb_transform(&topology.loop_momentum_basis, native_lmb)
344            };
345            let geometry = if native_geometry { member } else { topology };
346            let masses = member.get_real_mass_vector::<T>(model);
347            let existence = esurface.classify_existence_subspace(
348                native_sample.loop_moms(),
349                native_sample.external_moms(),
350                &native_subspace,
351                &lmbs,
352                geometry,
353                &masses,
354                &[],
355                &e_cm,
356                &existence_tolerance,
357            );
358            if !existence.is_existing() {
359                continue;
360            }
361            // Equal physical cycles allow the full sample to be expressed in this group's chart.
362            let rebased = if native_geometry {
363                native_sample
364            } else {
365                native_sample.lmb_transform(native_lmb, &common_lmb)
366            };
367            let rebased_subspace = if native_geometry {
368                native_subspace
369            } else {
370                SubspaceData::new_from_parent_basis_edges(
371                    &subspace.iter_basis_edges(&self.lmbs).collect::<Vec<_>>(),
372                    &topology.no_dummy(),
373                    common_subspace.parent_lmb_index(),
374                    topology,
375                    &lmbs,
376                )?
377            };
378            thresholds.push(esurface.clone());
379            threshold_subspaces.push(rebased_subspace);
380            surface_kinematics.push(OverlapKinematics {
381                loop_moms: rebased.loop_moms().iter().map(|mom| mom.to_f64()).collect(),
382                external_momenta: rebased
383                    .external_moms()
384                    .iter()
385                    .map(|mom| mom.to_f64())
386                    .collect(),
387                edge_masses: Some(masses.iter().map(|(_, mass)| F(mass.0.to_f64())).collect()),
388            });
389            threshold_masses.push(masses);
390            synthetic_variants.push(instance);
391        }
392        let existing_thresholds: ExistingThresholds = thresholds.keys().collect();
393        let sample_f64 = momentum_sample_to_f64(&sample_in_common_lmb);
394        let overlap = if native_full_space && !thresholds.is_empty() {
395            // Reuse the full-space solver's per-graph geometry. Each semantic variant is a
396            // separate synthetic graph/surface entry, including duplicate and foreign poles.
397            let raised_data = RaisedEsurfaceData {
398                raised_groups: thresholds
399                    .keys()
400                    .map(|id| RaisedEsurfaceGroup {
401                        esurface_ids: vec![id],
402                        max_occurence: 1,
403                    })
404                    .collect(),
405                pass_two_evaluator: None,
406            };
407            let mut probe_settings = settings.clone();
408            // This input supplies only generation frames, so skip the unavailable alternate-LMB
409            // origin heuristic. The regular SOCP still solves all of the same constraints.
410            probe_settings
411                .subtraction
412                .overlap_settings
413                .try_origin_all_lmbs = false;
414            if let Some(center) = &mut probe_settings
415                .subtraction
416                .overlap_settings
417                .force_global_center
418            {
419                let rotated = center
420                    .iter()
421                    .map(|p| ThreeMomentum::new(F(p[0]), F(p[1]), F(p[2])))
422                    .collect::<LoopMomenta<_>>()
423                    .rotate(rotation);
424                *center = rotated.iter().map(|p| [p.px.0, p.py.0, p.pz.0]).collect();
425            }
426            let input = OverlapInput {
427                graph_data: thresholds
428                    .keys()
429                    .map(|id| SingleGraphOverlapData {
430                        lmb: threshold_subspaces[id.0].get_lmb(&lmbs),
431                        esurfaces: &thresholds,
432                        raised_data: &raised_data,
433                        edge_masses: surface_kinematics[id.0].edge_masses.clone().unwrap(),
434                    })
435                    .collect(),
436                settings: &probe_settings,
437                group_esurface_map: thresholds
438                    .keys()
439                    .map(|id| {
440                        thresholds
441                            .keys()
442                            .map(|owner| (owner == id).then_some(RaisedEsurfaceId::from(id.0)))
443                            .collect()
444                    })
445                    .collect(),
446                local_esurface_exists: thresholds
447                    .keys()
448                    .map(|owner| thresholds.keys().map(|id| owner == id).collect())
449                    .collect(),
450            };
451            let overlap = find_maximal_overlap(
452                &input,
453                &thresholds
454                    .keys()
455                    .map(|id| GroupEsurfaceId::from(id.0))
456                    .collect(),
457                sample_f64.external_moms(),
458            )?;
459            Ok(SubspaceOverlapStructure {
460                existing_esurfaces: overlap
461                    .existing_esurfaces
462                    .iter()
463                    .map(|id| EsurfaceID::from(id.0))
464                    .collect(),
465                overlap_groups: overlap
466                    .overlap_groups
467                    .into_iter()
468                    .map(|group| crate::subtraction::overlap_subspace::OverlapGroup {
469                        existing_esurfaces: group.existing_esurfaces,
470                        complement: group.complement,
471                        center: group.center,
472                    })
473                    .collect(),
474            })
475        } else {
476            let overlap_input = SubspaceOverlapInput {
477                graph: topology,
478                settings,
479                subspace: &common_subspace,
480                threshold_subspaces: Some(&threshold_subspaces),
481                lmbs: &lmbs,
482                thresholds: &thresholds,
483                edge_masses: topology.get_real_mass_vector::<f64>(model),
484                surface_kinematics: Some(&surface_kinematics),
485            };
486            find_maximal_subspace_overlap(
487                &overlap_input,
488                &existing_thresholds,
489                sample_f64.loop_moms(),
490                sample_f64.external_moms(),
491                rotation,
492            )
493        }
494        .with_context(|| {
495            format!(
496                "Failed to find shared threshold overlap for amplitude group master '{}'",
497                topology.name,
498            )
499        })?;
500
501        Ok(PreparedAmplitudeOverlap {
502            thresholds,
503            threshold_subspaces,
504            threshold_masses,
505            synthetic_variants,
506            sample_in_common_lmb,
507            lmbs,
508            parent_lmb: common_subspace.parent_lmb_index(),
509            overlap,
510        })
511    }
512}
513
514#[derive(Clone, Encode, Decode)]
515#[trait_decode(trait = GammaLoopContext)]
516pub struct AmplitudeCountertermAtom {
517    pub parametric: Integrands,
518}
519
520impl AmplitudeCountertermAtom {
521    pub(crate) fn is_generated(&self) -> bool {
522        self.parametric.iter().next().is_some()
523    }
524
525    pub(crate) fn zero_like(&self) -> Self {
526        Self {
527            parametric: self.parametric.zero_like(),
528        }
529    }
530
531    #[instrument(skip_all)]
532    pub(crate) fn to_evaluator_with_timings(
533        &self,
534        param_builder: &ParamBuilder,
535        orientations: &TiVec<OrientationID, EdgeVec<Orientation>>,
536        production_orientation_ids: &[OrientationID],
537        global_settings: &GlobalSettings,
538    ) -> (AmplitudeCountertermEvaluator, EvaluatorBuildTimings) {
539        let _progress_guard =
540            crate::processes::enter_detailed_progress_span("Building Threshold CT Evaluator");
541        let mut evaluator_stacks = BTreeMap::new();
542        let mut timings = EvaluatorBuildTimings::default();
543
544        for (index, integrand) in self.parametric.iter() {
545            let dual_shape = shape_from_cut_cff_index(index);
546
547            // In explicit mode the atom already contains the complete
548            // orientation sum, so selecting orientations again would double count it.
549            let (evaluator_stack, evaluator_timings) = EvaluatorStack::from_integrand_with_timings(
550                integrand,
551                param_builder,
552                self.parametric.numerators(),
553                (!global_settings.generation.explicit_orientation_sum_only)
554                    .then_some((orientations.as_slice().as_ref(), production_orientation_ids)),
555                dual_shape,
556                &global_settings.generation.evaluator,
557            )
558            .unwrap();
559            timings += evaluator_timings;
560            evaluator_stacks.insert(*index, evaluator_stack);
561        }
562
563        (AmplitudeCountertermEvaluator { evaluator_stacks }, timings)
564    }
565
566    pub(crate) fn new() -> Self {
567        Self {
568            parametric: std::iter::empty().collect(),
569        }
570    }
571}
572
573#[derive(Clone, Encode, Decode)]
574#[trait_decode(trait = GammaLoopContext)]
575pub struct AmplitudeCountertermEvaluator {
576    pub evaluator_stacks: BTreeMap<CutCFFIndex, EvaluatorStack>,
577}
578
579impl AmplitudeCountertermEvaluator {
580    pub(crate) fn generic_evaluator_count(&self) -> usize {
581        self.evaluator_stacks
582            .values()
583            .map(EvaluatorStack::generic_evaluator_count)
584            .sum()
585    }
586}
587
588#[derive(Debug, Clone)]
589pub struct AmplitudeLocalCountertermEvaluation<T: FloatLike> {
590    pub variant_id: Option<ThresholdCountertermVariantId>,
591    pub esurface_id: RaisedEsurfaceId,
592    pub overlap_group: usize,
593    pub value: Complex<F<T>>,
594}
595
596#[derive(Debug, Clone)]
597pub struct AmplitudeCountertermComponentEvaluation<T: FloatLike> {
598    pub variant_id: ThresholdCountertermVariantId,
599    pub kind: ThresholdCountertermComponentKind,
600    pub esurface_id: RaisedEsurfaceId,
601    pub overlap_group: usize,
602    pub multiplier_value: F<T>,
603    pub bare: Option<Complex<F<T>>>,
604    pub weighted: Complex<F<T>>,
605    pub evaluation_skipped: bool,
606}
607
608#[derive(Debug, Clone)]
609pub struct AmplitudeCountertermEvaluation<T: FloatLike> {
610    pub total: Complex<F<T>>,
611    pub local_counterterms: Vec<AmplitudeLocalCountertermEvaluation<T>>,
612    pub components: Option<Vec<AmplitudeCountertermComponentEvaluation<T>>>,
613}
614
615impl AmplitudeCountertermData {
616    fn radial_root_identity(
617        graph_name: &str,
618        overlap_group: usize,
619        raised_esurface_id: RaisedEsurfaceId,
620        rotation: &Rotation,
621    ) -> RadialRootIdentity {
622        RadialRootIdentity::new(format!(
623            "amplitude graph '{graph_name}' overlap group {overlap_group} raised E-surface {} probe rotation {}",
624            raised_esurface_id.0, rotation.method,
625        ))
626    }
627
628    fn variant_radial_root_identity(
629        graph_name: &str,
630        overlap_group: usize,
631        variant_id: ThresholdCountertermVariantId,
632        raised_esurface_id: RaisedEsurfaceId,
633        rotation: &Rotation,
634    ) -> RadialRootIdentity {
635        RadialRootIdentity::new(format!(
636            "amplitude graph '{graph_name}' overlap group {overlap_group} threshold variant {} raised E-surface {} probe rotation {}",
637            variant_id.0, raised_esurface_id.0, rotation.method,
638        ))
639    }
640
641    pub(crate) fn generic_evaluator_count(&self) -> usize {
642        let stack_count = self
643            .evaluators
644            .iter()
645            .map(AmplitudeCountertermEvaluator::generic_evaluator_count)
646            .chain(
647                self.variant_evaluators
648                    .iter()
649                    .map(AmplitudeCountertermEvaluator::generic_evaluator_count),
650            )
651            .sum::<usize>();
652        let overlap_count = self
653            .overlap
654            .overlap_groups
655            .iter()
656            .map(|group| {
657                group
658                    .prefactor_evaluator
659                    .as_ref()
660                    .map(|evaluators| evaluators.len())
661                    .unwrap_or(0)
662            })
663            .sum::<usize>();
664        stack_count
665            + overlap_count
666            + self.helper_evaluators.len()
667            + self
668                .variant_helper_evaluators
669                .iter()
670                .map(Vec::len)
671                .sum::<usize>()
672    }
673
674    pub fn new_empty(own_group_position: GraphGroupPosition) -> Self {
675        Self {
676            legacy_equivalent: true,
677            overlap: OverlapStructure::new_empty(),
678            evaluators: TiVec::new(),
679            helper_evaluators: vec![],
680            generated_mask: TiVec::new(),
681            active_mask: TiVec::new(),
682            variant_evaluators: TiVec::new(),
683            variant_helper_evaluators: TiVec::new(),
684            variant_generated_mask: TiVec::new(),
685            variant_active_mask: TiVec::new(),
686            variant_raised_esurfaces: TiVec::new(),
687            variant_subspaces: TiVec::new(),
688            variant_metadata: TiVec::new(),
689            threshold_multipliers: None,
690            metadata_registry: None,
691            lmbs: TiVec::new(),
692            raised_data: RaisedEsurfaceData {
693                raised_groups: TiVec::new(),
694                pass_two_evaluator: None,
695            },
696            esurface_map: TiVec::new(),
697            local_esurface_exists: TiVec::new(),
698            own_group_position,
699            group_catalogue: RuntimeCache::default(),
700        }
701    }
702
703    fn overlap_catalogue(
704        &self,
705        graph: &Graph,
706        esurfaces: &EsurfaceCollection,
707    ) -> Arc<AmplitudeOverlapCatalogue> {
708        self.group_catalogue.as_ref().cloned().unwrap_or_else(|| {
709            Arc::new(AmplitudeOverlapCatalogue {
710                graphs: std::iter::repeat_with(|| graph.clone())
711                    .take(usize::from(self.own_group_position) + 1)
712                    .collect(),
713                lmbs: self.lmbs.clone(),
714                variants: self
715                    .variant_metadata
716                    .iter_enumerated()
717                    .filter(|(id, _)| {
718                        self.variant_generated_mask[*id] && self.variant_active_mask[*id]
719                    })
720                    .map(|(id, metadata)| {
721                        let raised = self.variant_raised_esurfaces[id];
722                        (
723                            self.own_group_position,
724                            id,
725                            metadata.clone(),
726                            esurfaces[self.raised_data.raised_groups[raised].esurface_ids[0]]
727                                .clone(),
728                        )
729                    })
730                    .collect(),
731            })
732        })
733    }
734
735    fn ensure_active_raised_esurface(&self, raised_esurface_id: RaisedEsurfaceId) -> Result<()> {
736        if self.active_mask[raised_esurface_id] {
737            return Ok(());
738        }
739
740        Err(color_eyre::eyre::eyre!(
741            "Amplitude threshold evaluator {} was reached at runtime even though generation marked it inactive for the selected orientation subset",
742            raised_esurface_id.0
743        ))
744    }
745
746    fn ensure_active_variant(&self, variant_id: ThresholdCountertermVariantId) -> Result<()> {
747        if self.variant_active_mask[variant_id] {
748            return Ok(());
749        }
750
751        Err(eyre!(
752            "Amplitude threshold variant {} was reached at runtime even though generation marked it inactive for the selected orientation subset",
753            variant_id.0,
754        ))
755    }
756
757    fn metadata_variant_for_raised_esurface(
758        &self,
759        raised_esurface_id: RaisedEsurfaceId,
760    ) -> Result<ThresholdCountertermVariantId> {
761        let registry = self.metadata_registry.as_ref().ok_or_else(|| {
762            eyre!("Amplitude threshold metadata was requested without a graph-local registry")
763        })?;
764        let raised_group = &self.raised_data.raised_groups[raised_esurface_id];
765        let mut matching = registry.variants.iter().filter(|variant| {
766            variant.side == ThresholdCountertermSide::Amplitude
767                && variant.threshold_esurface_ids.len() == raised_group.esurface_ids.len()
768                && variant
769                    .threshold_esurface_ids
770                    .iter()
771                    .zip(&raised_group.esurface_ids)
772                    .all(|(metadata_id, esurface_id)| *metadata_id == esurface_id.0)
773        });
774        let variant = matching.next().ok_or_else(|| {
775            eyre!(
776                "Amplitude threshold registry has no variant for raised E-surface {}",
777                raised_esurface_id.0,
778            )
779        })?;
780        if matching.next().is_some() {
781            return Err(eyre!(
782                "Amplitude threshold registry has multiple variants for legacy raised E-surface {}",
783                raised_esurface_id.0,
784            ));
785        }
786        Ok(ThresholdCountertermVariantId(variant.variant_id))
787    }
788
789    #[allow(clippy::too_many_arguments)]
790    fn evaluate_variant_multiplier<T: FloatLike>(
791        threshold_multipliers: &mut Option<ThresholdMultiplierEvaluatorCollection>,
792        multiplier_workspace: Option<&mut ThresholdMultiplierInputWorkspace<T>>,
793        variant_id: ThresholdCountertermVariantId,
794        graph: &Graph,
795        edge_masses: &EdgeVec<F<T>>,
796        model_values: &[Complex<F<f64>>],
797        additional_values: &[F<T>],
798        base_sample: &MomentumSample<T>,
799        root_sample: &MomentumSample<T>,
800        evaluation_metadata: &mut EvaluationMetaData,
801    ) -> Result<(F<T>, F<T>)> {
802        let Some(collection) = threshold_multipliers.as_mut() else {
803            return Ok((base_sample.one(), base_sample.one()));
804        };
805        let reference = collection
806            .left_variants()
807            .iter()
808            .find(|reference| reference.variant_id == variant_id)
809            .copied()
810            .ok_or_else(|| {
811                eyre!(
812                    "Amplitude threshold variant {} has no multiplier-registry entry",
813                    variant_id.0,
814                )
815            })?;
816        let Some(evaluator_id) = reference.evaluator_id else {
817            return Ok((base_sample.one(), base_sample.one()));
818        };
819
820        let workspace = multiplier_workspace.ok_or_else(|| {
821            eyre!(
822                "Amplitude threshold variant {} requires a missing multiplier workspace",
823                variant_id.0,
824            )
825        })?;
826        let local_values = {
827            let layout = collection.layout();
828            workspace.bind(
829                layout,
830                &graph.loop_momentum_basis,
831                edge_masses,
832                model_values,
833                additional_values,
834                base_sample,
835                root_sample,
836            )
837            .with_context(|| {
838                format!(
839                    "Failed to bind local threshold multiplier for amplitude graph '{}' variant {}",
840                    graph.name, variant_id.0,
841                )
842            })?
843        };
844        let local = collection.evaluators_mut()[evaluator_id.0].evaluate(
845            local_values,
846            evaluation_metadata,
847        )
848        .with_context(|| {
849            format!(
850                "Failed to evaluate local threshold multiplier for amplitude graph '{}' variant {}",
851                graph.name, variant_id.0,
852            )
853        })?;
854
855        let integrated_values = {
856            let layout = collection.layout();
857            workspace.bind(
858                layout,
859                &graph.loop_momentum_basis,
860                edge_masses,
861                model_values,
862                additional_values,
863                root_sample,
864                root_sample,
865            )
866            .with_context(|| {
867                format!(
868                    "Failed to bind integrated threshold multiplier for amplitude graph '{}' variant {}",
869                    graph.name, variant_id.0,
870                )
871            })?
872        };
873        let integrated = collection.evaluators_mut()[evaluator_id.0].evaluate(
874            integrated_values,
875            evaluation_metadata,
876        )
877        .with_context(|| {
878            format!(
879                "Failed to evaluate integrated threshold multiplier for amplitude graph '{}' variant {}",
880                graph.name, variant_id.0,
881            )
882        })?;
883
884        Ok((local, integrated))
885    }
886
887    pub fn compile(
888        &mut self,
889        path: impl AsRef<Path>,
890        _override_existing: bool,
891        frozen_mode: &crate::settings::global::FrozenCompilationMode,
892    ) -> Result<()> {
893        for (i, e) in self.evaluators.iter_mut_enumerated() {
894            for (cff_index, evaluator_stack) in e.evaluator_stacks.iter_mut() {
895                let order_index = cff_index.left_threshold_order.unwrap() - 1;
896                evaluator_stack.compile(
897                    format!("esurface_{}_order_{}", i.0, order_index + 1),
898                    path.as_ref(),
899                    frozen_mode,
900                )?;
901            }
902        }
903
904        for (variant_id, evaluator) in self.variant_evaluators.iter_mut_enumerated() {
905            for (cff_index, evaluator_stack) in &mut evaluator.evaluator_stacks {
906                let order_index = cff_index.left_threshold_order.unwrap() - 1;
907                evaluator_stack.compile(
908                    format!(
909                        "threshold_variant_{}_order_{}",
910                        variant_id.0,
911                        order_index + 1,
912                    ),
913                    path.as_ref(),
914                    frozen_mode,
915                )?;
916            }
917        }
918
919        for (group_index, group) in self.overlap.overlap_groups.iter_mut().enumerate() {
920            if let Some(prefactor_evaluators) = group.prefactor_evaluator.as_mut() {
921                for (order_index, prefactor_evaluator) in
922                    prefactor_evaluators.iter_mut().enumerate()
923                {
924                    prefactor_evaluator.borrow_mut().compile_external(
925                        path.as_ref()
926                            .join(format!(
927                                "overlap_prefactor_{group_index}_order_{}",
928                                order_index + 1
929                            ))
930                            .with_extension("cpp"),
931                        format!("overlap_prefactor_{group_index}_order_{}", order_index + 1),
932                        path.as_ref()
933                            .join(format!(
934                                "overlap_prefactor_{group_index}_order_{}",
935                                order_index + 1
936                            ))
937                            .with_extension("so"),
938                        frozen_mode,
939                    )?;
940                }
941            }
942        }
943
944        for (order_index, evaluator) in self.helper_evaluators.iter_mut().enumerate() {
945            evaluator.compile_external(
946                path.as_ref()
947                    .join(format!("threshold_helper_{order_index}"))
948                    .with_extension("cpp"),
949                format!("threshold_helper_{order_index}"),
950                path.as_ref()
951                    .join(format!("threshold_helper_{order_index}"))
952                    .with_extension("so"),
953                frozen_mode,
954            )?;
955        }
956        for (variant_id, helpers) in self.variant_helper_evaluators.iter_mut_enumerated() {
957            for (order_index, evaluator) in helpers.iter_mut().enumerate() {
958                evaluator.compile_external(
959                    path.as_ref()
960                        .join(format!(
961                            "threshold_variant_{}_helper_{order_index}",
962                            variant_id.0,
963                        ))
964                        .with_extension("cpp"),
965                    format!("threshold_variant_{}_helper_{order_index}", variant_id.0,),
966                    path.as_ref()
967                        .join(format!(
968                            "threshold_variant_{}_helper_{order_index}",
969                            variant_id.0,
970                        ))
971                        .with_extension("so"),
972                    frozen_mode,
973                )?;
974            }
975        }
976        Ok(())
977    }
978
979    pub(crate) fn for_each_generic_evaluator_mut(
980        &mut self,
981        mut f: impl FnMut(&mut crate::integrands::process::GenericEvaluator) -> Result<()>,
982    ) -> Result<()> {
983        for evaluator in self.evaluators.iter_mut() {
984            for evaluator_stack in evaluator.evaluator_stacks.values_mut() {
985                evaluator_stack.for_each_generic_evaluator_mut(&mut f)?;
986            }
987        }
988        for evaluator in &mut self.variant_evaluators {
989            for evaluator_stack in evaluator.evaluator_stacks.values_mut() {
990                evaluator_stack.for_each_generic_evaluator_mut(&mut f)?;
991            }
992        }
993
994        for group in &mut self.overlap.overlap_groups {
995            if let Some(prefactor_evaluators) = group.prefactor_evaluator.as_mut() {
996                for prefactor_evaluator in prefactor_evaluators.iter_mut() {
997                    f(prefactor_evaluator.get_mut())?;
998                }
999            }
1000        }
1001
1002        for evaluator in &mut self.helper_evaluators {
1003            f(evaluator)?;
1004        }
1005        for helpers in &mut self.variant_helper_evaluators {
1006            for evaluator in helpers {
1007                f(evaluator)?;
1008            }
1009        }
1010        if let Some(multipliers) = &mut self.threshold_multipliers {
1011            multipliers.for_each_generic_evaluator_mut(&mut f)?;
1012        }
1013
1014        Ok(())
1015    }
1016
1017    #[allow(clippy::too_many_arguments)]
1018    pub fn evaluate<T: FloatLike>(
1019        &mut self,
1020        momentum_sample: &MomentumSample<T>,
1021        graph: &Graph,
1022        model: &Model,
1023        esurfaces: &EsurfaceCollection,
1024        rotation: &Rotation,
1025        settings: &RuntimeSettings,
1026        param_builder: &mut ParamBuilder<f64>,
1027        orientation: SingleOrAllOrientations<'_, OrientationID>,
1028        evaluation_metadata: &mut EvaluationMetaData,
1029        record_components: bool,
1030    ) -> Result<AmplitudeCountertermEvaluation<T>> {
1031        if record_components && self.metadata_registry.is_none() {
1032            return Err(eyre!(
1033                "Amplitude graph '{}' requested detailed threshold components without metadata",
1034                graph.name,
1035            ));
1036        }
1037        if !self.legacy_equivalent {
1038            return self.evaluate_variants(
1039                momentum_sample,
1040                graph,
1041                model,
1042                esurfaces,
1043                rotation,
1044                settings,
1045                param_builder,
1046                orientation,
1047                evaluation_metadata,
1048                record_components,
1049            );
1050        }
1051
1052        debug!("start evaluate threshold counterterm");
1053        let existing_esurfaces = self
1054            .overlap
1055            .existing_esurfaces
1056            .iter()
1057            .map(|e| e.0)
1058            .collect::<Vec<_>>();
1059        debug!("subtracting esurfaces: {:?}", existing_esurfaces);
1060        debug!("overlap structure\n: {}", self.overlap);
1061
1062        let counter_term_builder = CounterTermBuilder::new(
1063            graph,
1064            model,
1065            rotation,
1066            settings,
1067            esurfaces,
1068            momentum_sample,
1069            &self.overlap,
1070            &self.raised_data,
1071            self.own_group_position,
1072            &self.esurface_map,
1073        );
1074
1075        let mut result = Complex::new_re(momentum_sample.zero());
1076        let mut local_counterterms = Vec::new();
1077        let mut components = record_components.then(Vec::new);
1078        let mut radial_root_failed = false;
1079        let split_helpers = self.metadata_registry.is_some();
1080
1081        for (overlap_group, group) in self.overlap.overlap_groups.iter().enumerate() {
1082            let overlap_builder = counter_term_builder.new_overlap_builder(group);
1083
1084            for existing_esurface_id in group.existing_esurfaces.iter() {
1085                let group_esurface_id = self.overlap.existing_esurfaces[*existing_esurface_id];
1086                if self
1087                    .local_esurface_exists
1088                    .get(group_esurface_id)
1089                    .is_some_and(|exists| !*exists)
1090                {
1091                    continue;
1092                }
1093
1094                let Some(esurface_builder) =
1095                    overlap_builder.new_esurface_builder(*existing_esurface_id)
1096                else {
1097                    continue;
1098                };
1099
1100                let raised_esurface_id = esurface_builder.raised_esurface_id;
1101                self.ensure_active_raised_esurface(raised_esurface_id)?;
1102                let radial_root_identity = Self::radial_root_identity(
1103                    &graph.name,
1104                    overlap_group,
1105                    raised_esurface_id,
1106                    rotation,
1107                );
1108                let Some(rstar_solution) = esurface_builder.solve_rstar(
1109                    &radial_root_identity,
1110                    &mut evaluation_metadata.radial_root_diagnostics,
1111                ) else {
1112                    evaluation_metadata.record_threshold_counterterm_error(format!(
1113                        "amplitude graph '{}' overlap group {} raised E-surface {} failed center or radial-root validation in probe rotation {}",
1114                        graph.name,
1115                        overlap_group,
1116                        raised_esurface_id.0,
1117                        rotation.method,
1118                    ));
1119                    radial_root_failed = true;
1120                    continue;
1121                };
1122                if radial_root_failed {
1123                    continue;
1124                }
1125                let single_evaluation = rstar_solution.rstar_samples().evaluate(
1126                    param_builder,
1127                    orientation,
1128                    evaluation_metadata,
1129                    &mut self.evaluators[raised_esurface_id],
1130                    &mut self.helper_evaluators,
1131                    split_helpers,
1132                    record_components,
1133                )?;
1134                let single_result = single_evaluation.weighted.clone();
1135
1136                if record_components {
1137                    let variant_id = self
1138                        .metadata_variant_for_raised_esurface(raised_esurface_id)
1139                        .with_context(|| {
1140                            format!(
1141                                "while recording amplitude graph '{}' overlap group {}",
1142                                graph.name, overlap_group,
1143                            )
1144                        })?;
1145                    let one = momentum_sample.one();
1146                    append_amplitude_component_evaluations(
1147                        &mut components,
1148                        &single_evaluation,
1149                        variant_id,
1150                        raised_esurface_id,
1151                        overlap_group,
1152                        &one,
1153                        &one,
1154                    );
1155                }
1156
1157                if !single_result.is_zero() {
1158                    //    debug!(
1159                    //        "Param Builder for {}:\n{}",
1160                    //        existing_esurface_id, param_builder
1161                    //    );
1162                    debug!(
1163                        "Counterterm for esurface {}: {:+16e}",
1164                        existing_esurface_id, single_result
1165                    );
1166                }
1167
1168                local_counterterms.push(AmplitudeLocalCountertermEvaluation {
1169                    variant_id: None,
1170                    esurface_id: raised_esurface_id,
1171                    overlap_group,
1172                    value: single_result.clone(),
1173                });
1174                result += single_result;
1175            }
1176        }
1177        if radial_root_failed {
1178            return Ok(AmplitudeCountertermEvaluation {
1179                total: Complex::new_re(F::from_f64(f64::NAN)),
1180                local_counterterms,
1181                components: None,
1182            });
1183        }
1184        Ok(AmplitudeCountertermEvaluation {
1185            total: result,
1186            local_counterterms,
1187            components,
1188        })
1189    }
1190
1191    #[allow(clippy::too_many_arguments)]
1192    fn evaluate_variants<T: FloatLike>(
1193        &mut self,
1194        momentum_sample: &MomentumSample<T>,
1195        graph: &Graph,
1196        model: &Model,
1197        esurfaces: &EsurfaceCollection,
1198        rotation: &Rotation,
1199        settings: &RuntimeSettings,
1200        param_builder: &mut ParamBuilder<f64>,
1201        orientation: SingleOrAllOrientations<'_, OrientationID>,
1202        evaluation_metadata: &mut EvaluationMetaData,
1203        record_components: bool,
1204    ) -> Result<AmplitudeCountertermEvaluation<T>> {
1205        let catalogue = self.overlap_catalogue(graph, esurfaces);
1206        let mut groups = BTreeMap::new();
1207        let mut explicit = BTreeMap::<usize, (_, GraphGroupPosition, usize)>::new();
1208        for (instance, (owner, variant_id, metadata, _)) in catalogue.variants.iter().enumerate() {
1209            let signature = metadata.subspace.solve_signature(&catalogue.lmbs);
1210            if let Some(label) = metadata.group_id {
1211                if let Some((previous, previous_owner, previous_variant)) = explicit.get(&label) {
1212                    if previous != &signature {
1213                        return Err(eyre!(
1214                            "Amplitude group master '{}' explicit threshold group_id={} has incompatible physical solve cycles: graph '{}' variant {} {:?}; graph '{}' variant {} {:?}",
1215                            catalogue.graphs[GraphGroupPosition::from(0)].name,
1216                            label,
1217                            catalogue.graphs[*previous_owner].name,
1218                            previous_variant,
1219                            previous,
1220                            catalogue.graphs[*owner].name,
1221                            variant_id.0,
1222                            signature,
1223                        ));
1224                    }
1225                } else {
1226                    explicit.insert(label, (signature.clone(), *owner, variant_id.0));
1227                }
1228            }
1229            groups
1230                .entry((metadata.group_id, signature))
1231                .or_insert_with(Vec::new)
1232                .push(instance);
1233        }
1234
1235        let mut total = Complex::new_re(momentum_sample.zero());
1236        let mut local_counterterms = Vec::new();
1237        let mut components = record_components.then(Vec::new);
1238        for (_, members) in groups {
1239            let evaluation = self.evaluate_variant_group(
1240                momentum_sample,
1241                graph,
1242                model,
1243                esurfaces,
1244                rotation,
1245                settings,
1246                param_builder,
1247                orientation,
1248                evaluation_metadata,
1249                record_components,
1250                &catalogue,
1251                &members,
1252            )?;
1253            total += evaluation.total;
1254            local_counterterms.extend(evaluation.local_counterterms);
1255            if let (Some(all), Some(mut group_components)) =
1256                (components.as_mut(), evaluation.components)
1257            {
1258                all.append(&mut group_components);
1259            }
1260        }
1261        Ok(AmplitudeCountertermEvaluation {
1262            total,
1263            local_counterterms,
1264            components,
1265        })
1266    }
1267
1268    #[allow(clippy::too_many_arguments)]
1269    fn evaluate_variant_group<T: FloatLike>(
1270        &mut self,
1271        momentum_sample: &MomentumSample<T>,
1272        graph: &Graph,
1273        model: &Model,
1274        _esurfaces: &EsurfaceCollection,
1275        rotation: &Rotation,
1276        settings: &RuntimeSettings,
1277        param_builder: &mut ParamBuilder<f64>,
1278        orientation: SingleOrAllOrientations<'_, OrientationID>,
1279        evaluation_metadata: &mut EvaluationMetaData,
1280        record_components: bool,
1281        catalogue: &AmplitudeOverlapCatalogue,
1282        allowed_instances: &[usize],
1283    ) -> Result<AmplitudeCountertermEvaluation<T>> {
1284        if self.variant_evaluators.len() != self.variant_subspaces.len()
1285            || self.variant_evaluators.len() != self.variant_raised_esurfaces.len()
1286            || self.variant_evaluators.len() != self.variant_helper_evaluators.len()
1287            || self.variant_evaluators.len() != self.variant_generated_mask.len()
1288            || self.variant_evaluators.len() != self.variant_active_mask.len()
1289            || self.variant_evaluators.len() != self.variant_metadata.len()
1290        {
1291            return Err(eyre!(
1292                "Amplitude graph '{}' has inconsistent threshold-variant runtime dimensions",
1293                graph.name,
1294            ));
1295        }
1296        if let Some(multipliers) = &self.threshold_multipliers {
1297            multipliers.validate(self.variant_evaluators.len(), 0)?;
1298            if multipliers
1299                .left_variants()
1300                .iter()
1301                .enumerate()
1302                .any(|(variant_id, reference)| reference.variant_id.0 != variant_id)
1303            {
1304                return Err(eyre!(
1305                    "Amplitude graph '{}' threshold multiplier references are not ordered by contiguous variant ID",
1306                    graph.name,
1307                ));
1308            }
1309        }
1310
1311        let PreparedAmplitudeOverlap {
1312            thresholds,
1313            threshold_subspaces,
1314            threshold_masses,
1315            synthetic_variants,
1316            sample_in_common_lmb,
1317            lmbs,
1318            parent_lmb,
1319            overlap,
1320        } = catalogue.prepare_group(
1321            allowed_instances,
1322            momentum_sample,
1323            model,
1324            settings,
1325            rotation,
1326        )?;
1327        let topology = &catalogue.graphs[GraphGroupPosition::from(0)];
1328        let common_lmb = &lmbs[parent_lmb];
1329        let real_mass_vector = graph.get_real_mass_vector(model);
1330        let e_cm = F::from_f64(settings.kinematics.e_cm);
1331        let max_prefactor_power = synthetic_variants
1332            .iter()
1333            .map(|&instance| {
1334                let order = catalogue.variants[instance]
1335                    .2
1336                    .raised_esurface_group
1337                    .max_occurence;
1338                2 * (order / 2 + 1)
1339            })
1340            .max()
1341            .unwrap_or(2);
1342        let mut total = Complex::new_re(momentum_sample.zero());
1343        let mut local_counterterms = Vec::new();
1344        let mut components = record_components.then(Vec::new);
1345        let mut radial_root_failed = false;
1346        let mut multiplier_workspace = self.threshold_multipliers.as_ref().map(|multipliers| {
1347            ThresholdMultiplierInputWorkspace::new(multipliers.layout(), momentum_sample.zero())
1348        });
1349        let additional_params = settings.additional_params::<T>();
1350
1351        for (overlap_group_index, overlap_group) in overlap.overlap_groups.iter().enumerate() {
1352            for &existing_esurface_id in &overlap_group.existing_esurfaces {
1353                let synthetic_esurface_id = overlap.existing_esurfaces[existing_esurface_id];
1354                let instance = synthetic_variants[synthetic_esurface_id];
1355                let (owner, variant_id, metadata, _) = &catalogue.variants[instance];
1356                if *owner != self.own_group_position {
1357                    continue;
1358                }
1359                let variant_id = *variant_id;
1360                self.ensure_active_variant(variant_id)?;
1361                let raised_esurface_id = self.variant_raised_esurfaces[variant_id];
1362                let subspace = &threshold_subspaces[synthetic_esurface_id.0];
1363                let esurface = &thresholds[synthetic_esurface_id];
1364                let native_geometry = metadata.requested_parent_lmb.is_none();
1365                let geometry = if native_geometry { graph } else { topology };
1366                let root_lmb = if native_geometry {
1367                    &graph.loop_momentum_basis
1368                } else {
1369                    common_lmb
1370                };
1371
1372                let mut center = overlap_group.center.cast::<T>();
1373                for loop_index in (0..center.0.len()).map(LoopIndex::from) {
1374                    if !subspace.contains_loop_index(loop_index) {
1375                        center[loop_index] = ThreeMomentum::new(
1376                            momentum_sample.zero(),
1377                            momentum_sample.zero(),
1378                            momentum_sample.zero(),
1379                        );
1380                    }
1381                }
1382                let shifted_loop_momenta = sample_in_common_lmb.loop_moms() - &center;
1383                let radius = shifted_loop_momenta
1384                    .hyper_radius_squared(subspace.as_subspace_simple())
1385                    .sqrt();
1386                let unit_shifted_momenta =
1387                    shifted_loop_momenta.rescale(&radius.inv(), subspace.as_subspace_simple());
1388                let (raw_radius_guess, _) = esurface.get_radius_guess_subspace(
1389                    &unit_shifted_momenta,
1390                    sample_in_common_lmb.external_moms(),
1391                    subspace,
1392                    &lmbs,
1393                    geometry,
1394                    &real_mass_vector,
1395                );
1396                let function = |r: &_| {
1397                    esurface.compute_self_and_r_derivative_subspace(
1398                        r,
1399                        &unit_shifted_momenta,
1400                        &center,
1401                        sample_in_common_lmb.external_moms(),
1402                        &real_mass_vector,
1403                        subspace,
1404                        &lmbs,
1405                        geometry,
1406                    )
1407                };
1408                let zero = raw_radius_guess.zero();
1409                let mut radius_guess = raw_radius_guess.clone();
1410                if radius_guess.is_nan() || radius_guess.is_infinite() || radius_guess <= zero {
1411                    radius_guess = e_cm.clone();
1412                }
1413                let identity = Self::variant_radial_root_identity(
1414                    &graph.name,
1415                    overlap_group_index,
1416                    variant_id,
1417                    raised_esurface_id,
1418                    rotation,
1419                );
1420                let tolerance = F::from_f64(settings.subtraction.radial_root_residual_tolerance);
1421                let solution = match evaluation_metadata.radial_root_diagnostics.solve(
1422                    &identity,
1423                    &zero,
1424                    &radius_guess,
1425                    function,
1426                    &tolerance,
1427                    MAX_ITERATIONS,
1428                    64,
1429                    &e_cm,
1430                ) {
1431                    Ok(solution) => solution,
1432                    Err(error) => {
1433                        evaluation_metadata.record_threshold_counterterm_error(format!(
1434                            "amplitude graph '{}' threshold variant {} overlap group {} failed radial-root validation: {error}",
1435                            graph.name, variant_id.0, overlap_group_index,
1436                        ));
1437                        radial_root_failed = true;
1438                        continue;
1439                    }
1440                };
1441                if radial_root_failed {
1442                    continue;
1443                }
1444
1445                let root_loop_momenta = &unit_shifted_momenta
1446                    .rescale(&solution.solution, subspace.as_subspace_simple())
1447                    + &center;
1448                let mut root_in_common_lmb = sample_in_common_lmb.clone();
1449                root_in_common_lmb.sample.loop_moms = root_loop_momenta;
1450                let root_in_metadata_lmb =
1451                    root_in_common_lmb.lmb_transform(common_lmb, &topology.loop_momentum_basis);
1452                let (local_multiplier, integrated_multiplier) = Self::evaluate_variant_multiplier(
1453                    &mut self.threshold_multipliers,
1454                    multiplier_workspace.as_mut(),
1455                    variant_id,
1456                    topology,
1457                    &real_mass_vector,
1458                    param_builder.model_values(),
1459                    &additional_params,
1460                    momentum_sample,
1461                    &root_in_metadata_lmb,
1462                    evaluation_metadata,
1463                )?;
1464                let both_zero = local_multiplier.is_zero() && integrated_multiplier.is_zero();
1465                let evaluation = if both_zero {
1466                    EvaluatedAmplitudeThreshold::new(momentum_sample.zero(), record_components)
1467                        .finish(&local_multiplier, &integrated_multiplier)
1468                } else {
1469                    evaluate_generalized_rstar(
1470                        graph,
1471                        settings,
1472                        param_builder,
1473                        orientation,
1474                        evaluation_metadata,
1475                        &real_mass_vector,
1476                        &lmbs,
1477                        root_lmb,
1478                        &root_in_common_lmb,
1479                        &center,
1480                        &unit_shifted_momenta,
1481                        &radius,
1482                        &solution,
1483                        esurface,
1484                        synthetic_esurface_id,
1485                        raised_esurface_id,
1486                        subspace,
1487                        &thresholds,
1488                        &threshold_subspaces,
1489                        &threshold_masses,
1490                        &overlap,
1491                        overlap_group_index,
1492                        max_prefactor_power,
1493                        &mut self.variant_evaluators[variant_id],
1494                        &mut self.variant_helper_evaluators[variant_id],
1495                        &local_multiplier,
1496                        &integrated_multiplier,
1497                        record_components,
1498                    )?
1499                };
1500                let value = evaluation.weighted.clone();
1501                append_amplitude_component_evaluations(
1502                    &mut components,
1503                    &evaluation,
1504                    variant_id,
1505                    raised_esurface_id,
1506                    overlap_group_index,
1507                    &local_multiplier,
1508                    &integrated_multiplier,
1509                );
1510
1511                local_counterterms.push(AmplitudeLocalCountertermEvaluation {
1512                    variant_id: Some(variant_id),
1513                    esurface_id: raised_esurface_id,
1514                    overlap_group: overlap_group_index,
1515                    value: value.clone(),
1516                });
1517                total += value;
1518            }
1519        }
1520
1521        if radial_root_failed {
1522            return Ok(AmplitudeCountertermEvaluation {
1523                total: Complex::new_re(F::from_f64(f64::NAN)),
1524                local_counterterms,
1525                components: None,
1526            });
1527        }
1528        Ok(AmplitudeCountertermEvaluation {
1529            total,
1530            local_counterterms,
1531            components,
1532        })
1533    }
1534
1535    #[allow(clippy::too_many_arguments)]
1536    fn kinematics_for_variant_approach_grouped<T: FloatLike>(
1537        &self,
1538        momentum_sample: &MomentumSample<T>,
1539        graph: &Graph,
1540        model: &Model,
1541        esurfaces: &EsurfaceCollection,
1542        rotation: &Rotation,
1543        settings: &RuntimeSettings,
1544    ) -> Result<OverlapStructureWithKinematics<T>> {
1545        let catalogue = self.overlap_catalogue(graph, esurfaces);
1546        let mut groups = BTreeMap::new();
1547        for (instance, (_, _, metadata, _)) in catalogue.variants.iter().enumerate() {
1548            groups
1549                .entry((
1550                    metadata.group_id,
1551                    metadata.subspace.solve_signature(&catalogue.lmbs),
1552                ))
1553                .or_insert_with(Vec::new)
1554                .push(instance);
1555        }
1556        let mut combined = OverlapStructureWithKinematics {
1557            existing_esurfaces: ExistingEsurfaces::new(),
1558            variant_ids: Some(TiVec::new()),
1559            overlap_groups_with_kinematics: Vec::new(),
1560        };
1561        for (_, members) in groups {
1562            let mut group_result = self.kinematics_for_variant_approach(
1563                momentum_sample,
1564                graph,
1565                model,
1566                esurfaces,
1567                rotation,
1568                settings,
1569                &catalogue,
1570                &members,
1571            )?;
1572            let existing_offset = combined.existing_esurfaces.len();
1573            if let (Some(all_variants), Some(group_variants)) =
1574                (&mut combined.variant_ids, group_result.variant_ids.take())
1575            {
1576                all_variants.extend(group_variants);
1577            }
1578            combined
1579                .existing_esurfaces
1580                .extend(group_result.existing_esurfaces);
1581            for mut group in group_result.overlap_groups_with_kinematics {
1582                group.overlap_group.existing_esurfaces = group
1583                    .overlap_group
1584                    .existing_esurfaces
1585                    .into_iter()
1586                    .map(|id| ExistingEsurfaceId::from(usize::from(id) + existing_offset))
1587                    .collect();
1588                group.overlap_group.complement = group
1589                    .overlap_group
1590                    .complement
1591                    .into_iter()
1592                    .map(|id| ExistingEsurfaceId::from(usize::from(id) + existing_offset))
1593                    .collect();
1594                combined.overlap_groups_with_kinematics.push(group);
1595            }
1596        }
1597        Ok(combined)
1598    }
1599
1600    #[allow(clippy::too_many_arguments)]
1601    fn kinematics_for_variant_approach<T: FloatLike>(
1602        &self,
1603        momentum_sample: &MomentumSample<T>,
1604        graph: &Graph,
1605        model: &Model,
1606        _esurfaces: &EsurfaceCollection,
1607        rotation: &Rotation,
1608        settings: &RuntimeSettings,
1609        catalogue: &AmplitudeOverlapCatalogue,
1610        allowed_instances: &[usize],
1611    ) -> Result<OverlapStructureWithKinematics<T>> {
1612        if self.variant_evaluators.len() != self.variant_subspaces.len()
1613            || self.variant_evaluators.len() != self.variant_raised_esurfaces.len()
1614            || self.variant_evaluators.len() != self.variant_generated_mask.len()
1615            || self.variant_evaluators.len() != self.variant_active_mask.len()
1616        {
1617            return Err(eyre!(
1618                "Amplitude graph '{}' has inconsistent threshold-variant approach dimensions",
1619                graph.name,
1620            ));
1621        }
1622
1623        let PreparedAmplitudeOverlap {
1624            thresholds,
1625            threshold_subspaces,
1626            synthetic_variants,
1627            sample_in_common_lmb,
1628            lmbs,
1629            parent_lmb,
1630            overlap,
1631            ..
1632        } = catalogue.prepare_group(
1633            allowed_instances,
1634            momentum_sample,
1635            model,
1636            settings,
1637            rotation,
1638        )?;
1639        let topology = &catalogue.graphs[GraphGroupPosition::from(0)];
1640        let common_lmb = &lmbs[parent_lmb];
1641        let common_subspace = &catalogue.variants[allowed_instances[0]].2.subspace;
1642        let real_mass_vector = graph.get_real_mass_vector(model);
1643        let e_cm = F::from_f64(settings.kinematics.e_cm);
1644        let sample_f64 = momentum_sample_to_f64(&sample_in_common_lmb);
1645        let mut existing_esurfaces = ExistingEsurfaces::new();
1646        let mut variant_ids = TiVec::new();
1647        let mut local_ids = BTreeMap::new();
1648        for (synthetic_id, &instance) in synthetic_variants.iter_enumerated() {
1649            let (owner, variant_id, _, _) = &catalogue.variants[instance];
1650            if *owner != self.own_group_position {
1651                continue;
1652            }
1653            let raised = self.variant_raised_esurfaces[*variant_id];
1654            let group_surface = self
1655                .esurface_map
1656                .iter_enumerated()
1657                .find_map(|(id, map)| (map[*owner] == Some(raised)).then_some(id))
1658                .ok_or_else(|| {
1659                    eyre!(
1660                        "Amplitude graph '{}' variant {} has no group E-surface mapping",
1661                        graph.name,
1662                        variant_id.0
1663                    )
1664                })?;
1665            local_ids.insert(
1666                ExistingEsurfaceId::from(synthetic_id.0),
1667                ExistingEsurfaceId::from(existing_esurfaces.len()),
1668            );
1669            existing_esurfaces.push(group_surface);
1670            variant_ids.push(*variant_id);
1671        }
1672        let mut radial_root_diagnostics = RadialRootDiagnostics::default();
1673        let mut overlap_groups_with_kinematics = Vec::with_capacity(overlap.overlap_groups.len());
1674        for (overlap_group_index, overlap_group) in overlap.overlap_groups.iter().enumerate() {
1675            let mut group_center_in_common_lmb = sample_f64.clone();
1676            for loop_index in common_subspace.iter_lmb_indices() {
1677                group_center_in_common_lmb.sample.loop_moms[loop_index] =
1678                    overlap_group.center[loop_index];
1679            }
1680            let group_center = group_center_in_common_lmb
1681                .lmb_transform(common_lmb, &graph.loop_momentum_basis)
1682                .loop_moms()
1683                .clone();
1684            let mut loop_momenta_at_esurface = TiVec::new();
1685            let mut approach_centers_at_esurface = TiVec::new();
1686
1687            for &existing_esurface_id in &overlap_group.existing_esurfaces {
1688                let synthetic_esurface_id = overlap.existing_esurfaces[existing_esurface_id];
1689                let instance = synthetic_variants[synthetic_esurface_id];
1690                let (owner, variant_id, metadata, _) = &catalogue.variants[instance];
1691                if *owner != self.own_group_position {
1692                    continue;
1693                }
1694                let variant_id = *variant_id;
1695                self.ensure_active_variant(variant_id)?;
1696                let raised_esurface_id = self.variant_raised_esurfaces[variant_id];
1697                let subspace = &threshold_subspaces[synthetic_esurface_id.0];
1698                let esurface = &thresholds[synthetic_esurface_id];
1699                let native_geometry = metadata.requested_parent_lmb.is_none();
1700                let geometry = if native_geometry { graph } else { topology };
1701                let root_lmb = if native_geometry {
1702                    &graph.loop_momentum_basis
1703                } else {
1704                    common_lmb
1705                };
1706
1707                let mut projected_center = overlap_group.center.cast::<T>();
1708                for loop_index in (0..projected_center.0.len()).map(LoopIndex::from) {
1709                    if !subspace.contains_loop_index(loop_index) {
1710                        projected_center[loop_index] = ThreeMomentum::new(
1711                            momentum_sample.zero(),
1712                            momentum_sample.zero(),
1713                            momentum_sample.zero(),
1714                        );
1715                    }
1716                }
1717                let shifted_loop_momenta = sample_in_common_lmb.loop_moms() - &projected_center;
1718                let radius = shifted_loop_momenta
1719                    .hyper_radius_squared(subspace.as_subspace_simple())
1720                    .sqrt();
1721                let unit_shifted_momenta =
1722                    shifted_loop_momenta.rescale(&radius.inv(), subspace.as_subspace_simple());
1723                let (raw_radius_guess, _) = esurface.get_radius_guess_subspace(
1724                    &unit_shifted_momenta,
1725                    sample_in_common_lmb.external_moms(),
1726                    subspace,
1727                    &lmbs,
1728                    geometry,
1729                    &real_mass_vector,
1730                );
1731                let function = |r: &_| {
1732                    esurface.compute_self_and_r_derivative_subspace(
1733                        r,
1734                        &unit_shifted_momenta,
1735                        &projected_center,
1736                        sample_in_common_lmb.external_moms(),
1737                        &real_mass_vector,
1738                        subspace,
1739                        &lmbs,
1740                        geometry,
1741                    )
1742                };
1743                let zero = raw_radius_guess.zero();
1744                let mut radius_guess = raw_radius_guess.clone();
1745                if radius_guess.is_nan() || radius_guess.is_infinite() || radius_guess <= zero {
1746                    radius_guess = e_cm.clone();
1747                }
1748                let identity = Self::variant_radial_root_identity(
1749                    &graph.name,
1750                    overlap_group_index,
1751                    variant_id,
1752                    raised_esurface_id,
1753                    rotation,
1754                );
1755                let tolerance = F::from_f64(settings.subtraction.radial_root_residual_tolerance);
1756                let solution = radial_root_diagnostics
1757                    .solve(
1758                        &identity,
1759                        &zero,
1760                        &radius_guess,
1761                        function,
1762                        &tolerance,
1763                        MAX_ITERATIONS,
1764                        64,
1765                        &e_cm,
1766                    )
1767                    .map_err(|error| {
1768                        eyre!(
1769                            "Could not construct threshold-counterterm approach kinematics for amplitude graph '{}' variant {} raised E-surface {} in overlap group {}: {error}",
1770                            graph.name,
1771                            variant_id.0,
1772                            raised_esurface_id.0,
1773                            overlap_group_index,
1774                        )
1775                    })?;
1776
1777                let root_loop_momenta = &unit_shifted_momenta
1778                    .rescale(&solution.solution, subspace.as_subspace_simple())
1779                    + &projected_center;
1780                let mut root_in_common_lmb = sample_in_common_lmb.clone();
1781                root_in_common_lmb.sample.loop_moms = root_loop_momenta;
1782                let root_in_generation_lmb =
1783                    root_in_common_lmb.lmb_transform(root_lmb, &graph.loop_momentum_basis);
1784
1785                let mut full_center_in_common_lmb = sample_in_common_lmb.clone();
1786                for loop_index in subspace.iter_lmb_indices() {
1787                    full_center_in_common_lmb.sample.loop_moms[loop_index] =
1788                        projected_center[loop_index].clone();
1789                }
1790                let full_center_in_generation_lmb = full_center_in_common_lmb
1791                    .lmb_transform(root_lmb, &graph.loop_momentum_basis)
1792                    .loop_moms()
1793                    .clone();
1794
1795                loop_momenta_at_esurface.push(Some(root_in_generation_lmb));
1796                approach_centers_at_esurface.push(Some(full_center_in_generation_lmb));
1797            }
1798
1799            // This is a graph-local approach report, not the catalogue used by MC normalization.
1800            if loop_momenta_at_esurface.is_empty() {
1801                continue;
1802            }
1803            overlap_groups_with_kinematics.push(OverlapGroupWithKinematics {
1804                overlap_group: OverlapGroup {
1805                    existing_esurfaces: overlap_group
1806                        .existing_esurfaces
1807                        .iter()
1808                        .filter_map(|id| local_ids.get(id).copied())
1809                        .collect(),
1810                    complement: overlap_group
1811                        .complement
1812                        .iter()
1813                        .filter_map(|id| local_ids.get(id).copied())
1814                        .collect(),
1815                    center: group_center,
1816                    prefactor_evaluator: None,
1817                },
1818                loop_momenta_at_esurface,
1819                approach_centers_at_esurface: Some(approach_centers_at_esurface),
1820            });
1821        }
1822
1823        Ok(OverlapStructureWithKinematics {
1824            existing_esurfaces,
1825            variant_ids: Some(variant_ids),
1826            overlap_groups_with_kinematics,
1827        })
1828    }
1829
1830    #[allow(clippy::too_many_arguments)]
1831    pub fn kinematics_for_approach<T: FloatLike>(
1832        &mut self,
1833        momentum_sample: &MomentumSample<T>,
1834        graph: &Graph,
1835        model: &Model,
1836        esurfaces: &EsurfaceCollection,
1837        rotation: &Rotation,
1838        settings: &RuntimeSettings,
1839    ) -> Result<OverlapStructureWithKinematics<T>> {
1840        if !self.legacy_equivalent {
1841            return self.kinematics_for_variant_approach_grouped(
1842                momentum_sample,
1843                graph,
1844                model,
1845                esurfaces,
1846                rotation,
1847                settings,
1848            );
1849        }
1850
1851        let counter_term_builder = CounterTermBuilder::new(
1852            graph,
1853            model,
1854            rotation,
1855            settings,
1856            esurfaces,
1857            momentum_sample,
1858            &self.overlap,
1859            &self.raised_data,
1860            self.own_group_position,
1861            &self.esurface_map,
1862        );
1863
1864        let mut overlap_sturcture_with_kinematics = OverlapStructureWithKinematics {
1865            existing_esurfaces: self.overlap.existing_esurfaces.clone(),
1866            variant_ids: None,
1867            overlap_groups_with_kinematics: Vec::new(),
1868        };
1869        let mut radial_root_diagnostics = RadialRootDiagnostics::default();
1870
1871        for (overlap_group, group) in self.overlap.overlap_groups.iter().enumerate() {
1872            let overlap_builder = counter_term_builder.new_overlap_builder(group);
1873
1874            let mut loop_momenta_at_esurface: TiVec<ExistingEsurfaceId, Option<MomentumSample<T>>> =
1875                ti_vec![];
1876            for existing_esurface_id in group.existing_esurfaces.iter() {
1877                let single_result = overlap_builder
1878                    .new_esurface_builder(*existing_esurface_id)
1879                    .map(|esurface_builder| -> Result<_> {
1880                        self.ensure_active_raised_esurface(esurface_builder.raised_esurface_id)?;
1881                        let raised_esurface_id = esurface_builder.raised_esurface_id;
1882                        let radial_root_identity = Self::radial_root_identity(
1883                            &graph.name,
1884                            overlap_group,
1885                            raised_esurface_id,
1886                            rotation,
1887                        );
1888                        let rstar_sample = esurface_builder
1889                            .solve_rstar(
1890                                &radial_root_identity,
1891                                &mut radial_root_diagnostics,
1892                            )
1893                            .ok_or_else(|| {
1894                                eyre!(
1895                                    "Could not construct threshold-counterterm kinematics for raised E-surface {} in probe rotation {}",
1896                                    raised_esurface_id.0,
1897                                    rotation.method,
1898                                )
1899                            })?
1900                            .rstar_samples();
1901                        Result::Ok(rstar_sample.rstar_sample)
1902                    })
1903                    .transpose()?;
1904
1905                loop_momenta_at_esurface.push(single_result);
1906            }
1907
1908            overlap_sturcture_with_kinematics
1909                .overlap_groups_with_kinematics
1910                .push(OverlapGroupWithKinematics {
1911                    overlap_group: group.clone(),
1912                    loop_momenta_at_esurface,
1913                    approach_centers_at_esurface: None,
1914                });
1915        }
1916
1917        Ok(overlap_sturcture_with_kinematics)
1918    }
1919}
1920
1921pub struct OverlapGroupWithKinematics<T: FloatLike> {
1922    pub overlap_group: OverlapGroup,
1923    pub loop_momenta_at_esurface: TiVec<ExistingEsurfaceId, Option<MomentumSample<T>>>,
1924    /// Full fixed-complement centers aligned with `loop_momenta_at_esurface`.
1925    /// `None` retains the homogeneous legacy group center exactly.
1926    pub approach_centers_at_esurface: Option<TiVec<ExistingEsurfaceId, Option<LoopMomenta<F<T>>>>>,
1927}
1928
1929pub struct OverlapStructureWithKinematics<T: FloatLike> {
1930    pub existing_esurfaces: ExistingEsurfaces,
1931    /// Stable semantic variant IDs aligned with `existing_esurfaces` on the generalized path.
1932    /// `None` identifies the untouched legacy group-E-surface representation.
1933    pub variant_ids: Option<TiVec<ExistingEsurfaceId, ThresholdCountertermVariantId>>,
1934    pub overlap_groups_with_kinematics: Vec<OverlapGroupWithKinematics<T>>,
1935}
1936
1937fn momentum_sample_to_f64<T: FloatLike>(sample: &MomentumSample<T>) -> MomentumSample<f64> {
1938    MomentumSample {
1939        sample: BareMomentumSample {
1940            loop_moms: sample
1941                .loop_moms()
1942                .iter()
1943                .map(|momentum| momentum.map_ref(&F::into_ff64))
1944                .collect(),
1945            dual_loop_moms: None,
1946            loop_mom_cache_id: sample.sample.loop_mom_cache_id,
1947            loop_mom_base_cache_id: sample.sample.loop_mom_base_cache_id,
1948            external_moms: sample
1949                .external_moms()
1950                .iter()
1951                .map(|momentum| momentum.map_ref(&F::into_ff64))
1952                .collect(),
1953            external_mom_cache_id: sample.sample.external_mom_cache_id,
1954            external_mom_base_cache_id: sample.sample.external_mom_base_cache_id,
1955            jacobian: sample.sample.jacobian.into_ff64(),
1956            orientation: sample.sample.orientation,
1957            parameterization_branch: sample.sample.parameterization_branch,
1958        },
1959    }
1960}
1961
1962fn dualize_external_momenta<T: FloatLike>(
1963    shape: &HyperDual<F<T>>,
1964    external_moms: &ExternalFourMomenta<F<T>>,
1965) -> ExternalFourMomenta<HyperDual<F<T>>> {
1966    external_moms
1967        .iter()
1968        .map(|momentum| momentum.map_ref(&|component| new_constant(shape, component)))
1969        .collect()
1970}
1971
1972fn real_dual_to_complex<T: FloatLike>(dual: HyperDual<F<T>>) -> HyperDual<Complex<F<T>>> {
1973    let shape = dual
1974        .get_shape()
1975        .iter()
1976        .map(|entry| entry.to_vec())
1977        .collect();
1978    let values = dual.values.into_iter().map(Complex::new_re).collect();
1979    HyperDual::from_values(shape, values)
1980}
1981
1982fn repeated_product<T>(value: T, power: usize, one: T) -> T
1983where
1984    T: Clone + std::ops::Mul<T, Output = T>,
1985{
1986    (0..power).fold(one, |product, _| product * value.clone())
1987}
1988
1989#[allow(clippy::too_many_arguments)]
1990fn evaluate_generalized_multichanneling_prefactor<T: FloatLike>(
1991    momentum_sample: &MomentumSample<T>,
1992    threshold_masses: &[EdgeVec<F<T>>],
1993    all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
1994    thresholds: &EsurfaceCollection,
1995    threshold_subspaces: &[SubspaceData],
1996    overlap: &SubspaceOverlapStructure,
1997    overlap_group_index: usize,
1998    power: usize,
1999) -> DualOrNot<Complex<F<T>>> {
2000    if overlap.overlap_groups.len() < 2 {
2001        return DualOrNot::NonDual(Complex::new_re(momentum_sample.one()));
2002    }
2003
2004    if let Some(dual_loop_momenta) = &momentum_sample.sample.dual_loop_moms {
2005        let shape = &dual_loop_momenta[LoopIndex(0)].px;
2006        let dual_external_momenta =
2007            dualize_external_momenta(shape, momentum_sample.external_moms());
2008        let group_products = overlap
2009            .overlap_groups
2010            .iter()
2011            .map(|group| {
2012                group.complement.iter().fold(
2013                    new_constant(shape, &momentum_sample.one()),
2014                    |product, &existing_esurface_id| {
2015                        let esurface_id = overlap.existing_esurfaces[existing_esurface_id];
2016                        let subspace = &threshold_subspaces[esurface_id.0];
2017                        let value = thresholds[esurface_id].compute_from_dual_momenta(
2018                            subspace.get_lmb(all_lmbs),
2019                            &threshold_masses[esurface_id.0],
2020                            dual_loop_momenta,
2021                            &dual_external_momenta,
2022                        );
2023                        product
2024                            * repeated_product(
2025                                value,
2026                                power,
2027                                new_constant(shape, &momentum_sample.one()),
2028                            )
2029                    },
2030                )
2031            })
2032            .collect::<Vec<_>>();
2033        let denominator = group_products
2034            .iter()
2035            .skip(1)
2036            .fold(group_products[0].clone(), |sum, product| sum + product);
2037        return DualOrNot::Dual(real_dual_to_complex(
2038            group_products[overlap_group_index].clone() / denominator,
2039        ));
2040    }
2041
2042    let group_products = overlap
2043        .overlap_groups
2044        .iter()
2045        .map(|group| {
2046            group
2047                .complement
2048                .iter()
2049                .map(|&existing_esurface_id| {
2050                    let esurface_id = overlap.existing_esurfaces[existing_esurface_id];
2051                    let subspace = &threshold_subspaces[esurface_id.0];
2052                    let value = thresholds[esurface_id].compute_from_momenta(
2053                        subspace.get_lmb(all_lmbs),
2054                        &threshold_masses[esurface_id.0],
2055                        momentum_sample.loop_moms(),
2056                        momentum_sample.external_moms(),
2057                    );
2058                    repeated_product(value, power, momentum_sample.one())
2059                })
2060                .fold(momentum_sample.one(), |product, value| product * value)
2061        })
2062        .collect::<Vec<_>>();
2063    let denominator = group_products
2064        .iter()
2065        .skip(1)
2066        .fold(group_products[0].clone(), |sum, value| sum + value);
2067    DualOrNot::NonDual(Complex::new_re(
2068        group_products[overlap_group_index].clone() / denominator,
2069    ))
2070}
2071
2072#[allow(clippy::too_many_arguments)]
2073fn evaluate_generalized_rstar<T: FloatLike>(
2074    graph: &Graph,
2075    settings: &RuntimeSettings,
2076    param_builder: &mut ParamBuilder<f64>,
2077    orientations: SingleOrAllOrientations<'_, OrientationID>,
2078    evaluation_metadata: &mut EvaluationMetaData,
2079    real_mass_vector: &EdgeVec<F<T>>,
2080    all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
2081    common_lmb: &LoopMomentumBasis,
2082    root_sample: &MomentumSample<T>,
2083    center: &LoopMomenta<F<T>>,
2084    unit_shifted_momenta: &LoopMomenta<F<T>>,
2085    radius: &F<T>,
2086    solution: &NewtonIterationResult<T>,
2087    esurface: &Esurface,
2088    esurface_id: EsurfaceID,
2089    raised_esurface_id: RaisedEsurfaceId,
2090    subspace: &SubspaceData,
2091    thresholds: &EsurfaceCollection,
2092    threshold_subspaces: &[SubspaceData],
2093    threshold_masses: &[EdgeVec<F<T>>],
2094    overlap: &SubspaceOverlapStructure,
2095    overlap_group_index: usize,
2096    prefactor_power: usize,
2097    ct_evaluator: &mut AmplitudeCountertermEvaluator,
2098    helper_evaluators: &mut [GenericEvaluator],
2099    local_multiplier: &F<T>,
2100    integrated_multiplier: &F<T>,
2101    record_components: bool,
2102) -> Result<EvaluatedAmplitudeThreshold<T>> {
2103    let radius_star = &solution.solution;
2104    let e_cm = F::from_f64(settings.kinematics.e_cm);
2105    let uv_settings = &settings.subtraction.local_ct_settings.uv_localisation;
2106    let integrated_settings = &settings.subtraction.integrated_ct_settings;
2107    let uv_damp_plus = evaluate_uv_damper(radius, radius_star, &e_cm, uv_settings);
2108    let uv_damp_minus = evaluate_uv_damper(&-radius, radius_star, &e_cm, uv_settings);
2109    let h_function =
2110        evaluate_integrated_ct_normalisation(radius, radius_star, &e_cm, integrated_settings);
2111    let mut evaluation = EvaluatedAmplitudeThreshold::new(root_sample.zero(), record_components);
2112
2113    for (cut_cff_index, evaluator_stack) in &mut ct_evaluator.evaluator_stacks {
2114        let order_index = cut_cff_index.left_threshold_order.unwrap() - 1;
2115        let (sample_for_order_in_common_lmb, threshold_params) = if order_index == 0 {
2116            (
2117                root_sample.clone(),
2118                ThresholdParams {
2119                    radius: DualOrNot::NonDual(radius.clone()),
2120                    radius_star: DualOrNot::NonDual(radius_star.clone()),
2121                    esurface_derivative: DualOrNot::NonDual(
2122                        solution.derivative_at_solution.clone(),
2123                    ),
2124                    uv_damp_plus: DualOrNot::NonDual(uv_damp_plus.clone()),
2125                    uv_damp_minus: DualOrNot::NonDual(uv_damp_minus.clone()),
2126                    h_function: DualOrNot::NonDual(h_function.clone()),
2127                },
2128            )
2129        } else {
2130            let dual_shape = HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index));
2131            let dual_radius_star = dual_shape.variable(0, radius_star.clone());
2132            let dual_center = center
2133                .iter()
2134                .map(|momentum| momentum.map_ref(&|value| new_constant(&dual_radius_star, value)))
2135                .collect::<LoopMomenta<_>>();
2136            let dual_loop_momenta = unit_shifted_momenta
2137                .rescale_with_hyper_dual(&dual_radius_star, subspace.as_subspace_simple())
2138                .iter()
2139                .zip(dual_center.iter())
2140                .map(|(momentum, center)| momentum.clone() + center.clone())
2141                .collect::<LoopMomenta<_>>();
2142            let mut sample = root_sample.clone();
2143            sample.sample.dual_loop_moms = Some(dual_loop_momenta);
2144            (
2145                sample,
2146                ThresholdParams {
2147                    radius: DualOrNot::Dual(new_constant(&dual_radius_star, radius)),
2148                    radius_star: DualOrNot::Dual(dual_radius_star.clone()),
2149                    esurface_derivative: DualOrNot::Dual(new_constant(
2150                        &dual_radius_star,
2151                        &solution.derivative_at_solution,
2152                    )),
2153                    uv_damp_plus: DualOrNot::Dual(new_constant(&dual_radius_star, &uv_damp_plus)),
2154                    uv_damp_minus: DualOrNot::Dual(new_constant(&dual_radius_star, &uv_damp_minus)),
2155                    h_function: DualOrNot::Dual(new_constant(&dual_radius_star, &h_function)),
2156                },
2157            )
2158        };
2159
2160        let esurface_derivatives = if order_index == 0 {
2161            DualOrNot::NonDual(solution.derivative_at_solution.clone())
2162        } else {
2163            let dual_shape = HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index + 1));
2164            let dual_radius_star = dual_shape.variable(0, radius_star.clone());
2165            let dual_center = center
2166                .iter()
2167                .map(|momentum| momentum.map_ref(&|value| new_constant(&dual_radius_star, value)))
2168                .collect::<LoopMomenta<_>>();
2169            let dual_loop_momenta = unit_shifted_momenta
2170                .rescale_with_hyper_dual(&dual_radius_star, subspace.as_subspace_simple())
2171                .iter()
2172                .zip(dual_center.iter())
2173                .map(|(momentum, center)| momentum.clone() + center.clone())
2174                .collect::<LoopMomenta<_>>();
2175            let dual_externals =
2176                dualize_external_momenta(&dual_radius_star, root_sample.external_moms());
2177            DualOrNot::Dual(esurface.compute_from_dual_momenta(
2178                common_lmb,
2179                real_mass_vector,
2180                &dual_loop_momenta,
2181                &dual_externals,
2182            ))
2183        };
2184
2185        let sample_for_order =
2186            sample_for_order_in_common_lmb.lmb_transform(common_lmb, &graph.loop_momentum_basis);
2187        let params = T::get_parameters(
2188            param_builder,
2189            (false, false),
2190            graph,
2191            &sample_for_order,
2192            settings.kinematics.externals.get_helicities(),
2193            &settings.additional_params(),
2194            Some(&threshold_params),
2195            None,
2196            None,
2197        );
2198        let pass_one = evaluator_stack
2199            .evaluate(params, orientations, settings, evaluation_metadata)
2200            .expect("Amplitude variant counterterm evaluator stack failed")
2201            .pop()
2202            .unwrap();
2203        let prefactor = evaluate_generalized_multichanneling_prefactor(
2204            &sample_for_order_in_common_lmb,
2205            threshold_masses,
2206            all_lmbs,
2207            thresholds,
2208            threshold_subspaces,
2209            overlap,
2210            overlap_group_index,
2211            prefactor_power,
2212        );
2213        let pass_one = multiply_dual_or_not_complex(pass_one, &prefactor);
2214
2215        let mut helper_params = Vec::new();
2216        match pass_one {
2217            DualOrNot::Dual(dual) => {
2218                helper_params.extend_from_slice(&extract_t_derivatives_complex(dual));
2219            }
2220            DualOrNot::NonDual(value) => helper_params.push(value),
2221        }
2222        match esurface_derivatives {
2223            DualOrNot::Dual(dual) => {
2224                helper_params.extend(
2225                    extract_t_derivatives(dual)[1..]
2226                        .iter()
2227                        .cloned()
2228                        .map(Complex::new_re),
2229                );
2230            }
2231            DualOrNot::NonDual(value) => helper_params.push(Complex::new_re(value)),
2232        }
2233        helper_params.push(Complex::new_re(radius.clone()));
2234        helper_params.push(Complex::new_re(radius_star.clone()));
2235        helper_params.push(Complex::new_re(uv_damp_plus.clone()));
2236        helper_params.push(Complex::new_re(uv_damp_minus.clone()));
2237        helper_params.push(Complex::new_re(h_function.clone()));
2238
2239        let mut pieces = evaluate_evaluator(
2240            &mut helper_evaluators[order_index],
2241            &helper_params,
2242            evaluation_metadata,
2243        );
2244        assert_eq!(
2245            pieces.len(),
2246            2,
2247            "amplitude threshold helper must return local and integrated pieces",
2248        );
2249        let integrated = pieces.pop().unwrap().unwrap_real();
2250        let local = pieces.pop().unwrap().unwrap_real();
2251        evaluation.add_completed_pieces(local, integrated, local_multiplier, integrated_multiplier);
2252    }
2253
2254    crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2255        stage = "amplitude_threshold_variant_total",
2256        graph = %graph.name,
2257        esurface_id = esurface_id.0,
2258        raised_esurface_id = raised_esurface_id.0,
2259        overlap_group = overlap_group_index,
2260        result = %format!("{:+16e}", evaluation.weighted),
2261        "amplitude threshold variant total"
2262    );
2263    Ok(evaluation.finish(local_multiplier, integrated_multiplier))
2264}
2265
2266struct CounterTermBuilder<'a, T: FloatLike> {
2267    overlap_structure: &'a OverlapStructure,
2268    raised_data: &'a RaisedEsurfaceData,
2269    own_group_position: GraphGroupPosition,
2270    esurface_map: &'a TiVec<GroupEsurfaceId, TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>>,
2271    real_mass_vector: EdgeVec<F<T>>,
2272    e_cm: F<T>,
2273    graph: &'a Graph,
2274    rotation_for_overlap: &'a Rotation,
2275    settings: &'a RuntimeSettings,
2276    esurface_collection: &'a EsurfaceCollection,
2277    sample: &'a MomentumSample<T>,
2278}
2279
2280impl<'a, T: FloatLike> CounterTermBuilder<'a, T> {
2281    #[allow(clippy::too_many_arguments)]
2282    fn new(
2283        graph: &'a Graph,
2284        model: &'a Model,
2285        rotation_for_overlap: &'a Rotation,
2286        settings: &'a RuntimeSettings,
2287        esurface_collection: &'a EsurfaceCollection,
2288        sample: &'a MomentumSample<T>,
2289        overlap_structure: &'a OverlapStructure,
2290        raised_data: &'a RaisedEsurfaceData,
2291        own_group_position: GraphGroupPosition,
2292        esurface_map: &'a TiVec<
2293            GroupEsurfaceId,
2294            TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>,
2295        >,
2296    ) -> Self {
2297        let real_mass_vector = graph.get_real_mass_vector(model);
2298        let e_cm = F::from_f64(settings.kinematics.e_cm);
2299
2300        Self {
2301            real_mass_vector,
2302            e_cm,
2303            graph,
2304            rotation_for_overlap,
2305            settings,
2306            esurface_collection,
2307            overlap_structure,
2308            raised_data,
2309            sample,
2310            own_group_position,
2311            esurface_map,
2312        }
2313    }
2314
2315    fn new_overlap_builder(&'a self, overlap_group: &'a OverlapGroup) -> OverlapBuilder<'a, T> {
2316        let center = &overlap_group.center;
2317
2318        // Overlap construction stores amplitude centers in the identity frame. Rotate the center
2319        // exactly once here, alongside the sample rotation used by this stability probe.
2320        let (unrotated_center, rotated_center) = (
2321            center.cast(),
2322            center.rotate(self.rotation_for_overlap).cast(),
2323        );
2324
2325        let shifted_loop_momenta = self.sample.loop_moms() - &rotated_center;
2326        let radius = shifted_loop_momenta.hyper_radius_squared(None).sqrt();
2327        let unit_shifted_momenta = shifted_loop_momenta.rescale(&radius.inv(), None);
2328
2329        OverlapBuilder {
2330            counterterm_builder: self,
2331            overlap_group,
2332            rotated_center,
2333            _unrotated_center: unrotated_center,
2334            unit_shifted_momenta,
2335            radius,
2336        }
2337    }
2338}
2339
2340struct OverlapBuilder<'a, T: FloatLike> {
2341    counterterm_builder: &'a CounterTermBuilder<'a, T>,
2342    overlap_group: &'a OverlapGroup,
2343    /// The center after its single identity-to-probe-frame rotation.
2344    rotated_center: LoopMomenta<F<T>>,
2345    /// The stored identity-frame center, retained for frame diagnostics.
2346    _unrotated_center: LoopMomenta<F<T>>,
2347    unit_shifted_momenta: LoopMomenta<F<T>>,
2348    radius: F<T>,
2349}
2350
2351impl<'a, T: FloatLike> OverlapBuilder<'a, T> {
2352    fn new_esurface_builder(
2353        &'a self,
2354        existing_esurface_id: ExistingEsurfaceId,
2355    ) -> Option<EsurfaceCTBuilder<'a, T>> {
2356        let group_esurface_id = self
2357            .counterterm_builder
2358            .overlap_structure
2359            .existing_esurfaces[existing_esurface_id];
2360
2361        let raised_esurface_id = self.counterterm_builder.esurface_map[group_esurface_id]
2362            [self.counterterm_builder.own_group_position];
2363
2364        raised_esurface_id.map(|raised_esurface_id| {
2365            let esurface_id = self.counterterm_builder.raised_data.raised_groups
2366                [raised_esurface_id]
2367                .esurface_ids[0];
2368
2369            EsurfaceCTBuilder {
2370                overlap_builder: self,
2371                _existing_esurface_id: existing_esurface_id,
2372                group_esurface_id,
2373                esurface: &self.counterterm_builder.esurface_collection[esurface_id],
2374                esurface_id,
2375                raised_esurface_id,
2376            }
2377        })
2378    }
2379}
2380
2381struct EsurfaceCTBuilder<'a, T: FloatLike> {
2382    overlap_builder: &'a OverlapBuilder<'a, T>,
2383    _existing_esurface_id: ExistingEsurfaceId,
2384    group_esurface_id: GroupEsurfaceId,
2385    esurface: &'a Esurface,
2386    esurface_id: EsurfaceID,
2387    raised_esurface_id: RaisedEsurfaceId,
2388}
2389
2390impl<'a, T: FloatLike> EsurfaceCTBuilder<'a, T> {
2391    fn solve_rstar(
2392        self,
2393        radial_root_identity: &RadialRootIdentity,
2394        radial_root_diagnostics: &mut RadialRootDiagnostics,
2395    ) -> Option<RstarSolution<'a, T>> {
2396        let mut all_center_values_valid = true;
2397        let center_surface_values = self
2398            .overlap_builder
2399            .overlap_group
2400            .existing_esurfaces
2401            .iter()
2402            .map(|&existing_esurface_id| {
2403                let group_esurface_id = self
2404                    .overlap_builder
2405                    .counterterm_builder
2406                    .overlap_structure
2407                    .existing_esurfaces[existing_esurface_id];
2408
2409                match self.overlap_builder.counterterm_builder.esurface_map[group_esurface_id]
2410                    [self.overlap_builder.counterterm_builder.own_group_position]
2411                {
2412                    Some(raised_esurface_id) => {
2413                        let esurface_id = self
2414                            .overlap_builder
2415                            .counterterm_builder
2416                            .raised_data
2417                            .raised_groups[raised_esurface_id]
2418                            .esurface_ids[0];
2419                        let esurface =
2420                            &self.overlap_builder.counterterm_builder.esurface_collection
2421                                [esurface_id];
2422                        let value = esurface.compute_from_momenta(
2423                            &self
2424                                .overlap_builder
2425                                .counterterm_builder
2426                                .graph
2427                                .loop_momentum_basis,
2428                            &self.overlap_builder.counterterm_builder.real_mass_vector,
2429                            &self.overlap_builder.rotated_center,
2430                            self.overlap_builder
2431                                .counterterm_builder
2432                                .sample
2433                                .external_moms(),
2434                        );
2435                        let is_valid = esurface_value_is_strictly_inside(
2436                            &value,
2437                            &self.overlap_builder.counterterm_builder.e_cm,
2438                        );
2439                        all_center_values_valid &= is_valid;
2440                        format!(
2441                            "existing={} group={} raised={} local={} edges={:?} value={:+16e} inside={}",
2442                            usize::from(existing_esurface_id),
2443                            group_esurface_id.0,
2444                            raised_esurface_id.0,
2445                            esurface_id.0,
2446                            esurface.energies,
2447                            value,
2448                            is_valid
2449                        )
2450                    }
2451                    None => {
2452                        format!(
2453                            "existing={} group={} absent_in_current_graph",
2454                            usize::from(existing_esurface_id),
2455                            group_esurface_id.0
2456                        )
2457                    }
2458                }
2459            })
2460            .collect::<Vec<_>>();
2461        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect, #center;
2462            stage = "amplitude_threshold_center_values",
2463            graph = %self.overlap_builder.counterterm_builder.graph.name,
2464            selected_existing_esurface_id = usize::from(self._existing_esurface_id),
2465            selected_group_esurface_id = self.group_esurface_id.0,
2466            selected_raised_esurface_id = self.raised_esurface_id.0,
2467            selected_esurface_id = self.esurface_id.0,
2468            rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
2469            center_provenance = "identity_frame_rotated_once",
2470            overlap_group_size = self.overlap_builder.overlap_group.existing_esurfaces.len(),
2471            radius = %format!("{:+16e}", self.overlap_builder.radius),
2472            file.rotated_center = %format!("{}", self.overlap_builder.rotated_center),
2473            file.surface_values = %center_surface_values.join("\n"),
2474            "amplitude threshold center values"
2475        );
2476
2477        if !all_center_values_valid {
2478            warn!(
2479                graph = %self.overlap_builder.counterterm_builder.graph.name,
2480                selected_esurface_id = self.esurface_id.0,
2481                rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
2482                center_provenance = "identity_frame_rotated_once",
2483                center = %self.overlap_builder.rotated_center,
2484                surface_values = %center_surface_values.join("; "),
2485                "refusing to evaluate an amplitude threshold counterterm with an invalid probe-frame overlap center"
2486            );
2487            return None;
2488        }
2489
2490        let (raw_radius_guess, _) = self.esurface.get_radius_guess(
2491            &self.overlap_builder.unit_shifted_momenta,
2492            self.overlap_builder
2493                .counterterm_builder
2494                .sample
2495                .external_moms(),
2496            &self
2497                .overlap_builder
2498                .counterterm_builder
2499                .graph
2500                .loop_momentum_basis,
2501        );
2502
2503        let function = |r: &_| {
2504            self.esurface.compute_self_and_r_derivative(
2505                r,
2506                &self.overlap_builder.unit_shifted_momenta,
2507                &self.overlap_builder.rotated_center,
2508                self.overlap_builder
2509                    .counterterm_builder
2510                    .sample
2511                    .external_moms(),
2512                &self.overlap_builder.counterterm_builder.real_mass_vector,
2513                &self
2514                    .overlap_builder
2515                    .counterterm_builder
2516                    .graph
2517                    .loop_momentum_basis,
2518            )
2519        };
2520
2521        let zero = raw_radius_guess.zero();
2522        let mut radius_guess = raw_radius_guess.clone();
2523        if radius_guess.is_nan() || radius_guess.is_infinite() || radius_guess <= zero {
2524            radius_guess = self.overlap_builder.counterterm_builder.e_cm.clone();
2525        }
2526        let tolerance = F::from_f64(
2527            self.overlap_builder
2528                .counterterm_builder
2529                .settings
2530                .subtraction
2531                .radial_root_residual_tolerance,
2532        );
2533        let solution = match radial_root_diagnostics.solve(
2534            radial_root_identity,
2535            &zero,
2536            &radius_guess,
2537            function,
2538            &tolerance,
2539            MAX_ITERATIONS,
2540            64,
2541            &self.overlap_builder.counterterm_builder.e_cm,
2542        ) {
2543            Ok(solution) => solution,
2544            Err(error) => {
2545                warn!(
2546                    graph = %self.overlap_builder.counterterm_builder.graph.name,
2547                    esurface_id = self.esurface_id.0,
2548                    rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
2549                    center_provenance = "identity_frame_rotated_once",
2550                    raw_radius_guess = %raw_radius_guess,
2551                    radius_guess = %radius_guess,
2552                    error = %error,
2553                    "refusing to evaluate an amplitude threshold counterterm with an invalid radial solution"
2554                );
2555                return None;
2556            }
2557        };
2558        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2559            stage = "amplitude_threshold_rstar_solution",
2560            graph = %self.overlap_builder.counterterm_builder.graph.name,
2561            existing_esurface_id = %self._existing_esurface_id,
2562            group_esurface_id = self.group_esurface_id.0,
2563            raised_esurface_id = self.raised_esurface_id.0,
2564            esurface_id = self.esurface_id.0,
2565            rotation_id = %self.overlap_builder.counterterm_builder.rotation_for_overlap.method,
2566            center_provenance = "identity_frame_rotated_once",
2567            radius_guess = %format!("{:+16e}", radius_guess),
2568            radius_star = %format!("{:+16e}", solution.solution),
2569            derivative = %format!("{:+16e}", solution.derivative_at_solution),
2570            error = %format!("{:+16e}", solution.error_of_function),
2571            iterations = solution.num_iterations_used,
2572            nonfinite = false,
2573            "amplitude threshold rstar solution"
2574        );
2575
2576        Some(RstarSolution {
2577            esurface_ct_builder: self,
2578            solution,
2579        })
2580    }
2581}
2582
2583struct RstarSolution<'a, T: FloatLike> {
2584    esurface_ct_builder: EsurfaceCTBuilder<'a, T>,
2585    solution: NewtonIterationResult<T>,
2586}
2587
2588impl<'a, T: FloatLike> RstarSolution<'a, T> {
2589    fn rstar_samples(self) -> RstarSample<'a, T> {
2590        let rstar_loop_momenta = &self
2591            .esurface_ct_builder
2592            .overlap_builder
2593            .unit_shifted_momenta
2594            .rescale(&self.solution.solution, None)
2595            + &self.esurface_ct_builder.overlap_builder.rotated_center;
2596
2597        let mut rstar_sample = self
2598            .esurface_ct_builder
2599            .overlap_builder
2600            .counterterm_builder
2601            .sample
2602            .clone();
2603
2604        rstar_sample.sample.loop_moms = rstar_loop_momenta;
2605
2606        RstarSample {
2607            rstar_solution: self,
2608            rstar_sample,
2609        }
2610    }
2611}
2612
2613struct RstarSample<'a, T: FloatLike> {
2614    rstar_solution: RstarSolution<'a, T>,
2615    rstar_sample: MomentumSample<T>,
2616}
2617
2618impl<'a, T: FloatLike> RstarSample<'a, T> {
2619    #[allow(clippy::too_many_arguments)]
2620    fn evaluate<'b, 'c: 'b>(
2621        self,
2622        param_builder: &mut ParamBuilder<f64>,
2623        orientations: SingleOrAllOrientations<'a, OrientationID>,
2624        evaluation_metadata: &mut EvaluationMetaData,
2625        ct_evaluator: &mut AmplitudeCountertermEvaluator,
2626        helper_evaluators: &mut [GenericEvaluator],
2627        split_helpers: bool,
2628        record_components: bool,
2629    ) -> Result<EvaluatedAmplitudeThreshold<T>> {
2630        assert!(
2631            split_helpers || !record_components,
2632            "recorded legacy amplitude components require split helpers",
2633        );
2634        let esurface_ct_builder = &self.rstar_solution.esurface_ct_builder;
2635        let ct_builder = esurface_ct_builder.overlap_builder.counterterm_builder;
2636
2637        let esurface_id = esurface_ct_builder.esurface_id;
2638
2639        let model_params = param_builder
2640            .model_values()
2641            .iter()
2642            .map(|c| Complex::new(F::from_ff64(c.re), F::from_ff64(c.im)))
2643            .collect::<Vec<_>>();
2644
2645        let radius = self
2646            .rstar_solution
2647            .esurface_ct_builder
2648            .overlap_builder
2649            .radius
2650            .clone();
2651
2652        let radius_star = self.rstar_solution.solution.solution.clone();
2653        let e_cm = &ct_builder.e_cm;
2654        let settings = &ct_builder
2655            .settings
2656            .subtraction
2657            .local_ct_settings
2658            .uv_localisation;
2659
2660        let integrated_settings = &ct_builder.settings.subtraction.integrated_ct_settings;
2661
2662        let uv_damp_plus = evaluate_uv_damper(&radius, &radius_star, e_cm, settings);
2663        let uv_damp_minus = evaluate_uv_damper(&-&radius, &radius_star, e_cm, settings);
2664
2665        debug!("uv_damp_plus: {:?}", uv_damp_plus);
2666        debug!("uv_damp_minus: {:?}", uv_damp_minus);
2667        debug!("radius: {:?}", radius);
2668        debug!("radius_star: {:?}", radius_star);
2669
2670        let h_function =
2671            evaluate_integrated_ct_normalisation(&radius, &radius_star, e_cm, integrated_settings);
2672        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2673            stage = "amplitude_threshold_prefactors",
2674            graph = %ct_builder.graph.name,
2675            esurface_id = esurface_id.0,
2676            raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
2677            radius = %format!("{:+16e}", radius),
2678            radius_star = %format!("{:+16e}", radius_star),
2679            derivative = %format!(
2680                "{:+16e}",
2681                self.rstar_solution.solution.derivative_at_solution
2682            ),
2683            uv_damp_plus = %format!("{:+16e}", uv_damp_plus),
2684            uv_damp_minus = %format!("{:+16e}", uv_damp_minus),
2685            h_function = %format!("{:+16e}", h_function),
2686            "amplitude threshold prefactors"
2687        );
2688
2689        let debug_diagnostics_enabled = tracing::event_enabled!(tracing::Level::DEBUG);
2690        if debug_diagnostics_enabled {
2691            let coincidence_tolerance = F::from_f64(1.0e-8) * e_cm;
2692            for (candidate_esurface_id, candidate_esurface) in
2693                ct_builder.esurface_collection.iter_enumerated()
2694            {
2695                let value = candidate_esurface.compute_from_momenta(
2696                    &ct_builder.graph.loop_momentum_basis,
2697                    &ct_builder.real_mass_vector,
2698                    self.rstar_sample.loop_moms(),
2699                    self.rstar_sample.external_moms(),
2700                );
2701                if value.abs() < coincidence_tolerance {
2702                    let candidate_raised_esurface_id = ct_builder
2703                        .raised_data
2704                        .raised_groups
2705                        .iter_enumerated()
2706                        .find_map(|(raised_esurface_id, raised_group)| {
2707                            raised_group
2708                                .esurface_ids
2709                                .contains(&candidate_esurface_id)
2710                                .then_some(raised_esurface_id.0)
2711                        });
2712                    let candidate_atom = candidate_esurface.to_atom(&[]).to_string();
2713                    crate::debug_tags!(#integration, #subtraction, #threshold, #inspect, #esurface;
2714                        stage = "amplitude_threshold_rstar_coincident_esurface",
2715                        graph = %ct_builder.graph.name,
2716                        selected_esurface_id = esurface_id.0,
2717                        selected_raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
2718                        candidate_esurface_id = candidate_esurface_id.0,
2719                        candidate_raised_esurface_id = ?candidate_raised_esurface_id,
2720                        selected = candidate_esurface_id == esurface_id,
2721                        value = %format!("{:+16e}", value),
2722                        tolerance = %format!("{:+16e}", coincidence_tolerance),
2723                        file.atom = %candidate_atom,
2724                        "threshold rstar coincident esurface graph={} selected={} raised={} candidate={} candidate_raised={:?} value={} tol={} atom={}",
2725                        ct_builder.graph.name,
2726                        esurface_id.0,
2727                        esurface_ct_builder.raised_esurface_id.0,
2728                        candidate_esurface_id.0,
2729                        candidate_raised_esurface_id,
2730                        format!("{:+16e}", value),
2731                        format!("{:+16e}", coincidence_tolerance),
2732                        candidate_atom
2733                    );
2734                }
2735            }
2736        }
2737
2738        let mut evaluation =
2739            EvaluatedAmplitudeThreshold::new(self.rstar_sample.zero(), record_components);
2740
2741        for (cut_cff_index, evaluator_stack) in ct_evaluator.evaluator_stacks.iter_mut() {
2742            let order_index = cut_cff_index.left_threshold_order.unwrap() - 1;
2743            let (sample_for_order, threshold_params) = if order_index == 0 {
2744                debug!(
2745                    "rescaled loop momenta at rstar:\n{}",
2746                    self.rstar_sample.loop_moms()
2747                );
2748                (
2749                    self.rstar_sample.clone(),
2750                    ThresholdParams {
2751                        radius: DualOrNot::NonDual(radius.clone()),
2752                        radius_star: DualOrNot::NonDual(radius_star.clone()),
2753                        esurface_derivative: DualOrNot::NonDual(
2754                            self.rstar_solution.solution.derivative_at_solution.clone(),
2755                        ),
2756                        uv_damp_plus: DualOrNot::NonDual(uv_damp_plus.clone()),
2757                        uv_damp_minus: DualOrNot::NonDual(uv_damp_minus.clone()),
2758                        h_function: DualOrNot::NonDual(h_function.clone()),
2759                    },
2760                )
2761            } else {
2762                let dual_shape = HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index));
2763                let dual_radius_star = dual_shape.variable(0, radius_star.clone());
2764
2765                let dualized_center = self
2766                    .rstar_solution
2767                    .esurface_ct_builder
2768                    .overlap_builder
2769                    .rotated_center
2770                    .iter()
2771                    .map(|momentum| {
2772                        momentum.map_ref(&|value| new_constant(&dual_radius_star, value))
2773                    })
2774                    .collect::<LoopMomenta<_>>();
2775                let dual_loop_momenta = self
2776                    .rstar_solution
2777                    .esurface_ct_builder
2778                    .overlap_builder
2779                    .unit_shifted_momenta
2780                    .rescale_with_hyper_dual(&dual_radius_star, None)
2781                    .iter()
2782                    .zip(dualized_center.iter())
2783                    .map(|(momentum, center)| momentum.clone() + center.clone())
2784                    .collect::<LoopMomenta<_>>();
2785                debug!("rescaled loop momenta at rstar:\n{}", dual_loop_momenta);
2786                let mut sample_with_duals = self.rstar_sample.clone();
2787                sample_with_duals.sample.dual_loop_moms = Some(dual_loop_momenta);
2788
2789                (
2790                    sample_with_duals,
2791                    ThresholdParams {
2792                        radius: DualOrNot::Dual(new_constant(&dual_radius_star, &radius)),
2793                        radius_star: DualOrNot::Dual(dual_radius_star.clone()),
2794                        esurface_derivative: DualOrNot::Dual(new_constant(
2795                            &dual_radius_star,
2796                            &self.rstar_solution.solution.derivative_at_solution.clone(),
2797                        )),
2798                        uv_damp_plus: DualOrNot::Dual(new_constant(
2799                            &dual_radius_star,
2800                            &uv_damp_plus,
2801                        )),
2802                        uv_damp_minus: DualOrNot::Dual(new_constant(
2803                            &dual_radius_star,
2804                            &uv_damp_minus,
2805                        )),
2806                        h_function: DualOrNot::Dual(new_constant(&dual_radius_star, &h_function)),
2807                    },
2808                )
2809            };
2810
2811            let esurface_derivatives = if order_index == 0 {
2812                DualOrNot::NonDual(self.rstar_solution.solution.derivative_at_solution.clone())
2813            } else {
2814                let dual_shape_for_esurface =
2815                    HyperDual::<F<T>>::new(simple_n_deriv_shape(order_index + 1));
2816                let dual_radius_star_for_esurface =
2817                    dual_shape_for_esurface.variable(0, radius_star.clone());
2818                let dualized_center_for_esurface = self
2819                    .rstar_solution
2820                    .esurface_ct_builder
2821                    .overlap_builder
2822                    .rotated_center
2823                    .iter()
2824                    .map(|momentum| {
2825                        momentum
2826                            .map_ref(&|value| new_constant(&dual_radius_star_for_esurface, value))
2827                    })
2828                    .collect::<LoopMomenta<_>>();
2829                let dual_loop_momenta_for_esurface = self
2830                    .rstar_solution
2831                    .esurface_ct_builder
2832                    .overlap_builder
2833                    .unit_shifted_momenta
2834                    .rescale_with_hyper_dual(&dual_radius_star_for_esurface, None)
2835                    .iter()
2836                    .zip(dualized_center_for_esurface.iter())
2837                    .map(|(momentum, center)| momentum.clone() + center.clone())
2838                    .collect::<LoopMomenta<_>>();
2839
2840                let dualized_externals = self
2841                    .rstar_sample
2842                    .external_moms()
2843                    .iter()
2844                    .map(|momentum| FourMomentum {
2845                        temporal: Energy {
2846                            value: new_constant(
2847                                &dual_radius_star_for_esurface,
2848                                &momentum.temporal.value,
2849                            ),
2850                        },
2851                        spatial: momentum
2852                            .spatial
2853                            .map_ref(&|value| new_constant(&dual_radius_star_for_esurface, value)),
2854                    })
2855                    .collect();
2856
2857                DualOrNot::Dual(
2858                    self.rstar_solution
2859                        .esurface_ct_builder
2860                        .esurface
2861                        .compute_from_dual_momenta(
2862                            &ct_builder.graph.loop_momentum_basis,
2863                            &ct_builder.real_mass_vector,
2864                            &dual_loop_momenta_for_esurface,
2865                            &dualized_externals,
2866                        ),
2867                )
2868            };
2869
2870            let params = T::get_parameters(
2871                param_builder,
2872                (false, false),
2873                ct_builder.graph,
2874                &sample_for_order,
2875                ct_builder.settings.kinematics.externals.get_helicities(),
2876                &ct_builder.settings.additional_params(),
2877                Some(&threshold_params),
2878                None,
2879                None,
2880            );
2881            let params_slice = params.as_slice();
2882            let params_nonfinite_count = params_slice
2883                .iter()
2884                .filter(|value| {
2885                    value.re.is_nan()
2886                        || value.re.is_infinite()
2887                        || value.im.is_nan()
2888                        || value.im.is_infinite()
2889                })
2890                .count();
2891            let first_params_nonfinite_index = params_slice.iter().position(|value| {
2892                value.re.is_nan()
2893                    || value.re.is_infinite()
2894                    || value.im.is_nan()
2895                    || value.im.is_infinite()
2896            });
2897            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2898                stage = "amplitude_threshold_pass_one_params",
2899                graph = %ct_builder.graph.name,
2900                esurface_id = esurface_id.0,
2901                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
2902                order = order_index + 1,
2903                params_len = params_slice.len(),
2904                params_nonfinite_count,
2905                first_params_nonfinite_index = ?first_params_nonfinite_index,
2906                "amplitude threshold pass one params"
2907            );
2908
2909            let pass_one_result = evaluator_stack
2910                .evaluate(
2911                    params,
2912                    orientations,
2913                    ct_builder.settings,
2914                    evaluation_metadata,
2915                )
2916                .expect("Amplitude counterterm evaluator stack failed")
2917                .pop()
2918                .unwrap();
2919
2920            let prefactor = self.evaluate_multichanneling_prefactor(
2921                &sample_for_order,
2922                &model_params,
2923                evaluation_metadata,
2924                order_index,
2925            );
2926
2927            let raw_pass_one_is_nonfinite = match &pass_one_result {
2928                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
2929                    value.re.is_nan()
2930                        || value.re.is_infinite()
2931                        || value.im.is_nan()
2932                        || value.im.is_infinite()
2933                }),
2934                DualOrNot::NonDual(non_dual_result) => {
2935                    non_dual_result.re.is_nan()
2936                        || non_dual_result.re.is_infinite()
2937                        || non_dual_result.im.is_nan()
2938                        || non_dual_result.im.is_infinite()
2939                }
2940            };
2941            let prefactor_is_nonfinite = match &prefactor {
2942                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
2943                    value.re.is_nan()
2944                        || value.re.is_infinite()
2945                        || value.im.is_nan()
2946                        || value.im.is_infinite()
2947                }),
2948                DualOrNot::NonDual(non_dual_result) => {
2949                    non_dual_result.re.is_nan()
2950                        || non_dual_result.re.is_infinite()
2951                        || non_dual_result.im.is_nan()
2952                        || non_dual_result.im.is_infinite()
2953                }
2954            };
2955            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2956                stage = "amplitude_threshold_pass_one_raw",
2957                graph = %ct_builder.graph.name,
2958                esurface_id = esurface_id.0,
2959                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
2960                order = order_index + 1,
2961                raw_result = %format!("{pass_one_result}"),
2962                prefactor = %format!("{prefactor}"),
2963                raw_nonfinite = raw_pass_one_is_nonfinite,
2964                prefactor_nonfinite = prefactor_is_nonfinite,
2965                "amplitude threshold pass one raw"
2966            );
2967
2968            let pass_one_result = multiply_dual_or_not_complex(pass_one_result, &prefactor);
2969            let pass_one_is_nonfinite = match &pass_one_result {
2970                DualOrNot::Dual(dual_result) => dual_result.values.iter().any(|value| {
2971                    value.re.is_nan()
2972                        || value.re.is_infinite()
2973                        || value.im.is_nan()
2974                        || value.im.is_infinite()
2975                }),
2976                DualOrNot::NonDual(non_dual_result) => {
2977                    non_dual_result.re.is_nan()
2978                        || non_dual_result.re.is_infinite()
2979                        || non_dual_result.im.is_nan()
2980                        || non_dual_result.im.is_infinite()
2981                }
2982            };
2983            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
2984                stage = "amplitude_threshold_pass_one",
2985                graph = %ct_builder.graph.name,
2986                esurface_id = esurface_id.0,
2987                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
2988                order = order_index + 1,
2989                prefactor = %format!("{prefactor}"),
2990                result = %format!("{pass_one_result}"),
2991                nonfinite = pass_one_is_nonfinite,
2992                "amplitude threshold pass one"
2993            );
2994
2995            debug!(
2996                "Pass one result for esurface {} order {}: {}",
2997                esurface_id.0,
2998                order_index + 1,
2999                pass_one_result
3000            );
3001
3002            let mut params_for_pass_two = vec![];
3003            match pass_one_result {
3004                DualOrNot::Dual(dual_result) => {
3005                    params_for_pass_two
3006                        .extend_from_slice(&extract_t_derivatives_complex(dual_result));
3007                }
3008                DualOrNot::NonDual(non_dual_result) => {
3009                    params_for_pass_two.push(non_dual_result);
3010                }
3011            }
3012
3013            match esurface_derivatives {
3014                DualOrNot::Dual(dual_e_surface) => {
3015                    extract_t_derivatives(dual_e_surface)[1..]
3016                        .iter()
3017                        .for_each(|value| params_for_pass_two.push(Complex::new_re(value.clone())));
3018                }
3019                DualOrNot::NonDual(non_dual_e_surface) => {
3020                    params_for_pass_two.push(Complex::new_re(non_dual_e_surface));
3021                }
3022            }
3023
3024            params_for_pass_two.push(Complex::new_re(radius.clone()));
3025            params_for_pass_two.push(Complex::new_re(radius_star.clone()));
3026            params_for_pass_two.push(Complex::new_re(uv_damp_plus.clone()));
3027            params_for_pass_two.push(Complex::new_re(uv_damp_minus.clone()));
3028            params_for_pass_two.push(Complex::new_re(h_function.clone()));
3029
3030            let pass_two_result = if split_helpers {
3031                let mut pieces = evaluate_evaluator(
3032                    &mut helper_evaluators[order_index],
3033                    &params_for_pass_two,
3034                    evaluation_metadata,
3035                );
3036                assert_eq!(
3037                    pieces.len(),
3038                    3,
3039                    "recording amplitude threshold helper must return legacy, local, and integrated pieces",
3040                );
3041                let integrated = pieces.pop().unwrap().unwrap_real();
3042                let local = pieces.pop().unwrap().unwrap_real();
3043                let legacy = pieces.pop().unwrap().unwrap_real();
3044                if record_components {
3045                    let one = self.rstar_sample.one();
3046                    let combined = combine_completed_threshold_pieces(
3047                        local.clone(),
3048                        integrated.clone(),
3049                        &one,
3050                        &one,
3051                    );
3052                    evaluation.add_completed_pieces(local, integrated, &one, &one);
3053                    combined
3054                } else {
3055                    evaluation.weighted += legacy.clone();
3056                    legacy
3057                }
3058            } else {
3059                let combined = evaluate_evaluator_single(
3060                    &mut helper_evaluators[order_index],
3061                    &params_for_pass_two,
3062                    evaluation_metadata,
3063                );
3064                evaluation.weighted += combined.clone();
3065                combined
3066            };
3067
3068            debug!(
3069                "Pass two result for esurface {} order {}: {}",
3070                esurface_id.0,
3071                order_index + 1,
3072                pass_two_result
3073            );
3074            let pass_two_is_nonfinite = pass_two_result.re.is_nan()
3075                || pass_two_result.re.is_infinite()
3076                || pass_two_result.im.is_nan()
3077                || pass_two_result.im.is_infinite();
3078            crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
3079                stage = "amplitude_threshold_pass_two",
3080                graph = %ct_builder.graph.name,
3081                esurface_id = esurface_id.0,
3082                raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
3083                order = order_index + 1,
3084                result = %format!("{:+16e}", pass_two_result),
3085                nonfinite = pass_two_is_nonfinite,
3086                "amplitude threshold pass two"
3087            );
3088        }
3089
3090        debug!(
3091            ct_eval = format!("{:+16e}", evaluation.weighted),
3092            "esurface {}", esurface_id.0
3093        );
3094        let total_ct_is_nonfinite = evaluation.weighted.re.is_nan()
3095            || evaluation.weighted.re.is_infinite()
3096            || evaluation.weighted.im.is_nan()
3097            || evaluation.weighted.im.is_infinite();
3098        crate::debug_tags!(#integration, #subtraction, #threshold, #inspect;
3099            stage = "amplitude_threshold_total",
3100            graph = %ct_builder.graph.name,
3101            esurface_id = esurface_id.0,
3102            raised_esurface_id = esurface_ct_builder.raised_esurface_id.0,
3103            result = %format!("{:+16e}", evaluation.weighted),
3104            nonfinite = total_ct_is_nonfinite,
3105            "amplitude threshold total"
3106        );
3107
3108        let one = self.rstar_sample.one();
3109        Ok(evaluation.finish(&one, &one))
3110    }
3111
3112    fn evaluate_multichanneling_prefactor(
3113        &self,
3114        momentum_sample: &MomentumSample<T>,
3115        model_params: &[Complex<F<T>>],
3116        evaluation_metadata: &mut EvaluationMetaData,
3117        order_index: usize,
3118    ) -> DualOrNot<Complex<F<T>>> {
3119        let overlap_builder = self.rstar_solution.esurface_ct_builder.overlap_builder;
3120        let overlap = overlap_builder.counterterm_builder.overlap_structure;
3121
3122        if overlap.overlap_groups.len() < 2 {
3123            return DualOrNot::NonDual(Complex::new_re(momentum_sample.one()));
3124        }
3125
3126        let multiplicative_offset = momentum_sample
3127            .sample
3128            .dual_loop_moms
3129            .as_ref()
3130            .map(|dual_loop_moms| dual_loop_moms.first().unwrap().px.values.len())
3131            .unwrap_or(1);
3132        let zero = Complex::new_re(momentum_sample.zero());
3133        let mut params = if let Some(dual_loop_moms) = &momentum_sample.sample.dual_loop_moms {
3134            dual_loop_moms
3135                .iter()
3136                .flat_map(|mom| {
3137                    [
3138                        mom.px.values.clone(),
3139                        mom.py.values.clone(),
3140                        mom.pz.values.clone(),
3141                    ]
3142                    .into_iter()
3143                    .flatten()
3144                    .map(Complex::new_re)
3145                    .collect::<Vec<_>>()
3146                })
3147                .collect::<Vec<_>>()
3148        } else {
3149            momentum_sample
3150                .loop_moms()
3151                .iter()
3152                .flat_map(|momentum| {
3153                    [
3154                        momentum.px.clone(),
3155                        momentum.py.clone(),
3156                        momentum.pz.clone(),
3157                    ]
3158                    .into_iter()
3159                    .map(Complex::new_re)
3160                    .collect::<Vec<_>>()
3161                })
3162                .collect::<Vec<_>>()
3163        };
3164
3165        params.extend(momentum_sample.external_moms().iter().flat_map(|momentum| {
3166            [
3167                momentum.temporal.value.clone(),
3168                momentum.spatial.px.clone(),
3169                momentum.spatial.py.clone(),
3170                momentum.spatial.pz.clone(),
3171            ]
3172            .into_iter()
3173            .flat_map(|value| {
3174                std::iter::once(Complex::new_re(value))
3175                    .chain((1..multiplicative_offset).map(|_| zero.clone()))
3176                    .collect::<Vec<_>>()
3177            })
3178            .collect::<Vec<_>>()
3179        }));
3180        params.extend(model_params.iter().cloned().flat_map(|value| {
3181            std::iter::once(value)
3182                .chain((1..multiplicative_offset).map(|_| zero.clone()))
3183                .collect::<Vec<_>>()
3184        }));
3185
3186        let evaluator = overlap_builder
3187            .overlap_group
3188            .prefactor_evaluator
3189            .as_ref()
3190            .unwrap()
3191            .get(order_index)
3192            .expect("missing overlap prefactor evaluator for amplitude threshold order");
3193
3194        evaluate_evaluator(&mut evaluator.borrow_mut(), &params, evaluation_metadata)
3195            .pop()
3196            .expect("overlap prefactor evaluator should return exactly one value")
3197    }
3198}
3199
3200#[cfg(test)]
3201mod tests {
3202    use super::{
3203        AmplitudeCountertermAtom, AmplitudeCountertermData, EvaluatedAmplitudeThreshold,
3204        append_amplitude_component_evaluations, combine_completed_threshold_pieces,
3205    };
3206    use crate::{
3207        cff::{CutCFFIndex, esurface::RaisedEsurfaceId},
3208        dot,
3209        graph::{Graph, GraphGroupPosition, parse::from_dot::IntoGraph},
3210        initialisation::test_initialise,
3211        integrands::{
3212            evaluation::EvaluationMetaData,
3213            process::threshold_multiplier::{
3214                ThresholdMultiplierEvaluatorCollection, ThresholdMultiplierInput,
3215                ThresholdMultiplierInputWorkspace, ThresholdMultiplierLayout,
3216                ThresholdMultiplierPoint,
3217            },
3218        },
3219        momentum::{
3220            FourMomentum, ThreeMomentum,
3221            sample::{BareMomentumSample, ExternalFourMomenta, LoopMomenta, MomentumSample},
3222        },
3223        processes::{EvaluatorSettings, ThresholdCountertermVariantId},
3224        utils::F,
3225        uv::Integrands,
3226    };
3227    use linnet::half_edge::involution::EdgeVec;
3228    use spenso::algebra::complex::Complex;
3229    use symbolica::{atom::Atom, symbol};
3230    use typed_index_collections::ti_vec;
3231
3232    #[test]
3233    fn empty_amplitude_counterterm_atom_is_not_generated() {
3234        let atom = AmplitudeCountertermAtom {
3235            parametric: std::iter::empty().collect(),
3236        };
3237
3238        assert!(!atom.is_generated());
3239    }
3240
3241    #[test]
3242    fn non_empty_amplitude_counterterm_atom_is_generated() {
3243        let atom = AmplitudeCountertermAtom {
3244            parametric: Integrands::from_iter([(
3245                CutCFFIndex::new_all_none(),
3246                Atom::var(symbol!("x")),
3247            )]),
3248        };
3249
3250        assert!(atom.is_generated());
3251    }
3252
3253    #[test]
3254    fn zero_like_amplitude_counterterm_atom_is_zero() {
3255        let atom = AmplitudeCountertermAtom {
3256            parametric: Integrands::from_iter([(
3257                CutCFFIndex::new_all_none(),
3258                Atom::var(symbol!("x")),
3259            )]),
3260        };
3261
3262        let zeroed = atom.zero_like();
3263
3264        assert_eq!(
3265            zeroed.parametric,
3266            Integrands::from_iter([(CutCFFIndex::new_all_none(), Atom::Zero)])
3267        );
3268    }
3269
3270    #[test]
3271    fn inactive_amplitude_esurface_guard_reports_runtime_access() {
3272        let mut data = AmplitudeCountertermData::new_empty(GraphGroupPosition(0));
3273        data.active_mask = ti_vec![false];
3274
3275        let error = data
3276            .ensure_active_raised_esurface(RaisedEsurfaceId(0))
3277            .unwrap_err();
3278        assert!(error.to_string().contains("generation marked it inactive"));
3279    }
3280
3281    #[test]
3282    fn grouped_amplitude_catalogue_keeps_foreign_complements_and_raised_weights() {
3283        use super::{AmplitudeOverlapCatalogue, evaluate_generalized_multichanneling_prefactor};
3284        use crate::{
3285            cff::{
3286                VertexSet,
3287                esurface::{Esurface, EsurfaceID, RaisedEsurfaceGroup},
3288            },
3289            graph::{FeynmanGraph, LMBext, LmbIndex},
3290            momentum::{
3291                Rotation, RotationMethod,
3292                sample::{LoopIndex, SubspaceData},
3293            },
3294            processes::{ResolvedThresholdCountertermVariant, ThresholdCountertermSide},
3295            settings::RuntimeSettings,
3296            utils::{
3297                hyperdual_utils::{DualOrNot, extract_t_derivatives, simple_n_deriv_shape},
3298                load_generic_model,
3299            },
3300        };
3301        use linnet::half_edge::involution::EdgeIndex;
3302        use symbolica::domains::dual::{DualNumberStructure, HyperDual};
3303
3304        test_initialise().unwrap();
3305        // Diagnostic geometry: A encloses disjoint balls B and C. Their owners are three
3306        // graph-group members; only A's owner will evaluate its CT at either center.
3307        let graph: Graph = dot!(digraph shared_threshold_geometry {
3308            ext [style=invis]
3309            node [num=1]
3310            edge [num=1 mass=0]
3311            ext -> a [id=0]
3312            ext -> b [id=1]
3313            ext -> c [id=2]
3314            a -> b [id=3]
3315            b -> c [id=4]
3316            c -> a [id=5 lmb_id=0]
3317        })
3318        .unwrap();
3319        let model = load_generic_model("scalars");
3320        let lmbs = ti_vec![graph.loop_momentum_basis.clone()];
3321        let subspace = SubspaceData::new_from_parent_basis_edges(
3322            &[EdgeIndex::from(5)],
3323            &graph.full_filter(),
3324            LmbIndex::from(0),
3325            &graph,
3326            &lmbs,
3327        )
3328        .unwrap();
3329        let variants = [5, 3, 4]
3330            .into_iter()
3331            .enumerate()
3332            .map(|(owner, edge)| {
3333                let surface = Esurface {
3334                    energies: vec![EdgeIndex::from(edge)],
3335                    // e2 is dependent; 2*e0+e1 is the timelike vector (2,0,0,0).
3336                    external_shift: [(0, -2), (1, -1)]
3337                        .into_iter()
3338                        .map(|(edge, sign)| {
3339                            (EdgeIndex::from(edge), sign * if owner == 0 { 4 } else { 1 })
3340                        })
3341                        .collect(),
3342                    vertex_set: VertexSet::dummy(),
3343                };
3344                let metadata = ResolvedThresholdCountertermVariant {
3345                    name: format!("member_{owner}"),
3346                    group_id: Some(0),
3347                    cut_group_id: None,
3348                    associations: Vec::new(),
3349                    side: ThresholdCountertermSide::Amplitude,
3350                    threshold_esurface_ids: vec![EsurfaceID::from(0)],
3351                    raised_esurface_group: RaisedEsurfaceGroup {
3352                        esurface_ids: vec![EsurfaceID::from(0)],
3353                        max_occurence: 2,
3354                    },
3355                    requested_subspace: Some(vec![EdgeIndex::from(5)]),
3356                    requested_parent_lmb: Some(vec![EdgeIndex::from(5)]),
3357                    resolved_parent_lmb: vec![EdgeIndex::from(5)],
3358                    subspace: subspace.clone(),
3359                    subspace_loop_count: 1,
3360                    multiplier: None,
3361                };
3362                (
3363                    GraphGroupPosition::from(owner),
3364                    ThresholdCountertermVariantId(0),
3365                    metadata,
3366                    surface,
3367                )
3368            })
3369            .collect();
3370        let catalogue = AmplitudeOverlapCatalogue {
3371            graphs: ti_vec![graph.clone(), graph.clone(), graph],
3372            lmbs,
3373            variants,
3374        };
3375        let mut sample = MomentumSample {
3376            sample: BareMomentumSample {
3377                loop_moms: [ThreeMomentum::new(F(6.4), F(4.8), F(0.0))]
3378                    .into_iter()
3379                    .collect(),
3380                dual_loop_moms: None,
3381                loop_mom_cache_id: 0,
3382                loop_mom_base_cache_id: 0,
3383                external_moms: [(2.0 / 3.0, 4.0), (2.0 / 3.0, -8.0), (-4.0 / 3.0, 4.0)]
3384                    .into_iter()
3385                    .map(|(energy, px)| FourMomentum::from_args(F(energy), F(px), F(0.0), F(0.0)))
3386                    .collect(),
3387                external_mom_cache_id: 0,
3388                external_mom_base_cache_id: 0,
3389                jacobian: F(1.0),
3390                orientation: None,
3391                parameterization_branch: None,
3392            },
3393        };
3394        let mut settings = RuntimeSettings::default();
3395        settings.subtraction.overlap_settings.try_origin_all_lmbs = false;
3396        let prepared = catalogue
3397            .prepare_group(
3398                &[0, 1, 2],
3399                &sample,
3400                &model,
3401                &settings,
3402                &Rotation::new(RotationMethod::Identity),
3403            )
3404            .unwrap();
3405        assert_eq!(prepared.overlap.overlap_groups.len(), 2);
3406        assert!(
3407            prepared
3408                .overlap
3409                .overlap_groups
3410                .iter()
3411                .all(|group| group.existing_esurfaces.len() == 2 && group.complement.len() == 1)
3412        );
3413        let weight = |point: &MomentumSample<f64>, group| {
3414            evaluate_generalized_multichanneling_prefactor(
3415                point,
3416                &prepared.threshold_masses,
3417                &catalogue.lmbs,
3418                &prepared.thresholds,
3419                &prepared.threshold_subspaces,
3420                &prepared.overlap,
3421                group,
3422                4,
3423            )
3424        };
3425        let scalar = |point: &MomentumSample<f64>, group| match weight(point, group) {
3426            DualOrNot::NonDual(value) => value.re.0,
3427            DualOrNot::Dual(_) => unreachable!(),
3428        };
3429        let first = scalar(&sample, 0);
3430        assert!(
3431            (first - 0.5).abs() > 0.1,
3432            "foreign complements must not collapse to equal empty products"
3433        );
3434        assert!((first + scalar(&sample, 1) - 1.0).abs() < 1.0e-12);
3435        let complement =
3436            prepared.overlap.existing_esurfaces[prepared.overlap.overlap_groups[0].complement[0]];
3437        let values = [1usize, 2].map(|id| {
3438            prepared.thresholds[EsurfaceID::from(id)]
3439                .compute_from_momenta(
3440                    &catalogue.lmbs[LmbIndex::from(0)],
3441                    &prepared.threshold_masses[id],
3442                    sample.loop_moms(),
3443                    sample.external_moms(),
3444                )
3445                .0
3446                .powi(4)
3447        });
3448        assert!((first - values[complement.0 - 1] / (values[0] + values[1])).abs() < 1.0e-12);
3449
3450        let delta = 1.0e-4;
3451        let mut displaced = sample.clone();
3452        displaced.sample.loop_moms[LoopIndex(0)] =
3453            ThreeMomentum::new(F(0.8 * (8.0 + delta)), F(0.6 * (8.0 + delta)), F(0.0));
3454        let plus = scalar(&displaced, 0);
3455        displaced.sample.loop_moms[LoopIndex(0)] =
3456            ThreeMomentum::new(F(0.8 * (8.0 - delta)), F(0.6 * (8.0 - delta)), F(0.0));
3457        let minus = scalar(&displaced, 0);
3458        let shape = HyperDual::<F<f64>>::new(simple_n_deriv_shape(2));
3459        let radius = shape.variable(0, F(8.0));
3460        sample.sample.dual_loop_moms = Some(
3461            [ThreeMomentum::new(
3462                radius.clone() * F(0.8),
3463                radius * F(0.6),
3464                super::new_constant(&shape, &F(0.0)),
3465            )]
3466            .into_iter()
3467            .collect(),
3468        );
3469        let DualOrNot::Dual(dual) = weight(&sample, 0) else {
3470            panic!("raised weight must retain its derivatives")
3471        };
3472        let real = HyperDual::from_values(
3473            dual.get_shape()
3474                .iter()
3475                .map(|entry| entry.to_vec())
3476                .collect(),
3477            dual.values.into_iter().map(|value| value.re).collect(),
3478        );
3479        let derivatives = extract_t_derivatives(real);
3480        assert!(derivatives[1].0.abs() > 1.0e-5);
3481        assert!((derivatives[1].0 - (plus - minus) / (2.0 * delta)).abs() < 1.0e-8);
3482
3483        // A native implicit member can have edges absent from the master. Its additional
3484        // ball must remain in the shared geometry without changing explicit members' routing.
3485        let native: Graph = dot!(digraph native_implicit_member {
3486            ext [style=invis]
3487            node [num=1]
3488            edge [num=1 mass=0]
3489            ext -> a [id=0]
3490            ext -> b [id=1]
3491            ext -> c [id=2]
3492            a -> b [id=3]
3493            b -> c [id=4]
3494            c -> d [id=5]
3495            d -> a [id=6 lmb_id=0 mass=1]
3496        })
3497        .unwrap();
3498        let mut mixed = catalogue.clone();
3499        let member_lmb = mixed.graphs[GraphGroupPosition(1)]
3500            .generate_loop_momentum_bases_of(&mixed.graphs[GraphGroupPosition(1)].no_dummy())
3501            .into_iter()
3502            .find(|lmb| lmb.loop_edges.raw.as_slice() == [EdgeIndex(3)])
3503            .unwrap();
3504        mixed.graphs[GraphGroupPosition(1)].loop_momentum_basis = member_lmb;
3505        for (_, _, metadata, _) in &mut mixed.variants {
3506            metadata.group_id = None;
3507        }
3508        let mut implicit = mixed.variants[0].2.clone();
3509        implicit.name = "native_default".into();
3510        implicit.requested_subspace = None;
3511        implicit.requested_parent_lmb = None;
3512        mixed.variants.push((
3513            GraphGroupPosition(3),
3514            ThresholdCountertermVariantId(0),
3515            implicit,
3516            Esurface {
3517                energies: vec![EdgeIndex(6)],
3518                external_shift: mixed.variants[1].3.external_shift.clone(),
3519                vertex_set: VertexSet::dummy(),
3520            },
3521        ));
3522        mixed.graphs.push(native.clone());
3523        settings.subtraction.overlap_settings.try_origin_all_lmbs = true;
3524        let mixed_prepared = mixed
3525            .prepare_group(
3526                &[0, 1, 2, 3],
3527                &sample,
3528                &model,
3529                &settings,
3530                &Rotation::new(RotationMethod::Identity),
3531            )
3532            .unwrap();
3533        assert_eq!(mixed_prepared.thresholds.len(), 4);
3534        assert_eq!(mixed_prepared.overlap.overlap_groups.len(), 3);
3535        for id in 0..3 {
3536            assert_eq!(
3537                mixed_prepared.threshold_subspaces[id]
3538                    .get_lmb(&mixed_prepared.lmbs)
3539                    .edge_signatures,
3540                prepared.threshold_subspaces[id]
3541                    .get_lmb(&prepared.lmbs)
3542                    .edge_signatures,
3543                "an implicit neighbor must not change explicit master-based surface equations",
3544            );
3545        }
3546        assert_eq!(
3547            mixed_prepared.threshold_subspaces[3]
3548                .get_lmb(&mixed_prepared.lmbs)
3549                .edge_signatures,
3550            native.loop_momentum_basis.edge_signatures
3551        );
3552        let weights = (0..3)
3553            .map(|group| {
3554                evaluate_generalized_multichanneling_prefactor(
3555                    &sample,
3556                    &mixed_prepared.threshold_masses,
3557                    &mixed_prepared.lmbs,
3558                    &mixed_prepared.thresholds,
3559                    &mixed_prepared.threshold_subspaces,
3560                    &mixed_prepared.overlap,
3561                    group,
3562                    4,
3563                )
3564            })
3565            .map(|weight| match weight {
3566                DualOrNot::Dual(value) => value,
3567                _ => panic!("mixed raised weight lost derivatives"),
3568            })
3569            .collect::<Vec<_>>();
3570        let sum = weights[0].clone() + &weights[1] + &weights[2];
3571        assert!((sum.values[0].re.0 - 1.0).abs() < 1.0e-12);
3572        assert!(
3573            sum.values
3574                .iter()
3575                .skip(1)
3576                .all(|value| value.re.0.abs() < 1.0e-12)
3577        );
3578        assert!(
3579            mixed_prepared
3580                .overlap
3581                .overlap_groups
3582                .iter()
3583                .all(|group| group.existing_esurfaces.len() == 2 && group.complement.len() == 2)
3584        );
3585    }
3586
3587    #[test]
3588    fn generalized_multiplier_uses_component_contexts_after_helper_evaluation() {
3589        test_initialise().unwrap();
3590        let graph: Graph = dot!(digraph amplitude_multiplier_contexts {
3591            ext [style=invis]
3592            v1 [num=1]
3593            v2 [num=1]
3594            edge [num=1 mass=0]
3595            ext -> v1 [id=0]
3596            v1 -> v2 [id=1]
3597            v1 -> v2 [id=2]
3598            v2 -> ext [id=3]
3599        })
3600        .unwrap();
3601        let edge = graph.loop_momentum_basis.loop_edges.raw[0];
3602        let layout = ThresholdMultiplierLayout::new(
3603            Vec::new(),
3604            Vec::new(),
3605            graph.loop_momentum_basis.ext_edges.len(),
3606            (0..graph.n_edges()).collect(),
3607            Vec::new(),
3608        )
3609        .unwrap();
3610        let expression = layout
3611            .parse_expression(
3612                &format!(
3613                    "Q3(effective, {}, cind(1)) + 10 * Q3(star, {}, cind(1))",
3614                    edge.0, edge.0,
3615                ),
3616                &Default::default(),
3617            )
3618            .unwrap();
3619        let mut collection = Some(
3620            ThresholdMultiplierEvaluatorCollection::build(
3621                layout,
3622                vec![(ThresholdCountertermVariantId(0), Some(expression))],
3623                Vec::new(),
3624                &EvaluatorSettings::default(),
3625            )
3626            .unwrap()
3627            .unwrap(),
3628        );
3629
3630        let make_sample = |loop_scale: f64| MomentumSample {
3631            sample: BareMomentumSample {
3632                loop_moms: (0..graph.loop_momentum_basis.loop_edges.len())
3633                    .map(|index| ThreeMomentum::new(F(loop_scale + index as f64), F(0.25), F(-0.5)))
3634                    .collect::<LoopMomenta<_>>(),
3635                dual_loop_moms: None,
3636                loop_mom_cache_id: 0,
3637                loop_mom_base_cache_id: 0,
3638                external_moms: (0..graph.loop_momentum_basis.ext_edges.len())
3639                    .map(|position| {
3640                        FourMomentum::from_args(
3641                            F(5.0 + position as f64),
3642                            F(0.1 * position as f64),
3643                            F(0.0),
3644                            F(0.0),
3645                        )
3646                    })
3647                    .collect::<ExternalFourMomenta<_>>(),
3648                external_mom_cache_id: 0,
3649                external_mom_base_cache_id: 0,
3650                jacobian: F(1.0),
3651                orientation: None,
3652                parameterization_branch: None,
3653            },
3654        };
3655        let base = make_sample(1.0);
3656        let root = make_sample(2.0);
3657        let masses = (0..graph.n_edges()).map(|_| F(0.0)).collect::<EdgeVec<_>>();
3658        let mut expected_workspace =
3659            ThresholdMultiplierInputWorkspace::new(collection.as_ref().unwrap().layout(), F(0.0));
3660        let expected_for = |values: &[Complex<F<f64>>]| {
3661            collection
3662                .as_ref()
3663                .unwrap()
3664                .layout()
3665                .inputs()
3666                .iter()
3667                .zip(values)
3668                .filter_map(|(input, value)| match input {
3669                    ThresholdMultiplierInput::EdgeMomentum {
3670                        point,
3671                        edge: input_edge,
3672                        component: 1,
3673                    } if *input_edge == edge.0 => Some(match point {
3674                        ThresholdMultiplierPoint::Effective => value.re,
3675                        ThresholdMultiplierPoint::Star => value.re * F(10.0),
3676                    }),
3677                    _ => None,
3678                })
3679                .fold(F(0.0), |sum, value| sum + value)
3680        };
3681        let expected_local = expected_for(
3682            expected_workspace
3683                .bind(
3684                    collection.as_ref().unwrap().layout(),
3685                    &graph.loop_momentum_basis,
3686                    &masses,
3687                    &[],
3688                    &[],
3689                    &base,
3690                    &root,
3691                )
3692                .unwrap()
3693                .as_slice(),
3694        );
3695        let expected_integrated = expected_for(
3696            expected_workspace
3697                .bind(
3698                    collection.as_ref().unwrap().layout(),
3699                    &graph.loop_momentum_basis,
3700                    &masses,
3701                    &[],
3702                    &[],
3703                    &root,
3704                    &root,
3705                )
3706                .unwrap()
3707                .as_slice(),
3708        );
3709        let mut workspace =
3710            ThresholdMultiplierInputWorkspace::new(collection.as_ref().unwrap().layout(), F(0.0));
3711        let (local, integrated) = AmplitudeCountertermData::evaluate_variant_multiplier(
3712            &mut collection,
3713            Some(&mut workspace),
3714            ThresholdCountertermVariantId(0),
3715            &graph,
3716            &masses,
3717            &[],
3718            &[],
3719            &base,
3720            &root,
3721            &mut EvaluationMetaData::new_empty(),
3722        )
3723        .unwrap();
3724        assert_eq!(local, expected_local);
3725        assert_eq!(integrated, expected_integrated);
3726        assert_ne!(local, integrated);
3727
3728        let completed = combine_completed_threshold_pieces(
3729            Complex::new_re(F(2.0)),
3730            Complex::new_re(F(3.0)),
3731            &F(5.0),
3732            &F(7.0),
3733        );
3734        assert_eq!(completed, Complex::new_re(F(31.0)));
3735
3736        let mut data = AmplitudeCountertermData::new_empty(GraphGroupPosition(0));
3737        data.threshold_multipliers = collection;
3738        let mut visited = 0;
3739        data.for_each_generic_evaluator_mut(|_| {
3740            visited += 1;
3741            Ok(())
3742        })
3743        .unwrap();
3744        assert_eq!(visited, 1);
3745        assert_eq!(data.generic_evaluator_count(), 0);
3746    }
3747
3748    #[test]
3749    fn exact_zero_multiplier_skips_a_nan_completed_piece() {
3750        let completed = combine_completed_threshold_pieces(
3751            Complex::new_re(F(f64::NAN)),
3752            Complex::new_re(F(3.0)),
3753            &F(0.0),
3754            &F(7.0),
3755        );
3756
3757        assert_eq!(completed, Complex::new_re(F(21.0)));
3758    }
3759
3760    #[test]
3761    fn partial_zero_records_a_skipped_component_without_its_nan_bare_value() {
3762        let local_multiplier = F(0.0);
3763        let integrated_multiplier = F(7.0);
3764        let mut evaluation = EvaluatedAmplitudeThreshold::new(F(0.0), true);
3765        evaluation.add_completed_pieces(
3766            Complex::new_re(F(f64::NAN)),
3767            Complex::new_re(F(3.0)),
3768            &local_multiplier,
3769            &integrated_multiplier,
3770        );
3771        let evaluation = evaluation.finish(&local_multiplier, &integrated_multiplier);
3772        let mut components = Some(Vec::new());
3773        append_amplitude_component_evaluations(
3774            &mut components,
3775            &evaluation,
3776            ThresholdCountertermVariantId(0),
3777            RaisedEsurfaceId(2),
3778            3,
3779            &local_multiplier,
3780            &integrated_multiplier,
3781        );
3782        let components = components.unwrap();
3783
3784        assert_eq!(evaluation.weighted, Complex::new_re(F(21.0)));
3785        assert_eq!(components.len(), 2);
3786        assert!(components[0].evaluation_skipped);
3787        assert!(components[0].bare.is_none());
3788        assert_eq!(components[0].weighted, Complex::new_re(F(0.0)));
3789        assert!(!components[1].evaluation_skipped);
3790        assert_eq!(components[1].bare, Some(Complex::new_re(F(3.0))));
3791        assert_eq!(components[1].weighted, Complex::new_re(F(21.0)));
3792    }
3793}