Skip to main content

gammalooprs/uv/approx/
mod.rs

1use crate::{
2    cff::{
3        CffEnergyDegreeBoundReport,
4        esurface::Esurface,
5        expression::{
6            GammaLoopOrientationExpression, OrientationExpression, OrientationID,
7            OrientationSelector, energy_map_replacements_gs,
8        },
9        hsurface::Hsurface,
10        surface::{GammaLoopLinearEnergyExpr, LinearEnergyExpr},
11    },
12    debug_tags,
13    graph::{Graph, LoopMomentumBasis, cuts::CutSet},
14    momentum::Sign,
15    settings::global::OrientationPattern,
16    utils::GS,
17    uv::{
18        ApproximationType, Spinney, UVgenerationSettings,
19        approx::{
20            direct_3d::{Direct3dApproximation, Direct3dCts},
21            final_integrand::{FinalIntegrandBuilder, FinalIntegrands},
22            integrated::{Integrated, IntegratedCts},
23            local_3d::{Local3DCts, Localizer},
24            local_4d::{Full4dCts, Local4dCts},
25            projected_4d::{Local4dProjectionContext, Projected4dApproximation},
26        },
27        marker::UvMarker,
28        settings::FinalIntegrandDimension,
29    },
30};
31use color_eyre::Result;
32use eyre::eyre;
33use gammaloop_tracing_filter::{LogMessage, debug_instrument};
34
35use std::sync::Mutex;
36
37use symbolica::{
38    atom::{Atom, AtomCore},
39    function,
40};
41
42use linnet::half_edge::involution::EdgeIndex;
43#[cfg(test)]
44use linnet::half_edge::involution::{EdgeVec, Orientation};
45use linnet::half_edge::subgraph::{InternalSubGraph, SuBitGraph, SubSetLike, SubSetOps};
46#[cfg(test)]
47use three_dimensional_reps::CffGenerationContext;
48use three_dimensional_reps::{Generate3DExpressionOptions, GeneratedThreeDExpression};
49use typed_index_collections::TiVec;
50
51use vakint::Vakint;
52
53#[cfg(test)]
54use self::direct_3d::DirectResidueBranches;
55
56pub mod direct_3d;
57pub mod final_integrand;
58pub mod integrated;
59pub mod local_3d;
60pub mod local_4d;
61pub(crate) mod projected_4d;
62
63pub trait Rooted {
64    fn root() -> Self;
65}
66
67pub trait ForestNodeLike: LogMessage {
68    fn subgraph(&self) -> &SuBitGraph;
69    fn lmb(&self) -> &LoopMomentumBasis;
70    fn lmb_id(&self) -> EdgeIndex {
71        *self.lmb().loop_edges.first().unwrap()
72    }
73    // fn lmb_given(&self, subgraph: &SuBitGraph) -> &LoopMomentumBasis;
74    fn dod(&self) -> i32;
75    fn renormalization_scheme(&self) -> ApproximationType;
76    fn topo_order(&self) -> usize;
77    fn reduced_subgraph(&self, given: &Self) -> SuBitGraph;
78}
79
80pub struct UVCtx<'a> {
81    pub graph: &'a Graph,
82    pub settings: &'a UVgenerationSettings,
83}
84
85impl<'a> UVCtx<'a> {
86    pub fn new(graph: &'a Graph, settings: &'a UVgenerationSettings) -> Self {
87        Self { graph, settings }
88    }
89}
90
91#[derive(Clone)]
92pub struct SimpleApprox {
93    t_args: Vec<Atom>,
94    pub sign: Sign,
95    graph: InternalSubGraph,
96}
97
98impl SimpleApprox {
99    pub(crate) fn expr(&self, bigger_graph: &SuBitGraph) -> Atom {
100        let reduced = UvMarker::subgraph(bigger_graph, &self.graph.filter);
101        let mut mul = Atom::num(1);
102        for i in &self.t_args {
103            mul *= i;
104        }
105        reduced * mul
106    }
107
108    pub(crate) fn t_op(&self, bigger_graph: &SuBitGraph) -> Atom {
109        function!(GS.uv_approx, self.expr(bigger_graph))
110    }
111
112    pub(crate) fn root(subgraph: InternalSubGraph) -> Self {
113        if !subgraph.is_empty() {
114            panic!(
115                "Root approximation must be empty {} {:?}",
116                subgraph.string_label(),
117                subgraph
118            )
119        }
120        SimpleApprox {
121            t_args: vec![],
122            sign: Sign::Positive,
123            graph: subgraph,
124        }
125    }
126
127    pub(crate) fn dependent(&self, bigger_graph: InternalSubGraph) -> Self {
128        Self {
129            t_args: vec![self.t_op(&bigger_graph.filter)],
130            sign: -self.sign,
131            graph: bigger_graph,
132        }
133    }
134}
135
136#[derive(Clone)]
137pub struct Approximation {
138    pub spinney: Spinney,
139    local_3d: Option<Local3DCts>,
140    local: Option<Local4dCts>,
141    integrated: Option<IntegratedCts>,
142    final_integrand: Option<FinalIntegrands>,
143    pub topo_order: usize,
144    pub simple_approx: Option<SimpleApprox>,
145}
146
147impl Approximation {
148    pub(crate) fn integrated(&self, graph: &Graph) -> Result<&IntegratedCts> {
149        self.integrated
150            .as_ref()
151            .ok_or_else(|| eyre!("No integrated CT for {}", self.simple_display(graph)))
152    }
153
154    pub(crate) fn local(&self, graph: &Graph) -> Result<&Local4dCts> {
155        self.local
156            .as_ref()
157            .ok_or_else(|| eyre!("No local CT for {}", self.simple_display(graph)))
158    }
159
160    pub(crate) fn recursion_input_4d(&self, graph: &Graph) -> Result<Full4dCts> {
161        Full4dCts::recursion_input(
162            self.local(graph)?,
163            self.integrated(graph)?,
164            self.renormalization_scheme(),
165            self.spinney.subgraph.is_empty(),
166            self.lmb(),
167        )
168    }
169
170    pub(crate) fn local_3d(&self, graph: &Graph) -> Result<&Local3DCts> {
171        self.local_3d
172            .as_ref()
173            .ok_or_else(|| eyre!("No local 3D CT for {}", self.simple_display(graph)))
174    }
175
176    pub(crate) fn final_integrand(&self, graph: &Graph) -> Result<&FinalIntegrands> {
177        self.final_integrand
178            .as_ref()
179            .ok_or_else(|| eyre!("No final integrand for {}", self.simple_display(graph)))
180    }
181
182    pub fn simple_display(&self, graph: &Graph) -> String {
183        format!(
184            "{} of {}",
185            self.simple_approx
186                .as_ref()
187                .unwrap()
188                .expr(&graph.full_filter()),
189            graph.name
190        )
191    }
192}
193
194impl LogMessage for Approximation {
195    fn log_display(&self) -> String {
196        format!(
197            "subgraph={}, topo_order={}, dod={}",
198            self.spinney.filter().string_label(),
199            self.topo_order,
200            self.spinney.dod
201        )
202    }
203}
204
205impl ForestNodeLike for Approximation {
206    fn dod(&self) -> i32 {
207        self.spinney.dod
208    }
209
210    fn renormalization_scheme(&self) -> ApproximationType {
211        self.spinney.renormalization_scheme
212    }
213
214    fn lmb(&self) -> &LoopMomentumBasis {
215        &self.spinney.lmb
216    }
217
218    fn reduced_subgraph(&self, given: &Self) -> SuBitGraph {
219        self.spinney
220            .subgraph
221            .subtract(&given.spinney.subgraph)
222            .filter
223    }
224
225    fn subgraph(&self) -> &SuBitGraph {
226        self.spinney.filter()
227    }
228
229    fn topo_order(&self) -> usize {
230        self.topo_order
231    }
232}
233
234#[derive(Clone)]
235pub struct CutStructure {
236    pub cuts: Vec<CutSet>,
237}
238
239#[derive(Clone, Copy, Debug)]
240enum OrientationProjectionSource<'a> {
241    Exact {
242        orientations: &'a TiVec<OrientationID, OrientationExpression>,
243        root_expression: Option<&'a GeneratedThreeDExpression<Esurface, Hsurface>>,
244        options: &'a Generate3DExpressionOptions,
245    },
246    FourDOnly,
247}
248
249#[derive(Clone, Copy, Debug)]
250pub(crate) struct OrientationProjection<'a> {
251    source: OrientationProjectionSource<'a>,
252    energy_degree_bound_reports: Option<&'a Mutex<Vec<CffEnergyDegreeBoundReport>>>,
253    pub(crate) orientation_pattern: &'a OrientationPattern,
254    pub(crate) explicit_orientation_sum_only: bool,
255    pub(crate) vacuum_subtraction: bool,
256}
257
258impl<'a> OrientationProjection<'a> {
259    /// Four-dimensional renormalization has no energy projection. It needs
260    /// neither a production CFF nor coarse orientation metadata. Computed
261    /// exports and production 3D UV use the stored exact source below.
262    pub(crate) fn four_d(orientation_pattern: &'a OrientationPattern) -> Self {
263        Self {
264            source: OrientationProjectionSource::FourDOnly,
265            energy_degree_bound_reports: None,
266            orientation_pattern,
267            explicit_orientation_sum_only: false,
268            vacuum_subtraction: false,
269        }
270    }
271
272    #[cfg(test)]
273    pub(crate) fn exact(
274        orientations: &'a TiVec<OrientationID, OrientationExpression>,
275        options: &'a Generate3DExpressionOptions,
276        orientation_pattern: &'a OrientationPattern,
277        explicit_orientation_sum_only: bool,
278    ) -> Self {
279        Self {
280            source: OrientationProjectionSource::Exact {
281                orientations,
282                root_expression: None,
283                options,
284            },
285            energy_degree_bound_reports: None,
286            orientation_pattern,
287            explicit_orientation_sum_only,
288            vacuum_subtraction: false,
289        }
290    }
291
292    pub(crate) fn exact_expression(
293        expression: &'a GeneratedThreeDExpression<Esurface, Hsurface>,
294        options: &'a Generate3DExpressionOptions,
295        orientation_pattern: &'a OrientationPattern,
296        explicit_orientation_sum_only: bool,
297    ) -> Self {
298        Self {
299            source: OrientationProjectionSource::Exact {
300                orientations: &expression.expression.orientations,
301                root_expression: Some(expression),
302                options,
303            },
304            energy_degree_bound_reports: None,
305            orientation_pattern,
306            explicit_orientation_sum_only,
307            vacuum_subtraction: false,
308        }
309    }
310
311    pub(crate) fn with_vacuum_subtraction(mut self, enabled: bool) -> Self {
312        self.vacuum_subtraction = enabled;
313        self
314    }
315
316    pub(crate) fn with_energy_degree_bound_reports(
317        mut self,
318        reports: &'a Mutex<Vec<CffEnergyDegreeBoundReport>>,
319    ) -> Self {
320        self.energy_degree_bound_reports = Some(reports);
321        self
322    }
323
324    pub(crate) fn record_energy_degree_bound_report(self, report: &CffEnergyDegreeBoundReport) {
325        let Some(reports) = self.energy_degree_bound_reports else {
326            return;
327        };
328        let mut report = report.clone();
329        report.physical_parent_bounds.sort_unstable();
330        report.assigned_cff_source_bounds.sort_unstable();
331        let mut reports = reports
332            .lock()
333            .unwrap_or_else(|poisoned| poisoned.into_inner());
334        if !reports.contains(&report) {
335            reports.push(report);
336        }
337    }
338
339    pub(crate) fn cff_options(self) -> Result<&'a Generate3DExpressionOptions> {
340        match self.source {
341            OrientationProjectionSource::Exact { options, .. } => Ok(options),
342            OrientationProjectionSource::FourDOnly => Err(eyre!(
343                "four-dimensional-only renormalization has no 3D projection options"
344            )),
345        }
346    }
347
348    pub(crate) fn orientation_ids(self) -> Result<Vec<OrientationID>> {
349        Ok(self
350            .exact_orientations()?
351            .iter_enumerated()
352            .filter_map(|(id, orientation)| {
353                self.orientation_pattern
354                    .filter_orientation(&orientation.data.orientation)
355                    .then_some(id)
356            })
357            .collect())
358    }
359
360    pub(crate) fn exact_orientations(
361        self,
362    ) -> Result<&'a TiVec<OrientationID, OrientationExpression>> {
363        match self.source {
364            OrientationProjectionSource::Exact { orientations, .. } => Ok(orientations),
365            OrientationProjectionSource::FourDOnly => Err(eyre!(
366                "four-dimensional-only renormalization has no 3D residue maps"
367            )),
368        }
369    }
370
371    pub(crate) fn root_expression(
372        self,
373    ) -> Option<&'a GeneratedThreeDExpression<Esurface, Hsurface>> {
374        match self.source {
375            OrientationProjectionSource::Exact {
376                root_expression, ..
377            } => root_expression,
378            OrientationProjectionSource::FourDOnly => None,
379        }
380    }
381
382    /// Apply one branch-owned exact energy map only to the newly owned
383    /// numerator fragment. The source map, when present, is authoritative;
384    /// otherwise the production selector also owns the numerator map.
385    pub(crate) fn map_numerator(
386        self,
387        graph: &Graph,
388        selector_id: OrientationID,
389        source_edge_energy_map: Option<&[LinearEnergyExpr]>,
390        numerator: &Atom,
391    ) -> Result<Atom> {
392        let orientation = self.exact_orientations()?.get(selector_id).ok_or_else(|| {
393            eyre!(
394                "missing production energy map for orientation {}",
395                selector_id.0
396            )
397        })?;
398        let replacements = source_edge_energy_map.map_or_else(
399            || orientation.energy_replacements_gs(graph),
400            |edge_energy_map| {
401                energy_map_replacements_gs(
402                    edge_energy_map.iter().map(|energy| energy.to_atom_gs(&[])),
403                    graph,
404                )
405            },
406        );
407        Ok(numerator.replace_multiple(replacements))
408    }
409}
410
411impl CutStructure {
412    pub(crate) fn empty(graph: &Graph) -> Self {
413        Self {
414            cuts: vec![CutSet::empty(graph.n_hedges())],
415        }
416    }
417}
418
419impl Approximation {
420    pub(crate) fn root(
421        &mut self,
422        graph: &mut Graph,
423        localizer: Localizer<'_>,
424        settings: &UVgenerationSettings,
425    ) -> Result<()> {
426        self.simple_approx = Some(SimpleApprox::root(self.spinney.subgraph.clone()));
427        self.local = Some(Local4dCts::root());
428        let integrated = IntegratedCts::root();
429        if let FinalIntegrandDimension::ThreeD = settings.final_integrand {
430            // The root is the original factorized 3D integrand, not a local UV
431            // approximation. Expanded-4D projection applies only to proper UV
432            // nodes and therefore must not change this identity element.
433            let direct = Direct3dCts::root(graph, localizer)?;
434            self.final_integrand = Some(
435                FinalIntegrandBuilder::new(localizer, settings).build_direct(
436                    graph,
437                    self,
438                    &direct,
439                    &integrated,
440                )?,
441            );
442            self.local_3d = Some(Local3DCts::Direct(direct));
443        }
444        self.integrated = Some(integrated);
445
446        Ok(())
447    }
448
449    pub(crate) fn new(spinney: Spinney) -> Approximation {
450        Approximation {
451            spinney,
452            topo_order: 0,
453            final_integrand: None,
454            simple_approx: None,
455            local: None,
456            local_3d: None,
457            integrated: None,
458        }
459    }
460
461    #[debug_instrument(
462        graph = %graph.log_display(),
463        current = %self.log_display(),
464        given = %dependent.log_display(),
465        reduced = ?self.reduced_subgraph(dependent),
466    )]
467    pub(crate) fn compute_4d(
468        &mut self,
469        graph: &Graph,
470        vakint: (&Vakint, &vakint::VakintSettings),
471        dependent: &Self,
472        settings: &UVgenerationSettings,
473    ) -> Result<()> {
474        let ctx = UVCtx { graph, settings };
475        debug_tags!(#generation,#uv,#fourd;
476            simple = %self.simple_display(graph),
477            "Computing 4D",
478        );
479
480        let old_full = dependent.recursion_input_4d(graph)?;
481        let local = local_4d::uv_limit(&old_full, &ctx, self, dependent, self, dependent)?;
482        let integrated = if settings.generate_integrated {
483            Integrated::new(vakint.0, vakint.1)
484                .run(&local, &ctx, self, dependent, self, dependent)?
485        } else {
486            IntegratedCts::root()
487        };
488
489        self.local = Some(local);
490        self.integrated = Some(integrated);
491
492        Ok(())
493    }
494
495    /// Computes the 3d approximation of the UV
496    #[allow(clippy::too_many_arguments)]
497    #[debug_instrument(
498        graph = %graph.log_display(),
499        current = %self.log_display(),
500        given = %dependent.log_display(),
501    )]
502    pub(crate) fn compute_3d(
503        &mut self,
504        dependent: &Self,
505        graph: &mut Graph,
506        localizer: Localizer<'_>,
507        settings: &UVgenerationSettings,
508        projection_context: &mut Local4dProjectionContext,
509    ) -> Result<()> {
510        let (local_3d, final_integrand) =
511            if settings.local_uv_cts_from_expanded_4d_integrands {
512                let local_4d = self.local(graph)?;
513                let projected = Projected4dApproximation::new(localizer, graph, settings)
514                    .project_local_4d(local_4d, projection_context)?;
515                let final_integrand = FinalIntegrandBuilder::new(localizer, settings)
516                    .build_projected(graph, self, &projected, self.integrated(graph)?)?;
517                (Local3DCts::Projected4d(projected), final_integrand)
518            } else {
519                let parent_local = dependent.local_3d(graph)?;
520                let parent_integrated = dependent.integrated(graph)?;
521                let direct = Direct3dApproximation::new(localizer, graph, settings).run(
522                    parent_local.direct()?,
523                    parent_integrated,
524                    self,
525                    dependent,
526                    self,
527                    dependent,
528                )?;
529                let final_integrand = FinalIntegrandBuilder::new(localizer, settings)
530                    .build_direct(graph, self, &direct, self.integrated(graph)?)?;
531                (Local3DCts::Direct(direct), final_integrand)
532            };
533
534        self.final_integrand = Some(final_integrand);
535        self.local_3d = Some(local_3d);
536        Ok(())
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use std::collections::{BTreeMap, BTreeSet};
543
544    use super::*;
545    use crate::{
546        cff::CutCFFIndex,
547        dot,
548        graph::{FeynmanGraph, cuts::LuCutSelection, parse::IntoGraph},
549        initialisation::test_initialise,
550        integrands::{
551            evaluation::EvaluationMetaData,
552            process::{
553                GenericEvaluatorFloat,
554                evaluators::{EvaluatorStack, SingleOrAllOrientations},
555            },
556        },
557        momentum::{
558            ThreeMomentum,
559            sample::{BareMomentumSample, ExternalFourMomenta, LoopMomenta, MomentumSample},
560        },
561        numerator::symbolica_ext::NumeratorAtomExt,
562        processes::{EvaluatorSettings, cross_section::build_derivative_structure},
563        settings::RuntimeSettings,
564        utils::{
565            ArbPrec, F, FUN_LIB, FloatLike, TENSORLIB, W_, cut_energy,
566            hyperdual_utils::{DualOrNot, extract_t_derivatives_complex, simple_n_deriv_shape},
567        },
568        uv::UltravioletGraph,
569    };
570    use idenso::shorthands::{metric::MetricSimplifier, schoonschip::Schoonschip};
571    use linnet::half_edge::subgraph::subset::SubSet;
572    use spenso::{
573        algebra::{algebraic_traits::IsZero, complex::Complex},
574        network::{ExecutionResult, Sequential, SmallestDegree},
575    };
576    use symbolica::domains::dual::HyperDual;
577    use symbolica::{
578        domains::{
579            float::{Complex as SymComplex, Real},
580            integer::IntegerRing,
581            rational::{Fraction, Rational},
582        },
583        evaluate::ExpressionEvaluator,
584        parse_lit,
585    };
586
587    #[test]
588    fn expanded_4d_setting_does_not_change_the_empty_forest_root() -> Result<()> {
589        test_initialise()?;
590        let mut graph: Graph = dot!(digraph root_identity {
591            edge [num=1 mass=1]
592            node [num=1]
593
594            a -> b [id=0 lmb_id=0]
595            a -> b [id=1]
596        })?;
597        let options = graph.denominator_only_cff_3d_expression_options();
598        let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
599        let production = graph.generate_3d_expression_for_integrand(
600            &[],
601            &canonization,
602            &options,
603            Some(&Atom::one()),
604        )?;
605        let mut direct_graph = graph.clone();
606        let mut projected_graph = graph;
607        let cutset = CutSet::empty(direct_graph.n_hedges());
608        let orientation_pattern = OrientationPattern::default();
609        let localizer = Localizer::new(
610            &cutset,
611            OrientationProjection::exact_expression(
612                &production,
613                &options,
614                &orientation_pattern,
615                false,
616            ),
617        );
618        let mut direct = Approximation::new(Spinney::empty(&direct_graph));
619        let mut projected = Approximation::new(Spinney::empty(&projected_graph));
620        let direct_settings = UVgenerationSettings::default();
621        let projected_settings = UVgenerationSettings {
622            local_uv_cts_from_expanded_4d_integrands: true,
623            ..direct_settings.clone()
624        };
625
626        direct.root(&mut direct_graph, localizer, &direct_settings)?;
627        projected.root(&mut projected_graph, localizer, &projected_settings)?;
628
629        assert_eq!(
630            projected
631                .final_integrand(&projected_graph)?
632                .iter()
633                .collect::<BTreeMap<_, _>>(),
634            direct
635                .final_integrand(&direct_graph)?
636                .iter()
637                .collect::<BTreeMap<_, _>>(),
638            "the empty forest must preserve every finalized residue value and selector"
639        );
640        Ok(())
641    }
642
643    #[test]
644    fn selected_raised_lu_projection_preserves_quadratic_contact_family() -> Result<()> {
645        test_initialise()?;
646        let mut graph: Graph = dot!(digraph raised_lu_contact_family {
647            num = 1
648            edge [particle="scalar_1" num=1]
649            node [num=1]
650
651            ext0 [style=invis is_cut=0]
652            v2 -> ext0 [id=0]
653            ext0 -> v3
654            v0 -> v1 [id=1 lmb_id=0]
655            v0 -> v1 [id=2]
656            v3 -> v0 [id=3 lmb_id=1]
657            v1 -> v2 [id=4]
658            v2 -> v3 [id=5]
659        }, "scalars")?;
660        let options = graph.denominator_only_cff_3d_expression_options();
661        let production_numerator =
662            GS.emr_mom(EdgeIndex(1), GS.cind(0)) * GS.emr_mom(EdgeIndex(3), GS.cind(0));
663        let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
664        let production = graph.generate_3d_expression_for_integrand(
665            &[],
666            &canonization,
667            &options,
668            Some(&production_numerator),
669        )?;
670        let lu_cut = graph
671            .determine_raised_esurfaces_from_expression(&production.expression)
672            .raised_groups
673            .into_iter()
674            .find(|group| {
675                group.max_occurence == 2
676                    && group.esurface_ids.iter().any(|surface_id| {
677                        !production.expression.surfaces.esurface_cache[*surface_id]
678                            .external_shift
679                            .is_empty()
680                    })
681            })
682            .expect("the repeated outer channel supplies a raised LU surface");
683        let mut cutset = CutSet::empty(graph.n_hedges());
684        cutset.residue_selector.lu = Some(LuCutSelection {
685            cut_edge_alternatives: lu_cut
686                .esurface_ids
687                .iter()
688                .map(|surface_id| {
689                    production.expression.surfaces.esurface_cache[*surface_id]
690                        .energies
691                        .clone()
692                })
693                .collect(),
694            raised_group: lu_cut,
695        });
696        let contracted = graph
697            .get_edge_subgraph(EdgeIndex(1))
698            .union(&graph.get_edge_subgraph(EdgeIndex(2)));
699        // These distinct occurrences carry the same physical four-momentum.
700        // Keep the multilinear form as an ordinary-CFF reference, and exercise
701        // a genuinely quadratic occurrence in the generalized contact source.
702        assert_eq!(
703            graph.loop_momentum_basis.edge_signatures[EdgeIndex(3)],
704            graph.loop_momentum_basis.edge_signatures[EdgeIndex(4)],
705        );
706        let multilinear_numerator = GS.emr_mom(EdgeIndex(3), GS.cind(0))
707            * GS.emr_mom(EdgeIndex(4), GS.cind(0))
708            + Atom::num(Rational::from((7, 11)));
709        let analysis_numerator =
710            GS.emr_mom(EdgeIndex(3), GS.cind(0)).pow(2) + Atom::num(Rational::from((7, 11)));
711        let contract_subgraph = contracted
712            .union(&graph.tree_edges)
713            .subtract(&graph.initial_state_cut);
714        let mut raw_graph = graph.clone();
715        let raw = raw_graph.cff(
716            &contract_subgraph,
717            &cutset,
718            &OrientationPattern::default(),
719            &options,
720            Some(&analysis_numerator),
721        )?;
722        let orientation_pattern = OrientationPattern::default();
723        let localizer = Localizer::new(
724            &cutset,
725            OrientationProjection::exact_expression(
726                &production,
727                &options,
728                &orientation_pattern,
729                true,
730            ),
731        );
732        let projected = localizer.projected_cff(
733            &mut graph,
734            &contracted,
735            [&analysis_numerator],
736            CffGenerationContext::Standalone,
737        )?;
738        let raw_expressions = raw
739            .terms
740            .iter()
741            .map(|(index, term)| {
742                (
743                    *index,
744                    term.orientations
745                        .iter()
746                        .map(|orientation| {
747                            &orientation.expression
748                                * analysis_numerator.replace_multiple(
749                                    orientation.orientation.energy_replacements_gs(&raw_graph),
750                                )
751                        })
752                        .fold(Atom::Zero, |sum, term| sum + term)
753                        * Atom::num(raw.production_prefactor_factor()),
754                )
755            })
756            .collect::<BTreeMap<_, _>>();
757        let mut denominator_only_graph = graph.clone();
758        let denominator_only = denominator_only_graph.cff(
759            &contract_subgraph,
760            &cutset,
761            &OrientationPattern::default(),
762            &options,
763            Some(&Atom::one()),
764        )?;
765        let denominator_only_expressions = denominator_only
766            .terms
767            .iter()
768            .map(|(index, term)| {
769                (
770                    *index,
771                    term.orientations
772                        .iter()
773                        .map(|orientation| {
774                            &orientation.expression
775                                * multilinear_numerator.replace_multiple(
776                                    orientation
777                                        .orientation
778                                        .energy_replacements_gs(&denominator_only_graph),
779                                )
780                        })
781                        .fold(Atom::Zero, |sum, term| sum + term)
782                        * Atom::num(denominator_only.production_prefactor_factor()),
783                )
784            })
785            .collect::<BTreeMap<_, _>>();
786        let mut projected_expressions = BTreeMap::<CutCFFIndex, Atom>::new();
787        for (orientation_id, source_edge_energy_map, integrands) in projected.iter_orientations() {
788            let mapped_numerator = localizer.map_numerator(
789                &graph,
790                orientation_id,
791                source_edge_energy_map,
792                &analysis_numerator,
793            )?;
794            for (index, expression) in integrands.iter() {
795                *projected_expressions.entry(*index).or_default() += expression * &mapped_numerator;
796            }
797        }
798        let fixed_point = |mut expression: Atom| -> Result<Atom> {
799            expression = expression
800                .replace(GS.dim)
801                .with(4)
802                .replace(GS.dim_epsilon)
803                .with(0)
804                .replace(GS.numerator_sampling_scale)
805                .with(Atom::num(Rational::from((13, 10))))
806                .replace(function!(GS.tree_denom_wrapper, W_.x_))
807                .with(W_.x_)
808                .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
809                .with(W_.d_);
810            for edge in 0..graph.underlying.n_edges() {
811                let edge = EdgeIndex(edge);
812                expression = expression
813                    .replace(graph.underlying[edge].particle.mass_atom())
814                    .with(Atom::one())
815                    .replace(GS.ose(edge))
816                    .with(Atom::num(Rational::from((5, 4))))
817                    .replace(cut_energy(edge))
818                    .with(Atom::num(Rational::from((5, 4))))
819                    .replace(GS.emr_mom(edge, GS.cind(0)))
820                    .with(Atom::num(Rational::from((17, 7))));
821            }
822            Ok(expression)
823        };
824        let evaluate_arb = |expression: Atom| -> Result<Complex<F<ArbPrec>>> {
825            let parameters = [Atom::var(GS.pi)];
826            let rational: ExpressionEvaluator<SymComplex<Fraction<IntegerRing>>> = expression
827                .evaluator(&parameters)
828                .build()
829                .map_err(|error| eyre!("failed to build raised-contact evaluator: {error}"))?;
830            let mut arb: ExpressionEvaluator<Complex<F<ArbPrec>>> =
831                rational.map_coeff(&|coefficient| {
832                    Complex::new(F::from(&coefficient.re), F::from(&coefficient.im))
833                });
834            let zero = F(ArbPrec::default());
835            Ok(arb.evaluate_single(&[Complex::new(zero.clone().pi(), zero)]))
836        };
837        let tolerance = F(ArbPrec::default().epsilon()).sqrt().sqrt().sqrt();
838        for order in [1, 2] {
839            let index = raw_expressions
840                .keys()
841                .find(|index| index.lu_cut_order == Some(order))
842                .copied()
843                .expect("both raised orders are present");
844            let raw_value = evaluate_arb(fixed_point(raw_expressions[&index].clone())?)?;
845            let denominator_only_value =
846                evaluate_arb(fixed_point(denominator_only_expressions[&index].clone())?)?;
847            let projected_value =
848                evaluate_arb(fixed_point(projected_expressions[&index].clone())?)?;
849            let scale = raw_value
850                .clone()
851                .norm()
852                .re
853                .max(denominator_only_value.clone().norm().re)
854                .max(projected_value.clone().norm().re);
855            assert!(
856                (raw_value.clone() - &denominator_only_value).norm().re <= &tolerance * &scale,
857                "the generalized and denominator-only selected LU{order} residues differ: generalized={raw_value}, denominator-only={denominator_only_value}"
858            );
859            assert!(
860                (raw_value - projected_value).norm().re <= &tolerance * scale,
861                "production hosting changed the complete selected LU{order} residue"
862            );
863        }
864        Ok(())
865    }
866
867    #[test]
868    fn gl24_direct_3d_modes_preserve_orientation_selector_contracts() -> Result<()> {
869        test_initialise()?;
870        let mut graph: Graph = dot!(digraph gl24_selector_contract {
871            edge [num=1 mass=1]
872            node [num=1]
873
874            v0 -> v1 [id=0 is_cut=0]
875            v0 -> v3 [id=1 lmb_id=0 num="(Q(1,spenso::cind(0))^2-Q(1,spenso::cind(1))^2-Q(1,spenso::cind(2))^2-Q(1,spenso::cind(3))^2)^2"]
876            v0 -> v5 [id=2]
877            v1 -> v2 [id=3 lmb_id=1]
878            v1 -> v3 [id=4]
879            v2 -> v4 [id=5 lmb_id=2]
880            v2 -> v4 [id=6]
881            v3 -> v5 [id=7]
882            v4 -> v5 [id=8 mass=2]
883        })?;
884        let options = graph.denominator_only_cff_3d_expression_options();
885        let numerator = graph.production_numerator_atom_for_full_3d_expression();
886        let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
887        let production = graph.generate_3d_expression_for_integrand(
888            &[],
889            &canonization,
890            &options,
891            Some(&numerator),
892        )?;
893        let uv_filter = graph
894            .get_edge_subgraph(EdgeIndex(5))
895            .union(&graph.get_edge_subgraph(EdgeIndex(6)));
896        let uv_subgraph = InternalSubGraph::cleaned_filter_optimist(uv_filter, graph.as_ref());
897        let child_spinney = Spinney::new(uv_subgraph, &graph, &graph.loop_momentum_basis)?
898            .expect("the GL24 e5/e6 bubble has a compatible nonempty UV sub-LMB");
899        assert!(!child_spinney.subgraph.is_empty());
900
901        let target_support = [EdgeIndex(3), EdgeIndex(4)]
902            .into_iter()
903            .collect::<BTreeSet<_>>();
904        let lu_surface = graph
905            .determine_raised_esurfaces_from_expression(&production.expression)
906            .raised_groups
907            .into_iter()
908            .find(|group| {
909                group.esurface_ids.iter().any(|surface_id| {
910                    production.expression.surfaces.esurface_cache[*surface_id]
911                        .energies
912                        .iter()
913                        .copied()
914                        .collect::<BTreeSet<_>>()
915                        == target_support
916                })
917            })
918            .ok_or_else(|| eyre!("GL24 q3/q4 LU surface was not generated"))?;
919        let mut cutset = CutSet::empty(graph.n_hedges());
920        cutset.residue_selector.lu = Some(LuCutSelection {
921            raised_group: lu_surface,
922            cut_edge_alternatives: vec![vec![EdgeIndex(3), EdgeIndex(4)]],
923        });
924        let expected_keys = cutset
925            .residue_selector
926            .generate_allowed_keys()
927            .into_iter()
928            .collect::<BTreeSet<_>>();
929        assert_eq!(
930            expected_keys
931                .iter()
932                .filter_map(|index| index.lu_cut_order)
933                .collect::<BTreeSet<_>>(),
934            BTreeSet::from([1]),
935        );
936
937        let orientation_pattern = OrientationPattern::default();
938        let settings = UVgenerationSettings {
939            generate_integrated: false,
940            local_uv_cts_from_expanded_4d_integrands: false,
941            add_marker: false,
942            ..Default::default()
943        };
944        let check = |explicit_orientation_sum_only| -> Result<Atom> {
945            let mut route_graph = graph.clone();
946            let localizer = Localizer::new(
947                &cutset,
948                OrientationProjection::exact_expression(
949                    &production,
950                    &options,
951                    &orientation_pattern,
952                    explicit_orientation_sum_only,
953                ),
954            );
955            let mut root = Approximation::new(Spinney::empty(&route_graph));
956            root.root(&mut route_graph, localizer, &settings)?;
957            let mut child = Approximation::new(child_spinney.clone());
958            child.simple_approx = Some(
959                root.simple_approx
960                    .as_ref()
961                    .expect("the root approximation is initialized")
962                    .dependent(child.spinney.subgraph.clone()),
963            );
964            let vakint_settings = vakint::VakintSettings::default();
965            child.compute_4d(
966                &route_graph,
967                (crate::utils::vakint()?, &vakint_settings),
968                &root,
969                &settings,
970            )?;
971            child.compute_3d(
972                &root,
973                &mut route_graph,
974                localizer,
975                &settings,
976                &mut Local4dProjectionContext::default(),
977            )?;
978            // Both direct modes Taylor-expand the same complete CFF. Check
979            // their selector contract at final assembly, after the opaque
980            // residue-map keys have been materialized.
981            let final_integrand = child
982                .final_integrand(&route_graph)?
983                .iter()
984                .find(|(index, _)| expected_keys.contains(index))
985                .ok_or_else(|| eyre!("the selected GL24 residue has no final integrand"))?
986                .1;
987            Ok(final_integrand)
988        };
989        let ordinary_integrand = check(false)?;
990        let explicit_integrand = check(true)?;
991        assert!(!ordinary_integrand.is_zero());
992        assert!(!explicit_integrand.is_zero());
993
994        let selector_atoms = production
995            .expression
996            .orientations
997            .iter_enumerated()
998            .map(|(id, _)| id.atom())
999            .collect::<Vec<_>>();
1000        // Prove that every nonzero selector monomial is one production key.
1001        // Then summing its one-hot selections is exactly sigma=1, without
1002        // expanding 240 separately selected rational representations.
1003        for (key, coefficient) in ordinary_integrand.coefficient_list::<u8>(&selector_atoms) {
1004            if !coefficient.is_zero() {
1005                assert!(
1006                    selector_atoms.contains(&key),
1007                    "the final integrand must be homogeneous of degree one in its production selectors"
1008                );
1009                assert!(
1010                    !coefficient.contains_symbol(OrientationID::symbol()),
1011                    "selector coefficients must not contain uncatalogued production keys"
1012                );
1013            }
1014        }
1015        let ordinary_sum = ordinary_integrand
1016            .replace(function!(
1017                OrientationID::symbol(),
1018                symbolica::symbol!("gl24_selector_")
1019            ))
1020            .with(Atom::one());
1021        assert!(
1022            (ordinary_sum.collect_factors() - explicit_integrand.collect_factors()).is_zero(),
1023            "summing the orientation-local final integrand must recover the explicit final integrand"
1024        );
1025
1026        Ok(())
1027    }
1028
1029    #[test]
1030    fn nested_scalar_bubble_direct_3d_modes_match_without_a_cut() -> Result<()> {
1031        test_initialise()?;
1032        let mut graph: Graph = dot!(
1033            digraph nested_scalar_bubble_selector_contract {
1034                edge [particle="scalar_1"];
1035                node [num=1];
1036
1037                v1 -> v2 [id=0 lmb_id=0];
1038                v1 -> v2 [id=1];
1039                v2 -> v3 [id=2 lmb_id=1];
1040                v3 -> v1 [id=3];
1041            },
1042            "scalars"
1043        )?;
1044        let options = graph.denominator_only_cff_3d_expression_options();
1045        let numerator = graph.production_numerator_atom_for_full_3d_expression();
1046        let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
1047        let production = graph.generate_3d_expression_for_integrand(
1048            &[],
1049            &canonization,
1050            &options,
1051            Some(&numerator),
1052        )?;
1053        let uv_filter = graph
1054            .get_edge_subgraph(EdgeIndex(0))
1055            .union(&graph.get_edge_subgraph(EdgeIndex(1)));
1056        let uv_subgraph = InternalSubGraph::cleaned_filter_optimist(uv_filter, graph.as_ref());
1057        let child_spinney = Spinney::new(uv_subgraph, &graph, &graph.loop_momentum_basis)?
1058            .expect("the doubled edge is a logarithmic one-loop UV subgraph");
1059        let orientation_pattern = OrientationPattern::default();
1060        let settings = UVgenerationSettings {
1061            generate_integrated: false,
1062            add_marker: false,
1063            ..Default::default()
1064        };
1065        let build = |mut route_graph: Graph, explicit_orientation_sum_only| -> Result<_> {
1066            let cutset = CutSet::empty(route_graph.n_hedges());
1067            let localizer = Localizer::new(
1068                &cutset,
1069                OrientationProjection::exact_expression(
1070                    &production,
1071                    &options,
1072                    &orientation_pattern,
1073                    explicit_orientation_sum_only,
1074                ),
1075            );
1076            let mut root = Approximation::new(Spinney::empty(&route_graph));
1077            root.root(&mut route_graph, localizer, &settings)?;
1078            let mut child = Approximation::new(child_spinney.clone());
1079            child.simple_approx = Some(
1080                root.simple_approx
1081                    .as_ref()
1082                    .expect("the root approximation is initialized")
1083                    .dependent(child.spinney.subgraph.clone()),
1084            );
1085            child.compute_4d(
1086                &route_graph,
1087                (crate::utils::vakint()?, &vakint::VakintSettings::default()),
1088                &root,
1089                &settings,
1090            )?;
1091            child.compute_3d(
1092                &root,
1093                &mut route_graph,
1094                localizer,
1095                &settings,
1096                &mut Local4dProjectionContext::default(),
1097            )?;
1098            let sectors = child.local_3d(&route_graph)?.direct()?.sectors()?;
1099            let mut hosts = sectors
1100                .iter()
1101                .flat_map(|sector| sector.active.iter_keys())
1102                .filter(|(_, integrands)| {
1103                    integrands
1104                        .resolved()
1105                        .expect("diagnostic branch numerator definitions must resolve")
1106                        .iter()
1107                        .any(|(_, atom)| !atom.is_zero())
1108                })
1109                .map(|(key, _)| key.selector_host)
1110                .collect::<Vec<_>>();
1111            hosts.sort_unstable();
1112            hosts.dedup();
1113            let summarize_direct = |integrands: &DirectResidueBranches| {
1114                let mut by_host = BTreeMap::<OrientationID, Atom>::new();
1115                for (key, branch) in integrands.iter_keys() {
1116                    *by_host.entry(key.selector_host).or_default() += branch
1117                        .resolved()
1118                        .expect("diagnostic branch numerator definitions must resolve")
1119                        .iter()
1120                        .fold(Atom::Zero, |sum, (_, atom)| sum + atom);
1121                }
1122                by_host.into_iter().collect::<Vec<_>>()
1123            };
1124            let summarize_projected = |integrands: &local_3d::OrientationIntegrands| {
1125                let mut by_host = BTreeMap::<OrientationID, Atom>::new();
1126                for (host, _, branch) in integrands.iter_orientations() {
1127                    *by_host.entry(host).or_default() += branch
1128                        .resolved()
1129                        .expect("diagnostic branch numerator definitions must resolve")
1130                        .iter()
1131                        .fold(Atom::Zero, |sum, (_, atom)| sum + atom);
1132                }
1133                by_host.into_iter().collect::<Vec<_>>()
1134            };
1135            let child_branches = sectors
1136                .iter()
1137                .flat_map(|sector| summarize_direct(&sector.active))
1138                .collect::<Vec<_>>();
1139            let mut outer_branches = Vec::new();
1140            if !explicit_orientation_sum_only {
1141                for sector in sectors {
1142                    let reduced = route_graph
1143                        .full_filter()
1144                        .subtract(child.subgraph())
1145                        .subtract(&route_graph.initial_state_cut);
1146                    let resnum = route_graph
1147                        .numerator(&reduced, child.subgraph())
1148                        .get_single_atom()
1149                        .expect("the scalar outer numerator is available")
1150                        * route_graph.global_atom();
1151                    let analysis_branches = sector
1152                        .active
1153                        .iter_keys()
1154                        .map(|(_, integrands)| integrands.resolved())
1155                        .collect::<Result<Vec<_>>>()?;
1156                    let analysis_numerators = analysis_branches
1157                        .iter()
1158                        .flat_map(|integrands| integrands.iter())
1159                        .map(|(_, atom)| atom * &resnum);
1160                    let outer = localizer.projected_cff(
1161                        &mut route_graph,
1162                        child.subgraph(),
1163                        analysis_numerators,
1164                        CffGenerationContext::Standalone,
1165                    )?;
1166                    outer_branches.extend(summarize_projected(&outer));
1167                }
1168            }
1169            let final_integrand = child
1170                .final_integrand(&route_graph)?
1171                .iter()
1172                .find(|(index, _)| *index == CutCFFIndex::new_all_none())
1173                .ok_or_else(|| eyre!("the scalar-bubble child has no root residue"))?
1174                .1
1175                .clone();
1176            Ok((hosts, child_branches, outer_branches, final_integrand))
1177        };
1178        let build_full_cff_oracle = |mut route_graph: Graph| -> Result<Atom> {
1179            let cutset = CutSet::empty(route_graph.n_hedges());
1180            let localizer = Localizer::new(
1181                &cutset,
1182                OrientationProjection::exact_expression(
1183                    &production,
1184                    &options,
1185                    &orientation_pattern,
1186                    false,
1187                ),
1188            );
1189            let mut root = Approximation::new(Spinney::empty(&route_graph));
1190            root.root(&mut route_graph, localizer, &settings)?;
1191            let child = Approximation::new(child_spinney.clone());
1192            let root_local = root.local_3d(&route_graph)?.direct()?.clone();
1193            let local = Direct3dApproximation::new(localizer, &mut route_graph, &settings).run(
1194                &root_local,
1195                &IntegratedCts::root(),
1196                &child,
1197                &root,
1198                &child,
1199                &root,
1200            )?;
1201            Ok(FinalIntegrandBuilder::new(localizer, &settings)
1202                .build_direct(&mut route_graph, &child, &local, &IntegratedCts::root())?
1203                .iter()
1204                .find(|(index, _)| *index == CutCFFIndex::new_all_none())
1205                .ok_or_else(|| eyre!("the full-CFF selector oracle has no root residue"))?
1206                .1)
1207        };
1208        let (ordinary_hosts, child_branches, outer_branches, ordinary) =
1209            build(graph.clone(), false)?;
1210        let (_, _, _, explicit) = build(graph.clone(), true)?;
1211        let full_cff_oracle = build_full_cff_oracle(graph.clone())?;
1212
1213        let production_map_ids = production
1214            .expression
1215            .orientations
1216            .iter_enumerated()
1217            .map(|(id, _)| id)
1218            .collect::<Vec<_>>();
1219        let ordinary_sum = production_map_ids
1220            .iter()
1221            .map(|id| id.select(ordinary.as_view()))
1222            .fold(Atom::Zero, |sum, atom| sum + atom);
1223        let full_cff_sum = production_map_ids
1224            .iter()
1225            .map(|id| id.select(full_cff_oracle.as_view()))
1226            .fold(Atom::Zero, |sum, atom| sum + atom);
1227        assert!(
1228            (&full_cff_sum - &explicit).expand().is_zero(),
1229            "the established full-CFF selector oracle must recover the explicit child sum"
1230        );
1231        let mismatched_map_keys = production_map_ids
1232            .iter()
1233            .filter_map(|id| {
1234                let ordinary = id.select(ordinary.as_view());
1235                let oracle = id.select(full_cff_oracle.as_view());
1236                let difference = (&ordinary - &oracle).expand();
1237                (!difference.is_zero()).then_some((
1238                    *id,
1239                    production.expression.orientations[*id]
1240                        .data
1241                        .orientation
1242                        .clone(),
1243                    ordinary,
1244                    oracle,
1245                    difference,
1246                ))
1247            })
1248            .collect::<Vec<_>>();
1249        assert!(
1250            (ordinary_sum - explicit).expand().is_zero(),
1251            "the isolated scalar-bubble child differs between direct local-3D modes; ordinary hosts {ordinary_hosts:?}, child branches {child_branches:?}, outer branches {outer_branches:?}, mismatched production map keys {mismatched_map_keys:?}"
1252        );
1253
1254        Ok(())
1255    }
1256
1257    #[test]
1258    fn factorized_owned_dot_child_cff_matches_direct_3d_for_uncut_self_energy() -> Result<()> {
1259        test_initialise()?;
1260        let mut graph: Graph = dot!(digraph factorized_child_cff {
1261            edge [num=1 mass=1]
1262            node [num=1]
1263            incoming [style=invis]
1264            outgoing [style=invis]
1265
1266            incoming -> v0 [id=0]
1267            v0 -> v1 [id=1 lmb_id=0]
1268            v0 -> v1 [id=2 num="Q(2,spenso::cind(0))^2-Q(2,spenso::cind(1))^2-Q(2,spenso::cind(2))^2-Q(2,spenso::cind(3))^2-1"]
1269            v1 -> outgoing [id=3]
1270        })?;
1271        let options = graph.denominator_only_cff_3d_expression_options();
1272        let numerator = graph.production_numerator_atom_for_full_3d_expression();
1273        let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
1274        let production = graph.generate_3d_expression_for_integrand(
1275            &[],
1276            &canonization,
1277            &options,
1278            Some(&numerator),
1279        )?;
1280        let uv_filter = graph
1281            .get_edge_subgraph(EdgeIndex(1))
1282            .union(&graph.get_edge_subgraph(EdgeIndex(2)));
1283        let uv_subgraph = InternalSubGraph::cleaned_filter_optimist(uv_filter, graph.as_ref());
1284        let child_spinney = Spinney::with_scheme(
1285            uv_subgraph,
1286            &graph,
1287            &graph.loop_momentum_basis,
1288            ApproximationType::MUV,
1289            2,
1290        )?
1291        .expect("the self-energy bubble has a compatible sub-LMB");
1292        assert_eq!(child_spinney.dod, 2);
1293        let cutset = CutSet::empty(graph.n_hedges());
1294        let orientation_pattern = OrientationPattern::default();
1295        let build = |mut route_graph: Graph, from_expanded_4d| -> Result<_> {
1296            let settings = UVgenerationSettings {
1297                generate_integrated: false,
1298                local_uv_cts_from_expanded_4d_integrands: from_expanded_4d,
1299                ..Default::default()
1300            };
1301            let bound_reports = Mutex::new(Vec::new());
1302            let localizer = Localizer::new(
1303                &cutset,
1304                OrientationProjection::exact_expression(
1305                    &production,
1306                    &options,
1307                    &orientation_pattern,
1308                    true,
1309                )
1310                .with_energy_degree_bound_reports(&bound_reports),
1311            );
1312            let mut root = Approximation::new(Spinney::empty(&route_graph));
1313            root.root(&mut route_graph, localizer, &settings)?;
1314            let root_integrand = root
1315                .final_integrand(&route_graph)?
1316                .iter()
1317                .find(|(index, _)| *index == CutCFFIndex::new_all_none())
1318                .expect("the uncut root has one residue sector")
1319                .1;
1320            let mut child = Approximation::new(child_spinney.clone());
1321            child.simple_approx = Some(
1322                root.simple_approx
1323                    .as_ref()
1324                    .expect("the root approximation is initialized")
1325                    .dependent(child.spinney.subgraph.clone()),
1326            );
1327            let vakint_settings = vakint::VakintSettings::default();
1328            child.compute_4d(
1329                &route_graph,
1330                (crate::utils::vakint()?, &vakint_settings),
1331                &root,
1332                &settings,
1333            )?;
1334            let projected_denominator_owners: Vec<Vec<usize>> = if from_expanded_4d {
1335                child
1336                    .local(&route_graph)?
1337                    .active_sectors()
1338                    .iter()
1339                    .map(|sector| sector.physical_terms())
1340                    .collect::<Result<Vec<_>>>()?
1341                    .into_iter()
1342                    .flatten()
1343                    .map(|term| {
1344                        term.denominators
1345                            .into_iter()
1346                            .map(|denominator| usize::from(denominator.source_edge))
1347                            .collect()
1348                    })
1349                    .collect()
1350            } else {
1351                Vec::new()
1352            };
1353            child.compute_3d(
1354                &root,
1355                &mut route_graph,
1356                localizer,
1357                &settings,
1358                &mut Local4dProjectionContext::default(),
1359            )?;
1360            let integrand = child
1361                .final_integrand(&route_graph)?
1362                .iter()
1363                .find(|(index, _)| *index == CutCFFIndex::new_all_none())
1364                .expect("the uncut self-energy has one residue sector")
1365                .1;
1366            let reports = bound_reports
1367                .lock()
1368                .unwrap_or_else(|poisoned| poisoned.into_inner())
1369                .clone();
1370            Ok((
1371                integrand,
1372                root_integrand,
1373                reports,
1374                projected_denominator_owners,
1375            ))
1376        };
1377        let (direct_expression, direct_root, direct_reports, _) = build(graph.clone(), false)?;
1378        let (
1379            projected_expression,
1380            projected_root,
1381            projected_reports,
1382            mut projected_denominator_owners,
1383        ) = build(graph.clone(), true)?;
1384        // Numerator Taylor layers may share a topology. Compare propagator
1385        // multisets without regrouping their factorized production numerators.
1386        for owners in &mut projected_denominator_owners {
1387            owners.sort_unstable();
1388        }
1389        projected_denominator_owners.sort_unstable();
1390        projected_denominator_owners.dedup();
1391        assert_eq!(
1392            projected_denominator_owners,
1393            vec![vec![1, 1, 2], vec![1, 2], vec![1, 2, 2], vec![1, 2, 2, 2]],
1394        );
1395
1396        let direct_bound = direct_reports
1397            .iter()
1398            .find(|report| {
1399                report.source_kind == crate::cff::CffEnergyBoundSourceKind::PhysicalGraph
1400                    && report.physical_parent_bounds == vec![(2, 2)]
1401            })
1402            .expect("the direct child CFF must retain its owned-dot energy bounds");
1403        assert_eq!(direct_bound.assigned_cff_source_bounds, vec![(2, 2)]);
1404        let projected_bound = projected_reports
1405            .iter()
1406            .find(|report| {
1407                report.source_kind == crate::cff::CffEnergyBoundSourceKind::ExactFourD
1408                    && report.physical_parent_bounds == vec![(2, 4)]
1409            })
1410            .expect("the quadratic numerator and two denominator derivatives retain e2 rank four");
1411        assert_eq!(
1412            projected_bound
1413                .assigned_cff_source_bounds
1414                .iter()
1415                .map(|(_, degree)| degree)
1416                .sum::<usize>(),
1417            4,
1418        );
1419
1420        // Component expansion is confined to these test copies. Production
1421        // keeps the owned-dot numerator factorized throughout construction.
1422        let normalize = |expression: Atom| {
1423            expression
1424                .replace(GS.dim)
1425                .with(4)
1426                .replace(GS.dim_epsilon)
1427                .with(0)
1428                .replace(GS.m_uv_expansion)
1429                .with(Atom::num(Rational::from((7, 5))))
1430                .replace(GS.m_uv_vacuum)
1431                .with(Atom::num(Rational::from((7, 5))))
1432                .replace(function!(GS.tree_denom_wrapper, W_.x_))
1433                .with(W_.x_)
1434                .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
1435                .with(W_.d_)
1436                .replace(function!(GS.ose, W_.mass_, W_.prop_))
1437                .with(W_.prop_)
1438                .replace(function!(GS.on_shell_energy, W_.a_, W_.prop_))
1439                .with(Atom::var(W_.prop_).pow((1, 2)))
1440                .expand_dots()
1441                .expect("test-only component expansion must succeed")
1442        };
1443        let expressions = vec![
1444            normalize(direct_expression),
1445            normalize(projected_expression),
1446            normalize(direct_root),
1447            normalize(projected_root),
1448        ];
1449        assert_eq!(graph.loop_momentum_basis.loop_edges.len(), 1);
1450        assert_eq!(graph.loop_momentum_basis.ext_edges.len(), 2);
1451        let rational =
1452            |numerator, denominator| F::<ArbPrec>::from(&Rational::from((numerator, denominator)));
1453        let loop_moms: LoopMomenta<F<ArbPrec>> = [ThreeMomentum::new(
1454            rational(31, 100),
1455            rational(-47, 100),
1456            rational(83, 100),
1457        )]
1458        .into_iter()
1459        .collect();
1460        let external_moms: ExternalFourMomenta<F<ArbPrec>> = [
1461            [
1462                rational(12, 5),
1463                rational(0, 1),
1464                rational(0, 1),
1465                rational(0, 1),
1466            ]
1467            .into(),
1468            [
1469                rational(-12, 5),
1470                rational(0, 1),
1471                rational(0, 1),
1472                rational(0, 1),
1473            ]
1474            .into(),
1475        ]
1476        .into_iter()
1477        .collect();
1478        let sample = MomentumSample {
1479            sample: BareMomentumSample {
1480                loop_moms: loop_moms.clone(),
1481                dual_loop_moms: None,
1482                loop_mom_cache_id: 0,
1483                loop_mom_base_cache_id: 0,
1484                external_moms,
1485                external_mom_cache_id: 0,
1486                external_mom_base_cache_id: 0,
1487                jacobian: rational(1, 1),
1488                orientation: None,
1489                parameterization_branch: None,
1490            },
1491        };
1492        let orientations = TiVec::<OrientationID, EdgeVec<Orientation>>::new();
1493        let orientation_filter = SubSet::full(orientations.len());
1494        let mut param_builder = graph.param_builder.clone();
1495        let (mut evaluator, _) = EvaluatorStack::new_explicit_sum_with_timings(
1496            &expressions,
1497            &param_builder,
1498            &[],
1499            None,
1500            &EvaluatorSettings::default(),
1501        )?;
1502        let input = <ArbPrec as GenericEvaluatorFloat>::get_parameters(
1503            &mut param_builder,
1504            (false, false),
1505            &graph,
1506            &sample,
1507            &[],
1508            &[],
1509            None,
1510            None,
1511            None,
1512        );
1513        let values = evaluator
1514            .evaluate(
1515                input,
1516                SingleOrAllOrientations::All {
1517                    all: &orientations,
1518                    filter: &orientation_filter,
1519                },
1520                &RuntimeSettings::default(),
1521                &mut EvaluationMetaData::new_empty(),
1522            )?
1523            .into_iter()
1524            .map(|value| value.unwrap_real())
1525            .collect::<Vec<_>>();
1526        let distance = (values[0].clone() - values[1].clone()).norm().re;
1527        let direct_norm = values[0].clone().norm().re;
1528        let projected_norm = values[1].clone().norm().re;
1529        let scale = if direct_norm > projected_norm {
1530            direct_norm
1531        } else {
1532            projected_norm
1533        };
1534        let relative_distance = if scale.is_zero() {
1535            distance
1536        } else {
1537            distance / scale
1538        };
1539        let tolerance = F(ArbPrec::default().epsilon()).sqrt().sqrt().sqrt();
1540        assert!(
1541            relative_distance <= tolerance,
1542            "kinematically consistent owned-dot self-energy mismatch: direct={:e}, projected={:e}, relative delta={relative_distance:e}, tolerance={tolerance:e}",
1543            values[0],
1544            values[1],
1545        );
1546        Ok(())
1547    }
1548
1549    #[test]
1550    fn direct_nested_scalar_banana_replays_complete_cff_at_depth_three() -> Result<()> {
1551        test_initialise()?;
1552        for depth in [2, 3] {
1553            let mut graph: Graph = if depth == 2 {
1554                dot!(digraph nested_scalar_banana {
1555                    edge [num=1 mass=1]
1556                    node [num=1]
1557                    incoming [style=invis]
1558                    outgoing [style=invis]
1559
1560                    incoming -> a [id=0]
1561                    a -> b [id=1 lmb_id=0]
1562                    a -> b [id=2 lmb_id=1]
1563                    a -> b [id=3]
1564                    b -> outgoing [id=4]
1565                })?
1566            } else {
1567                dot!(digraph depth_three_scalar_banana {
1568                    edge [num=1 mass=1]
1569                    node [num=1]
1570                    incoming [style=invis]
1571                    outgoing [style=invis]
1572
1573                    incoming -> a [id=0]
1574                    a -> b [id=1 lmb_id=0]
1575                    a -> b [id=2 lmb_id=1]
1576                    a -> b [id=3 lmb_id=2]
1577                    a -> b [id=4]
1578                    b -> outgoing [id=5]
1579                })?
1580            };
1581            let global_marker = symbolica::symbol!("nested_scalar_banana::global");
1582            graph.global_prefactor.num = Atom::var(global_marker);
1583
1584            let mut filter = graph.get_edge_subgraph(EdgeIndex(1));
1585            let mut spinneys = Vec::new();
1586            for edge in 2..=depth + 1 {
1587                filter = filter.union(&graph.get_edge_subgraph(EdgeIndex(edge)));
1588                let subgraph =
1589                    InternalSubGraph::cleaned_filter_optimist(filter.clone(), graph.as_ref());
1590                spinneys.push(
1591                    Spinney::new(subgraph, &graph, &graph.loop_momentum_basis)?
1592                        .expect("each nested banana has a compatible sub-LMB"),
1593                );
1594            }
1595
1596            let options = graph.denominator_only_cff_3d_expression_options();
1597            let numerator = graph.production_numerator_atom_for_full_3d_expression();
1598            let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
1599            let production = graph.generate_3d_expression_for_integrand(
1600                &[],
1601                &canonization,
1602                &options,
1603                Some(&numerator),
1604            )?;
1605            let cutset = CutSet::empty(graph.n_hedges());
1606            let orientation_pattern = OrientationPattern::default();
1607
1608            let build = |mut route_graph: Graph, from_expanded_4d| -> Result<Vec<Atom>> {
1609                let settings = UVgenerationSettings {
1610                    generate_integrated: false,
1611                    local_uv_cts_from_expanded_4d_integrands: from_expanded_4d,
1612                    ..Default::default()
1613                };
1614                let localizer = Localizer::new(
1615                    &cutset,
1616                    OrientationProjection::exact_expression(
1617                        &production,
1618                        &options,
1619                        &orientation_pattern,
1620                        true,
1621                    ),
1622                );
1623                let vakint_settings = vakint::VakintSettings::default();
1624                let mut root = Approximation::new(Spinney::empty(&route_graph));
1625                root.root(&mut route_graph, localizer, &settings)?;
1626
1627                let mut parent = root;
1628                let mut finals = Vec::new();
1629                for spinney in &spinneys {
1630                    let mut current = Approximation::new(spinney.clone());
1631                    current.simple_approx = Some(
1632                        parent
1633                            .simple_approx
1634                            .as_ref()
1635                            .expect("the parent approximation is initialized")
1636                            .dependent(current.spinney.subgraph.clone()),
1637                    );
1638                    current.compute_4d(
1639                        &route_graph,
1640                        (crate::utils::vakint()?, &vakint_settings),
1641                        &parent,
1642                        &settings,
1643                    )?;
1644                    current.compute_3d(
1645                        &parent,
1646                        &mut route_graph,
1647                        localizer,
1648                        &settings,
1649                        &mut Local4dProjectionContext::default(),
1650                    )?;
1651                    let local = current.local_3d(&route_graph)?;
1652                    if let Local3DCts::Direct(direct) = local {
1653                        let value = direct.branches()?.factorized_sum();
1654                        assert!(
1655                            !value.contains_symbol(global_marker),
1656                            "the untouched global numerator stays outside every local Taylor operation"
1657                        );
1658                        let negated = (-local.clone()).map(|atom| Ok(atom.clone()))?;
1659                        assert!(
1660                            (negated.direct()?.branches()?.factorized_sum() + value)
1661                                .expand()
1662                                .is_zero(),
1663                            "mapping a negated forest must negate its complete materialized value"
1664                        );
1665                    }
1666                    let final_integrand = current
1667                        .final_integrand(&route_graph)?
1668                        .iter()
1669                        .find(|(index, _)| *index == CutCFFIndex::new_all_none())
1670                        .ok_or_else(|| eyre!("the nested banana has no uncut final integrand"))?
1671                        .1
1672                        .clone();
1673                    assert!(
1674                        !final_integrand.contains_symbol(OrientationID::symbol()),
1675                        "explicit final assembly must have summed all selectors"
1676                    );
1677                    let without_global = final_integrand.replace(global_marker).with(1);
1678                    assert!(
1679                        !without_global.is_zero(),
1680                        "the subtraction fixture must be nonzero"
1681                    );
1682                    assert!(
1683                        (&final_integrand - Atom::var(global_marker) * without_global)
1684                            .expand()
1685                            .is_zero(),
1686                        "final assembly restores the outside global numerator exactly once"
1687                    );
1688                    finals.push(final_integrand);
1689                    parent = current;
1690                }
1691                Ok(finals)
1692            };
1693
1694            let direct = build(graph.clone(), false)?;
1695            let projected = build(graph.clone(), true)?;
1696            // Test-only component expansion makes the independently constructed
1697            // expressions evaluable by one Arb stack. Production keeps all
1698            // numerator factors intact throughout CFF and UV construction.
1699            let normalize = |expression: Atom| {
1700                expression
1701                    .replace(global_marker)
1702                    .with(1)
1703                    .replace(GS.dim)
1704                    .with(4)
1705                    .replace(GS.dim_epsilon)
1706                    .with(0)
1707                    .replace(GS.m_uv_expansion)
1708                    .with(Atom::num(Rational::from((7, 5))))
1709                    .replace(GS.m_uv_vacuum)
1710                    .with(Atom::num(Rational::from((7, 5))))
1711                    .replace(function!(GS.tree_denom_wrapper, W_.x_))
1712                    .with(W_.x_)
1713                    .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
1714                    .with(W_.d_)
1715                    .replace(function!(GS.ose, W_.mass_, W_.prop_))
1716                    .with(W_.prop_)
1717                    .replace(function!(GS.on_shell_energy, W_.a_, W_.prop_))
1718                    .with(Atom::var(W_.prop_).pow((1, 2)))
1719                    .expand_dots()
1720                    .expect("test-only component expansion must succeed")
1721            };
1722            let expressions = direct
1723                .into_iter()
1724                .zip(projected)
1725                .flat_map(|(direct, projected)| [normalize(direct), normalize(projected)])
1726                .collect::<Vec<_>>();
1727
1728            let rational = |numerator, denominator| {
1729                F::<ArbPrec>::from(&Rational::from((numerator, denominator)))
1730            };
1731            let loop_moms: LoopMomenta<F<ArbPrec>> = [
1732                ThreeMomentum::new(rational(31, 100), rational(-47, 100), rational(83, 100)),
1733                ThreeMomentum::new(rational(-19, 100), rational(37, 100), rational(61, 100)),
1734                ThreeMomentum::new(rational(43, 100), rational(29, 100), rational(-53, 100)),
1735            ]
1736            .into_iter()
1737            .take(depth)
1738            .collect();
1739            let external_moms: ExternalFourMomenta<F<ArbPrec>> = [
1740                [
1741                    rational(12, 5),
1742                    rational(11, 100),
1743                    rational(-8, 100),
1744                    rational(5, 100),
1745                ]
1746                .into(),
1747                [
1748                    rational(-12, 5),
1749                    rational(-11, 100),
1750                    rational(8, 100),
1751                    rational(-5, 100),
1752                ]
1753                .into(),
1754            ]
1755            .into_iter()
1756            .collect();
1757            let sample = MomentumSample {
1758                sample: BareMomentumSample {
1759                    loop_moms,
1760                    dual_loop_moms: None,
1761                    loop_mom_cache_id: 0,
1762                    loop_mom_base_cache_id: 0,
1763                    external_moms,
1764                    external_mom_cache_id: 0,
1765                    external_mom_base_cache_id: 0,
1766                    jacobian: rational(1, 1),
1767                    orientation: None,
1768                    parameterization_branch: None,
1769                },
1770            };
1771            let orientations = TiVec::<OrientationID, EdgeVec<Orientation>>::new();
1772            let orientation_filter = SubSet::full(orientations.len());
1773            let mut param_builder = graph.param_builder.clone();
1774            let (mut evaluator, _) = EvaluatorStack::new_explicit_sum_with_timings(
1775                &expressions,
1776                &param_builder,
1777                &[],
1778                None,
1779                &EvaluatorSettings::default(),
1780            )?;
1781            let input = <ArbPrec as GenericEvaluatorFloat>::get_parameters(
1782                &mut param_builder,
1783                (false, false),
1784                &graph,
1785                &sample,
1786                &[],
1787                &[],
1788                None,
1789                None,
1790                None,
1791            );
1792            let values = evaluator
1793                .evaluate(
1794                    input,
1795                    SingleOrAllOrientations::All {
1796                        all: &orientations,
1797                        filter: &orientation_filter,
1798                    },
1799                    &RuntimeSettings::default(),
1800                    &mut EvaluationMetaData::new_empty(),
1801                )?
1802                .into_iter()
1803                .map(|value| value.unwrap_real())
1804                .collect::<Vec<_>>();
1805            let tolerance = F(ArbPrec::default().epsilon()).sqrt().sqrt().sqrt();
1806            for (stage, [direct, projected]) in values.as_chunks::<2>().0.iter().enumerate() {
1807                let label = format!("depth {depth}, stage {}", stage + 1);
1808                assert!(
1809                    [&direct.re, &direct.im, &projected.re, &projected.im]
1810                        .into_iter()
1811                        .all(symbolica::domains::float::SingleFloat::is_finite),
1812                    "{label} must produce finite values: direct={direct:e}, projected={projected:e}"
1813                );
1814                let distance = (direct.clone() - projected.clone()).norm().re;
1815                let direct_norm = direct.clone().norm().re;
1816                let projected_norm = projected.clone().norm().re;
1817                let scale = if direct_norm > projected_norm {
1818                    direct_norm
1819                } else {
1820                    projected_norm
1821                };
1822                let relative_distance = if scale.is_zero() {
1823                    distance
1824                } else {
1825                    distance / scale
1826                };
1827                assert!(
1828                    relative_distance <= tolerance,
1829                    "{label} direct complete-CFF vs projected local-4D mismatch: direct={direct:e}, projected={projected:e}, relative delta={relative_distance:e}, tolerance={tolerance:e}"
1830                );
1831            }
1832        }
1833        Ok(())
1834    }
1835
1836    #[test]
1837    fn complete_self_energy_taylor_sum_matches_direct_3d_for_raised_lu_jets() -> Result<()> {
1838        test_initialise()?;
1839        let base_graph: Graph = dot!(
1840            digraph complete_self_energy_taylor_sum {
1841                num = 1
1842                edge [particle="scalar_1" num=1]
1843                node [num=1]
1844
1845                ext0 [style=invis is_cut=0]
1846                v2 -> ext0 [id=0]
1847                ext0 -> v3
1848                v0 -> v1 [id=1 lmb_id=0 num="Q(1,spenso::mink(4,1))"]
1849                v0 -> v1 [id=2]
1850                v3 -> v0 [id=3 lmb_id=1 num="Q(3,spenso::mink(4,1))"]
1851                v1 -> v2 [id=4]
1852                v2 -> v3 [id=5]
1853            },
1854            "scalars"
1855        )?;
1856        assert_eq!(
1857            base_graph.loop_momentum_basis.edge_signatures[EdgeIndex(3)],
1858            base_graph.loop_momentum_basis.edge_signatures[EdgeIndex(4)],
1859            "the GL0-like cograph must expose a genuinely repeated outer channel"
1860        );
1861
1862        let outer_factors = [
1863            "unit outer numerator",
1864            "factorized cubic outer numerator",
1865            "factorized affine cubic outer numerator",
1866        ];
1867        for outer_label in outer_factors {
1868            let mut graph = base_graph.clone();
1869            graph.underlying = graph.underlying.map_data_ref(
1870                |_, _, vertex| {
1871                    let mut vertex = vertex.clone();
1872                    if outer_label == "factorized affine cubic outer numerator"
1873                        && vertex.name.value == "v2"
1874                    {
1875                        vertex.num.value *= GS.emr_mom(EdgeIndex(5), GS.cind(0))
1876                            + GS.emr_mom(EdgeIndex(0), GS.cind(0));
1877                    }
1878                    vertex
1879                },
1880                |_, edge, _, data| {
1881                    data.map(|data| {
1882                        let mut data = data.clone();
1883                        let factor = match (outer_label, edge) {
1884                            ("factorized cubic outer numerator", EdgeIndex(3..=5)) => {
1885                                GS.emr_mom(edge, GS.cind(0))
1886                            }
1887                            ("factorized affine cubic outer numerator", EdgeIndex(3 | 5)) => {
1888                                GS.emr_mom(edge, GS.cind(0))
1889                            }
1890                            _ => Atom::one(),
1891                        };
1892                        data.num.value *= factor;
1893                        data
1894                    })
1895                },
1896                |_, data| data.clone(),
1897            );
1898            let options = graph.denominator_only_cff_3d_expression_options();
1899            let numerator = graph.production_numerator_atom_for_full_3d_expression();
1900            let canonization = graph.get_esurface_canonization(&graph.loop_momentum_basis);
1901            let production = graph.generate_3d_expression_for_integrand(
1902                &[],
1903                &canonization,
1904                &options,
1905                Some(&numerator),
1906            )?;
1907            let lu_cut = graph
1908                .determine_raised_esurfaces_from_expression(&production.expression)
1909                .raised_groups
1910                .into_iter()
1911                .find(|group| {
1912                    group.max_occurence > 1
1913                        && group.esurface_ids.iter().any(|surface_id| {
1914                            !production.expression.surfaces.esurface_cache[*surface_id]
1915                                .external_shift
1916                                .is_empty()
1917                        })
1918                })
1919                .expect("the repeated outer channel must supply a physical raised LU surface");
1920            assert_eq!(
1921                lu_cut.max_occurence, 2,
1922                "the GL0-like cograph must expose LU orders one and two"
1923            );
1924            let mut cutset = CutSet::empty(graph.n_hedges());
1925            cutset.residue_selector.lu = Some(LuCutSelection {
1926                raised_group: lu_cut.clone(),
1927                cut_edge_alternatives: lu_cut
1928                    .esurface_ids
1929                    .iter()
1930                    .map(|surface_id| {
1931                        production.expression.surfaces.esurface_cache[*surface_id]
1932                            .energies
1933                            .clone()
1934                    })
1935                    .collect(),
1936            });
1937            let uv_filter = graph
1938                .get_edge_subgraph(EdgeIndex(1))
1939                .union(&graph.get_edge_subgraph(EdgeIndex(2)));
1940            let uv_subgraph = InternalSubGraph::cleaned_filter_optimist(uv_filter, graph.as_ref());
1941            let child_spinney = Spinney::with_scheme(
1942                uv_subgraph,
1943                &graph,
1944                &graph.loop_momentum_basis,
1945                ApproximationType::MUV,
1946                1,
1947            )?
1948            .expect("the self-energy bubble has a compatible sub-LMB");
1949            let orientation_pattern = OrientationPattern::default();
1950
1951            let build_child = |mut route_graph: Graph,
1952                               route_cutset: &CutSet,
1953                               local_uv_cts_from_expanded_4d_integrands|
1954             -> Result<BTreeMap<CutCFFIndex, Atom>> {
1955                let settings = UVgenerationSettings {
1956                    generate_integrated: false,
1957                    local_uv_cts_from_expanded_4d_integrands,
1958                    ..Default::default()
1959                };
1960                let localizer = Localizer::new(
1961                    route_cutset,
1962                    OrientationProjection::exact_expression(
1963                        &production,
1964                        &options,
1965                        &orientation_pattern,
1966                        true,
1967                    ),
1968                );
1969                let mut root = Approximation::new(Spinney::empty(&route_graph));
1970                root.root(&mut route_graph, localizer, &settings)?;
1971                let mut child = Approximation::new(child_spinney.clone());
1972                child.simple_approx = Some(
1973                    root.simple_approx
1974                        .as_ref()
1975                        .expect("the root approximation is initialized")
1976                        .dependent(child.spinney.subgraph.clone()),
1977                );
1978                let vakint_settings = vakint::VakintSettings::default();
1979                child.compute_4d(
1980                    &route_graph,
1981                    (crate::utils::vakint()?, &vakint_settings),
1982                    &root,
1983                    &settings,
1984                )?;
1985                // The complete Taylor value keeps the natural propagator
1986                // powers, factorized numerator, and physical edge owners.
1987                let reduced = child.reduced_subgraph(&root);
1988                let t_arg = route_graph
1989                    .numerator(&reduced, root.subgraph())
1990                    .to_d_dim(GS.dim)
1991                    .get_single_atom()
1992                    .expect("the scalar child numerator is available")
1993                    / route_graph.denominator(&reduced, |_| 1);
1994                let explicit_taylor_sum = route_graph
1995                    .uv_rescaled(
1996                        &reduced,
1997                        route_graph.n_loops(child.subgraph()),
1998                        child.lmb(),
1999                        child.lmb(),
2000                        &t_arg,
2001                    )
2002                    .series(GS.rescale, Atom::Zero, 0)
2003                    .expect("the scalar child Taylor series exists")
2004                    .to_atom()
2005                    .replace(GS.rescale)
2006                    .with(Atom::one())
2007                    .simplify_metrics()
2008                    .to_dots()
2009                    .normalize_dots();
2010                let local_atom = child.local(&route_graph)?.atom();
2011                assert!(
2012                    (local_atom.collect_factors() + explicit_taylor_sum.collect_factors())
2013                        .is_zero(),
2014                    "the local counterterm must equal the negative independently reconstructed Taylor sum"
2015                );
2016                child.compute_3d(
2017                    &root,
2018                    &mut route_graph,
2019                    localizer,
2020                    &settings,
2021                    &mut Local4dProjectionContext::default(),
2022                )?;
2023                Ok(child.final_integrand(&route_graph)?.iter().collect())
2024            };
2025            let direct = build_child(graph.clone(), &cutset, false)?;
2026            let expanded = build_child(graph.clone(), &cutset, true)?;
2027            let expected_orders = BTreeSet::from([1, 2]);
2028            assert_eq!(
2029                direct
2030                    .keys()
2031                    .filter_map(|index| index.lu_cut_order)
2032                    .collect::<BTreeSet<_>>(),
2033                expected_orders,
2034                "the direct child must retain both raised LU orders"
2035            );
2036            assert_eq!(
2037                direct.keys().collect::<Vec<_>>(),
2038                expanded.keys().collect::<Vec<_>>(),
2039                "the two routes must finalize the same residue sectors"
2040            );
2041
2042            let mass_squared = Atom::num(Rational::from((4, 9)));
2043            let m_uv = Atom::num(Rational::from((7, 5)));
2044            let base_spatial_momentum = |edge: EdgeIndex| match usize::from(edge) {
2045                1 => Atom::num(Rational::from((4, 3))),
2046                2 => Atom::num(Rational::from((-7, 12))),
2047                3..=5 => Atom::num(Rational::from((3, 4))),
2048                _ => Atom::Zero,
2049            };
2050            let scaled_on_shell_energy = |edge: EdgeIndex, rescale: Atom| {
2051                (mass_squared.clone() + (base_spatial_momentum(edge) * rescale).pow(2)).sqrt()
2052            };
2053            let representative =
2054                &production.expression.surfaces.esurface_cache[lu_cut.esurface_ids[0]];
2055            let root_energy_sum = representative
2056                .energies
2057                .iter()
2058                .map(|edge| scaled_on_shell_energy(*edge, Atom::one()))
2059                .fold(Atom::Zero, |sum, energy| sum + energy);
2060            let external_shift_coefficient = representative
2061                .external_shift
2062                .iter()
2063                .map(|(_, coefficient)| *coefficient)
2064                .sum::<i64>();
2065            assert_ne!(
2066                external_shift_coefficient, 0,
2067                "the selected raised LU surface must have a nonzero external shift"
2068            );
2069            let external_energy = -root_energy_sum / Atom::num(external_shift_coefficient);
2070            let external_edges = graph
2071                .external_momentum_edge_order()
2072                .into_iter()
2073                .collect::<BTreeSet<_>>();
2074            let scalarize = |expression: Atom| -> Result<Atom> {
2075                // Contract finite tensor components, including products of indexed
2076                // sums, without distributing the numerator. The independent oracle
2077                // below supplies its own coordinates and Arb jets; it shares tensor
2078                // contraction, but not EvaluatorStack lowering or parameter layout.
2079                let mut network = expression.parse_into_net()?;
2080                network.execute::<Sequential, SmallestDegree, _, _, _>(
2081                    &*TENSORLIB.read().unwrap(),
2082                    &*FUN_LIB,
2083                )?;
2084                Ok(match network.result_scalar()? {
2085                    ExecutionResult::One => Atom::one(),
2086                    ExecutionResult::Zero => Atom::Zero,
2087                    ExecutionResult::Val(value) => value.into_owned(),
2088                })
2089            };
2090            let rescale_expression = |mut expression: Atom| -> Result<Atom> {
2091                expression = expression
2092                    .replace(GS.dim)
2093                    .with(4)
2094                    .simplify_metrics()
2095                    .to_dots()
2096                    .normalize_dots()
2097                    .expand_dots()?;
2098                let rescale = Atom::var(GS.rescale);
2099                let compact_minkowski = parse_lit!(spenso::mink(4));
2100                let time_direction =
2101                    function!(GS.delta_vec, GS.cind(0), compact_minkowski.as_view());
2102                expression = expression
2103                    .replace(function!(
2104                        GS.dot,
2105                        time_direction.as_view(),
2106                        time_direction.as_view()
2107                    ))
2108                    .with(Atom::one());
2109                for left in 0..graph.underlying.n_edges() {
2110                    let left = EdgeIndex(left);
2111                    let left_spatial = if !external_edges.contains(&left) {
2112                        base_spatial_momentum(left) * &rescale
2113                    } else {
2114                        Atom::Zero
2115                    };
2116                    for right in left.0..graph.underlying.n_edges() {
2117                        let right = EdgeIndex(right);
2118                        let right_spatial = if !external_edges.contains(&right) {
2119                            base_spatial_momentum(right) * &rescale
2120                        } else {
2121                            Atom::Zero
2122                        };
2123                        expression = expression
2124                            .replace(function!(
2125                                GS.dot,
2126                                GS.emr_vec(left, compact_minkowski.as_view()),
2127                                GS.emr_vec(right, compact_minkowski.as_view())
2128                            ))
2129                            .with(-&left_spatial * right_spatial);
2130                    }
2131                    expression = expression
2132                        .replace(function!(
2133                            GS.dot,
2134                            time_direction.as_view(),
2135                            GS.emr_vec(left, compact_minkowski.as_view())
2136                        ))
2137                        .with(Atom::Zero);
2138                }
2139                expression = expression
2140                    .replace(Atom::var(GS.m_uv_expansion))
2141                    .with(m_uv.clone())
2142                    .replace(Atom::var(GS.m_uv_vacuum))
2143                    .with(m_uv.clone())
2144                    .replace(Atom::var(GS.integrated_loop_scale))
2145                    .with(Atom::one())
2146                    .replace(function!(GS.tree_denom_wrapper, W_.x_))
2147                    .with(W_.x_)
2148                    .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
2149                    .with(W_.d_);
2150                expression = scalarize(expression)?;
2151                for edge in 0..graph.underlying.n_edges() {
2152                    let edge = EdgeIndex(edge);
2153                    expression = expression
2154                        .replace(graph.underlying[edge].particle.mass_atom())
2155                        .with(mass_squared.clone().sqrt());
2156                    if external_edges.contains(&edge) {
2157                        expression = expression
2158                            .replace(GS.emr_mom(edge, GS.cind(0)))
2159                            .with(external_energy.clone());
2160                        for spatial_index in 1..=3 {
2161                            expression = expression
2162                                .replace(GS.emr_mom(edge, GS.cind(spatial_index)))
2163                                .with(Atom::Zero)
2164                                .replace(GS.emr_vec(edge, GS.cind(spatial_index)))
2165                                .with(Atom::Zero);
2166                        }
2167                    } else {
2168                        let on_shell_energy = scaled_on_shell_energy(edge, rescale.clone());
2169                        expression = expression
2170                            .replace(GS.emr_mom(edge, GS.cind(1)))
2171                            .with(base_spatial_momentum(edge) * &rescale)
2172                            .replace(GS.emr_vec(edge, GS.cind(1)))
2173                            .with(base_spatial_momentum(edge) * &rescale)
2174                            .replace(GS.ose(edge))
2175                            .with(on_shell_energy.clone())
2176                            .replace(cut_energy(edge))
2177                            .with(on_shell_energy);
2178                        for spatial_index in 2..=3 {
2179                            expression = expression
2180                                .replace(GS.emr_mom(edge, GS.cind(spatial_index)))
2181                                .with(Atom::Zero)
2182                                .replace(GS.emr_vec(edge, GS.cind(spatial_index)))
2183                                .with(Atom::Zero);
2184                        }
2185                    }
2186                }
2187                Ok(expression)
2188            };
2189            let eta = rescale_expression(representative.to_atom(&[]))?;
2190            assert!(
2191                eta.replace(GS.rescale).with(Atom::one()).expand().is_zero(),
2192                "the comparison point must lie on the selected raised LU surface"
2193            );
2194            let value_and_t_derivative = |expression: Atom| -> Result<[Atom; 2]> {
2195                let series = rescale_expression(expression)?
2196                    .series(GS.rescale, Atom::one(), 1)
2197                    .map_err(|error| eyre!("failed to build raised-LU child t jet: {error}"))?;
2198                Ok([
2199                    series
2200                        .coefficient(Rational::from(0))
2201                        .expect("requested coefficient is within series precision"),
2202                    series
2203                        .coefficient(Rational::from(1))
2204                        .expect("requested coefficient is within series precision"),
2205                ])
2206            };
2207            let evaluate_arb = |expression: Atom| -> Result<Complex<F<ArbPrec>>> {
2208                let expression = expression
2209                    .replace(function!(GS.on_shell_energy, W_.a_, W_.prop_))
2210                    .with(Atom::var(W_.prop_).pow((1, 2)));
2211                let parameters = [Atom::var(GS.pi)];
2212                let rational: ExpressionEvaluator<SymComplex<Fraction<IntegerRing>>> = expression
2213                    .evaluator(&parameters)
2214                    .build()
2215                    .map_err(|error| eyre!("failed to build complete-child evaluator: {error}"))?;
2216                let mut arb: ExpressionEvaluator<Complex<F<ArbPrec>>> =
2217                    rational.map_coeff(&|coefficient| {
2218                        Complex::new(F::from(&coefficient.re), F::from(&coefficient.im))
2219                    });
2220                Ok(arb.evaluate_single(&[Complex::new(
2221                    F::<ArbPrec>::from_f64(0.0).pi(),
2222                    F::<ArbPrec>::from_f64(0.0),
2223                )]))
2224            };
2225            // One eighth of ArbPrec's requested precision permits substantial,
2226            // route-dependent loss while preserving a precision-scaled oracle.
2227            let tolerance = F(ArbPrec::default().epsilon()).sqrt().sqrt().sqrt();
2228            let eta_series = eta
2229                .series(GS.rescale, Atom::one(), 2)
2230                .map_err(|error| eyre!("failed to build raised-LU eta jet: {error}"))?;
2231            let eta_prime = evaluate_arb(
2232                eta_series
2233                    .coefficient(Rational::from(1))
2234                    .expect("requested coefficient is within series precision"),
2235            )?;
2236            let eta_second = evaluate_arb(
2237                eta_series
2238                    .coefficient(Rational::from(2))
2239                    .expect("requested coefficient is within series precision")
2240                    * Atom::num(2),
2241            )?;
2242            let combined_raised_residue =
2243                |route: &BTreeMap<CutCFFIndex, Atom>| -> Result<Complex<F<ArbPrec>>> {
2244                    let sum_order = |order| {
2245                        route
2246                            .iter()
2247                            .filter(|(index, _)| index.lu_cut_order == Some(order))
2248                            .map(|(_, expression)| expression)
2249                            .fold(Atom::Zero, |sum, expression| sum + expression)
2250                    };
2251                    let order_one = sum_order(1);
2252                    let order_two = sum_order(2);
2253                    assert!(
2254                        !order_one.is_zero() && !order_two.is_zero(),
2255                        "the fixed raised cut must retain nonzero order-one and order-two pieces"
2256                    );
2257                    let f_one = value_and_t_derivative(order_one)?;
2258                    let f_two = value_and_t_derivative(order_two)?;
2259                    Ok(evaluate_arb(f_one[0].clone())? / eta_prime.clone()
2260                        + evaluate_arb(f_two[1].clone())? / eta_prime.clone().pow(2)
2261                        - evaluate_arb(f_two[0].clone())? * eta_second.clone()
2262                            / eta_prime.clone().pow(3))
2263                };
2264
2265            // The individual LU orders are representatives, not observables: replacing
2266            // f2 by f2 + eta*h and f1 by f1 - h leaves the complete raised residue
2267            // f1/eta' + f2'/eta'^2 - f2*eta''/eta'^3 invariant at eta = 0.  Compare the
2268            // fixed-Cutkosky physical residue only after summing every piece of each order.
2269            let direct_residue = combined_raised_residue(&direct)?;
2270            let expanded_residue = combined_raised_residue(&expanded)?;
2271            let distance = (direct_residue.clone() - expanded_residue.clone())
2272                .norm()
2273                .re;
2274            let scale = direct_residue
2275                .clone()
2276                .norm()
2277                .re
2278                .max(expanded_residue.clone().norm().re);
2279            let relative_distance = if scale.is_zero() {
2280                distance
2281            } else {
2282                distance / scale
2283            };
2284            assert!(
2285                relative_distance <= tolerance,
2286                "{outer_label} complete raised-LU residue mismatch: direct={direct_residue:e}, expanded={expanded_residue:e}, relative delta={relative_distance:e}, tolerance={tolerance:e}"
2287            );
2288
2289            // Exercise the production boundary which the symbolic comparison below bypasses:
2290            // forest output is tensor-preprocessed, lowered into an EvaluatorStack, and fed the
2291            // same first-order HyperDual loop-momentum jet as raised-LU pass one.  Construct the
2292            // independent Arb oracle directly from the generation LMB, so this remains a
2293            // comparison of two evaluation routes rather than two uses of the compiled evaluator.
2294            let production_rational =
2295                |numerator, denominator| Atom::num(Rational::from((numerator, denominator)));
2296            let base_loop_atoms = [
2297                [
2298                    production_rational(31, 100),
2299                    production_rational(-47, 100),
2300                    production_rational(83, 100),
2301                ],
2302                [
2303                    production_rational(-19, 100),
2304                    production_rational(37, 100),
2305                    production_rational(61, 100),
2306                ],
2307            ];
2308            let external_atoms = [[
2309                production_rational(12, 5),
2310                production_rational(11, 100),
2311                production_rational(-8, 100),
2312                production_rational(5, 100),
2313            ]];
2314            assert_eq!(
2315                graph.loop_momentum_basis.loop_edges.len(),
2316                base_loop_atoms.len()
2317            );
2318            assert_eq!(
2319                graph.loop_momentum_basis.ext_edges.len(),
2320                external_atoms.len()
2321            );
2322
2323            let production_rescale = |mut expression: Atom| -> Result<Atom> {
2324                expression = expression
2325                    .replace(GS.dim)
2326                    .with(4)
2327                    // Scalarize the independent test oracle while retaining the same
2328                    // factorization as the production numerator and EvaluatorStack input.
2329                    .simplify_metrics()
2330                    .to_dots()
2331                    .normalize_dots()
2332                    .replace(GS.dim_epsilon)
2333                    .with(0)
2334                    .replace(GS.m_uv_expansion)
2335                    .with(production_rational(7, 5))
2336                    .replace(GS.m_uv_vacuum)
2337                    .with(production_rational(7, 5))
2338                    .replace(function!(GS.tree_denom_wrapper, W_.x_))
2339                    .with(W_.x_)
2340                    .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
2341                    .with(W_.d_)
2342                    .replace(function!(GS.ose, W_.mass_, W_.prop_))
2343                    .with(W_.prop_)
2344                    .replace(function!(GS.on_shell_energy, W_.a_, W_.prop_))
2345                    .with(Atom::var(W_.prop_).pow((1, 2)))
2346                    .expand_dots()?;
2347                expression = scalarize(expression)?;
2348                let t = Atom::var(GS.rescale);
2349                let loop_components = (0..3)
2350                    .map(|component| {
2351                        base_loop_atoms
2352                            .iter()
2353                            .map(|momentum| &momentum[component] * &t)
2354                            .collect::<Vec<_>>()
2355                    })
2356                    .collect::<Vec<_>>();
2357                let external_components = (0..3)
2358                    .map(|component| {
2359                        external_atoms
2360                            .iter()
2361                            .map(|momentum| momentum[component + 1].clone())
2362                            .collect::<Vec<_>>()
2363                    })
2364                    .collect::<Vec<_>>();
2365                let external_temporal = external_atoms
2366                    .iter()
2367                    .map(|momentum| momentum[0].clone())
2368                    .collect::<Vec<_>>();
2369                for edge in 0..graph.underlying.n_edges() {
2370                    let edge = EdgeIndex(edge);
2371                    let signature = &graph.loop_momentum_basis.edge_signatures[edge];
2372                    let spatial = (0..3)
2373                        .map(|component| {
2374                            signature
2375                                .try_compute_momentum(
2376                                    &loop_components[component],
2377                                    &external_components[component],
2378                                )
2379                                .unwrap_or(Atom::Zero)
2380                        })
2381                        .collect::<Vec<_>>();
2382                    let on_shell_energy = spatial
2383                        .iter()
2384                        .fold(Atom::one(), |norm, component| norm + component.pow(2))
2385                        .sqrt();
2386                    let temporal = if signature.internal.iter().any(|sign| sign.is_sign()) {
2387                        on_shell_energy.clone()
2388                    } else {
2389                        signature
2390                            .external
2391                            .try_apply(&external_temporal)
2392                            .unwrap_or(Atom::Zero)
2393                    };
2394                    expression = expression
2395                        .replace(GS.ose(edge))
2396                        .with(on_shell_energy.clone())
2397                        .replace(cut_energy(edge))
2398                        .with(on_shell_energy)
2399                        .replace(GS.emr_mom(edge, GS.cind(0)))
2400                        .with(temporal);
2401                    for (component, spatial_component) in spatial.iter().enumerate() {
2402                        expression = expression
2403                            .replace(GS.emr_mom(edge, GS.cind(component + 1)))
2404                            .with(spatial_component.clone())
2405                            .replace(GS.emr_vec(edge, GS.cind(component + 1)))
2406                            .with(spatial_component.clone());
2407                    }
2408                }
2409                Ok(expression)
2410            };
2411            let production_jet = |expression: Atom| -> Result<[Complex<F<ArbPrec>>; 2]> {
2412                let series = production_rescale(expression)?
2413                    .series(GS.rescale, Atom::one(), 1)
2414                    .map_err(|error| {
2415                        eyre!("failed to build production-boundary Arb jet: {error}")
2416                    })?;
2417                Ok([
2418                    evaluate_arb(
2419                        series
2420                            .coefficient(Rational::from(0))
2421                            .expect("requested coefficient is within series precision"),
2422                    )?,
2423                    evaluate_arb(
2424                        series
2425                            .coefficient(Rational::from(1))
2426                            .expect("requested coefficient is within series precision"),
2427                    )?,
2428                ])
2429            };
2430            let production_expressions = [&direct, &expanded]
2431                .into_iter()
2432                .map(|route| {
2433                    route
2434                        .iter()
2435                        .find(|(index, _)| index.lu_cut_order == Some(2))
2436                        .expect("the production-boundary route has an order-two LU sector")
2437                        .1
2438                        .clone()
2439                        .replace(GS.dim)
2440                        .with(4)
2441                        .replace(GS.dim_epsilon)
2442                        .with(0)
2443                        .replace(GS.m_uv_expansion)
2444                        .with(production_rational(7, 5))
2445                        .replace(GS.m_uv_vacuum)
2446                        .with(production_rational(7, 5))
2447                        .replace(function!(GS.tree_denom_wrapper, W_.x_))
2448                        .with(W_.x_)
2449                        .replace(GS.den(W_.a_, W_.b_, W_.c_, W_.d_))
2450                        .with(W_.d_)
2451                        .replace(function!(GS.ose, W_.mass_, W_.prop_))
2452                        .with(W_.prop_)
2453                        .replace(function!(GS.on_shell_energy, W_.a_, W_.prop_))
2454                        .with(Atom::var(W_.prop_).pow((1, 2)))
2455                })
2456                .collect::<Vec<_>>();
2457            let expected_production_jets = production_expressions
2458                .iter()
2459                .cloned()
2460                .map(production_jet)
2461                .collect::<Result<Vec<_>>>()?;
2462            let arb_rational = |numerator, denominator| {
2463                F::<ArbPrec>::from(&Rational::from((numerator, denominator)))
2464            };
2465            let loop_moms: LoopMomenta<F<ArbPrec>> = [
2466                ThreeMomentum::new(
2467                    arb_rational(31, 100),
2468                    arb_rational(-47, 100),
2469                    arb_rational(83, 100),
2470                ),
2471                ThreeMomentum::new(
2472                    arb_rational(-19, 100),
2473                    arb_rational(37, 100),
2474                    arb_rational(61, 100),
2475                ),
2476            ]
2477            .into_iter()
2478            .collect();
2479            let external_moms: ExternalFourMomenta<F<ArbPrec>> = [[
2480                arb_rational(12, 5),
2481                arb_rational(11, 100),
2482                arb_rational(-8, 100),
2483                arb_rational(5, 100),
2484            ]
2485            .into()]
2486            .into_iter()
2487            .collect();
2488            let dual_t =
2489                HyperDual::new(simple_n_deriv_shape(1)).variable(0, F::<ArbPrec>::from_f64(1.0));
2490            let dual_loop_moms = loop_moms.rescale_with_hyper_dual(&dual_t, None);
2491            let sample = MomentumSample {
2492                sample: BareMomentumSample {
2493                    loop_moms,
2494                    dual_loop_moms: Some(dual_loop_moms),
2495                    loop_mom_cache_id: 0,
2496                    loop_mom_base_cache_id: 0,
2497                    external_moms,
2498                    external_mom_cache_id: 0,
2499                    external_mom_base_cache_id: 0,
2500                    jacobian: arb_rational(1, 1),
2501                    orientation: None,
2502                    parameterization_branch: None,
2503                },
2504            };
2505            // The complete factorized child is deliberately deeper than a normal unit-test
2506            // thread stack. Use the same 32 MiB worker size as UV profiling rather than flattening
2507            // the numerator before it reaches the production evaluator.
2508            let actual_production_jets = std::thread::scope(|scope| -> Result<_> {
2509                let worker = std::thread::Builder::new()
2510                    .name("raised-lu-production-boundary".to_owned())
2511                    .stack_size(32 * 1024 * 1024)
2512                    .spawn_scoped(scope, || -> Result<_> {
2513                        let orientations = TiVec::<OrientationID, EdgeVec<Orientation>>::new();
2514                        let orientation_filter = SubSet::full(orientations.len());
2515                        let mut param_builder = graph.param_builder.clone();
2516                        param_builder.initialize_duals(2);
2517                        let (mut production_evaluator, _) =
2518                            EvaluatorStack::new_explicit_sum_with_timings(
2519                                &production_expressions,
2520                                &param_builder,
2521                                &[],
2522                                Some(simple_n_deriv_shape(1)),
2523                                &EvaluatorSettings::default(),
2524                            )?;
2525                        let input = <ArbPrec as GenericEvaluatorFloat>::get_parameters(
2526                            &mut param_builder,
2527                            (false, false),
2528                            &graph,
2529                            &sample,
2530                            &[],
2531                            &[],
2532                            None,
2533                            None,
2534                            None,
2535                        );
2536                        production_evaluator.evaluate(
2537                            input,
2538                            SingleOrAllOrientations::All {
2539                                all: &orientations,
2540                                filter: &orientation_filter,
2541                            },
2542                            &RuntimeSettings::default(),
2543                            &mut EvaluationMetaData::new_empty(),
2544                        )
2545                    })?;
2546                worker
2547                    .join()
2548                    .map_err(|_| eyre!("raised-LU production-boundary worker panicked"))?
2549            })?;
2550            for (route, actual, expected) in ["direct 3D", "projected local 4D"]
2551                .into_iter()
2552                .zip(actual_production_jets)
2553                .zip(&expected_production_jets)
2554                .map(|((route, actual), expected)| (route, actual, expected))
2555            {
2556                let DualOrNot::Dual(actual) = actual else {
2557                    panic!("{route} order-two production evaluator did not return a HyperDual jet")
2558                };
2559                let actual = extract_t_derivatives_complex(actual);
2560                for (component, actual, expected) in ["f2", "f2 prime"]
2561                    .into_iter()
2562                    .zip(actual)
2563                    .zip(expected)
2564                    .map(|((component, actual), expected)| (component, actual, expected))
2565                {
2566                    let distance = (actual.clone() - expected.clone()).norm().re;
2567                    let scale = actual.clone().norm().re.max(expected.clone().norm().re);
2568                    let relative_distance = if scale.is_zero() {
2569                        distance
2570                    } else {
2571                        distance / scale
2572                    };
2573                    assert!(
2574                        relative_distance <= tolerance,
2575                        "{route} production-boundary {component} mismatch: actual={actual:e}, expected={expected:e}, relative delta={relative_distance:e}, tolerance={tolerance:e}"
2576                    );
2577                }
2578            }
2579
2580            let consistent_eta = production_rescale(representative.to_atom(&[]))?;
2581            let consistent_eta_series = consistent_eta
2582                .series(GS.rescale, Atom::one(), 2)
2583                .map_err(|error| eyre!("failed to build production-boundary eta jet: {error}"))?;
2584            let consistent_eta_prime = evaluate_arb(
2585                consistent_eta_series
2586                    .coefficient(Rational::from(1))
2587                    .expect("requested coefficient is within series precision"),
2588            )?;
2589            let consistent_eta_second = evaluate_arb(
2590                consistent_eta_series
2591                    .coefficient(Rational::from(2))
2592                    .expect("requested coefficient is within series precision")
2593                    * Atom::num(2),
2594            )?;
2595            let mut pass_two_evaluator =
2596                build_derivative_structure(2, -1, &EvaluatorSettings::default());
2597            for (route, jet) in ["direct 3D", "projected local 4D"]
2598                .into_iter()
2599                .zip(&expected_production_jets)
2600            {
2601                let actual = <ArbPrec as GenericEvaluatorFloat>::get_evaluator_single(
2602                    &mut pass_two_evaluator,
2603                )(&[
2604                    jet[0].clone(),
2605                    jet[1].clone(),
2606                    consistent_eta_prime.clone(),
2607                    consistent_eta_second.clone(),
2608                ]);
2609                let expected = jet[1].clone() / consistent_eta_prime.clone().pow(2)
2610                    - jet[0].clone() * &consistent_eta_second / consistent_eta_prime.clone().pow(3);
2611                let distance = (actual.clone() - expected.clone()).norm().re;
2612                let scale = actual.clone().norm().re.max(expected.clone().norm().re);
2613                let relative_distance = if scale.is_zero() {
2614                    distance
2615                } else {
2616                    distance / scale
2617                };
2618                assert!(
2619                    relative_distance <= tolerance,
2620                    "{route} production pass-two mismatch: actual={actual:e}, expected={expected:e}, relative delta={relative_distance:e}, tolerance={tolerance:e}"
2621                );
2622            }
2623        }
2624        Ok(())
2625    }
2626}