Skip to main content

gammalooprs/cff/
esurface.rs

1use std::fmt::Display;
2
3use bincode_trait_derive::{Decode, Encode};
4use derive_more::{From, Into};
5use eyre::eyre;
6use itertools::Itertools;
7use linnet::half_edge::HedgeGraph;
8use linnet::half_edge::involution::{EdgeIndex, EdgeVec, Flow, HedgePair};
9use linnet::half_edge::subgraph::OrientedCut;
10use ref_ops::RefNeg;
11use rug::{Float, float::Round};
12use serde::{Deserialize, Serialize};
13
14use symbolica::atom::{Atom, AtomCore};
15use symbolica::domains::dual::HyperDual;
16use symbolica::domains::float::{FloatLike as SymFloatLike, Real};
17use symbolica::id::Replacement;
18use symbolica::{function, parse};
19use tracing::debug;
20use typed_index_collections::TiVec;
21
22use crate::cff::VertexSet;
23
24use crate::cff::expression::{
25    CFFExpression, OrientationID, RaisedEsurfaceDataView, RaisedEsurfaceGroupView,
26};
27pub use crate::cff::surface::EsurfaceID;
28use crate::graph::{Graph, GraphGroupPosition, LmbIndex, LoopMomentumBasis};
29use crate::{GammaLoopContext, define_index};
30
31use crate::integrands::process::sampling::maps::SamplingEvaluationError;
32use crate::integrands::process::{GenericEvaluator, ImplicitSurfaceRadialMap};
33use crate::momentum::sample::{
34    ExternalFourMomenta, ExternalIndex, ExternalThreeMomenta, LoopIndex, LoopMomenta, SubspaceData,
35};
36use crate::momentum::{Rotatable, Rotation, ThreeMomentum};
37use crate::processes::CrossSectionCut;
38use crate::utils::hyperdual_utils::new_constant;
39use crate::utils::newton_solver::{
40    NewtonIterationResult, RadialRootDiagnostics, RadialRootIdentity, SafeguardedNewtonError,
41};
42use crate::utils::{
43    ArbPrec, DEFAULT_ESURFACE_EXISTENCE_THRESHOLD, ESURFACE_SHIFT_THRESHOLD, F, FloatLike, GS,
44    Length, compute_loop_part, compute_loop_part_subspace, compute_shift_part,
45    compute_shift_part_subspace, compute_t_part_of_shift_part, cut_energy,
46    external_energy_atom_from_index, ose_atom_from_index,
47};
48use crate::uv::uv_graph::UVE;
49use color_eyre::Result;
50
51use super::generation::ShiftRewrite;
52
53/// Core esurface struct
54#[derive(Serialize, Deserialize, Debug, Clone, bincode::Encode, bincode::Decode)]
55pub struct Esurface {
56    pub energies: Vec<EdgeIndex>,
57    pub external_shift: ExternalShift,
58    pub vertex_set: VertexSet,
59    //#[bincode(with_serde)]
60    //pub subspace_graph: InternalSubGraph,
61}
62
63// Edge identity, radial velocity, constant spatial offset, and mass.
64type EsurfaceRayEnergy<T> = (EdgeIndex, ThreeMomentum<F<T>>, ThreeMomentum<F<T>>, F<T>);
65// Ephemeral endpoint tuples for the same affine energy fold, with no stored ray.
66type EnclosedRayEnergy = ([[Float; 2]; 3], [[Float; 2]; 3], [Float; 2]);
67
68/// One represented affine energy equation, retaining its ordered edge occurrences.
69/// This is native geometry, not a cross-precision or selected-host transport record.
70#[derive(Debug, Clone)]
71pub(crate) struct EsurfaceRay<T: FloatLike> {
72    // A Vec deliberately preserves repeated energy occurrences; an edge-keyed
73    // map would not. The tuple's fields are documented on EsurfaceRayEnergy.
74    energies: Vec<EsurfaceRayEnergy<T>>,
75    shift: F<T>,
76}
77
78impl EsurfaceRay<ArbPrec> {
79    /// Retain the canonical ordered equation while materializing its native
80    /// coefficients. Physical adoption still authenticates and checks the ray.
81    pub(crate) fn materialize<T: FloatLike>(&self) -> Result<EsurfaceRay<T>> {
82        let spatial = |p: &ThreeMomentum<F<ArbPrec>>| -> Result<ThreeMomentum<F<T>>> {
83            Ok(ThreeMomentum::new(
84                F::from_arb(&p.px.0)?,
85                F::from_arb(&p.py.0)?,
86                F::from_arb(&p.pz.0)?,
87            ))
88        };
89        Ok(EsurfaceRay {
90            energies: self
91                .energies
92                .iter()
93                .map(|(edge, velocity, offset, mass)| {
94                    Ok((
95                        *edge,
96                        spatial(velocity)?,
97                        spatial(offset)?,
98                        F::from_arb(&mass.0)?,
99                    ))
100                })
101                .collect::<Result<_>>()?,
102            shift: F::from_arb(&self.shift.0)?,
103        })
104    }
105}
106
107impl<T: FloatLike> EsurfaceRay<T> {
108    /// Embed the represented equation exactly, preserving each ordered energy
109    /// occurrence. No routing, coefficient reconstruction or root solve occurs.
110    pub(crate) fn to_arb_exact(&self) -> Result<EsurfaceRay<ArbPrec>> {
111        let spatial = |p: &ThreeMomentum<F<T>>| -> Result<ThreeMomentum<F<ArbPrec>>> {
112            Ok(ThreeMomentum::new(
113                p.px.to_arb_exact()?,
114                p.py.to_arb_exact()?,
115                p.pz.to_arb_exact()?,
116            ))
117        };
118        Ok(EsurfaceRay {
119            energies: self
120                .energies
121                .iter()
122                .map(|(edge, velocity, offset, mass)| {
123                    Ok((
124                        *edge,
125                        spatial(velocity)?,
126                        spatial(offset)?,
127                        mass.to_arb_exact()?,
128                    ))
129                })
130                .collect::<Result<_>>()?,
131            shift: self.shift.to_arb_exact()?,
132        })
133    }
134
135    const ENCLOSURE_PRECISION: u32 = 2048;
136
137    // These directed operations serve the represented and original-routed
138    // affine-energy checks; they do not introduce a second certificate engine.
139    fn enclosed_add(a: &[Float; 2], b: &[Float; 2]) -> [Float; 2] {
140        [
141            Float::with_val_round(Self::ENCLOSURE_PRECISION, &a[0] + &b[0], Round::Down).0,
142            Float::with_val_round(Self::ENCLOSURE_PRECISION, &a[1] + &b[1], Round::Up).0,
143        ]
144    }
145    fn enclosed_mul(a: &[Float; 2], b: &[Float; 2]) -> [Float; 2] {
146        let lower = a
147            .iter()
148            .flat_map(|x| {
149                b.iter().map(move |y| {
150                    Float::with_val_round(Self::ENCLOSURE_PRECISION, x * y, Round::Down).0
151                })
152            })
153            .reduce(|a, b| a.min(&b))
154            .unwrap();
155        let upper = a
156            .iter()
157            .flat_map(|x| {
158                b.iter().map(move |y| {
159                    Float::with_val_round(Self::ENCLOSURE_PRECISION, x * y, Round::Up).0
160                })
161            })
162            .reduce(|a, b| a.max(&b))
163            .unwrap();
164        [lower, upper]
165    }
166    fn enclosed_square(a: &[Float; 2]) -> [Float; 2] {
167        let upper = a[0].clone().abs().max(&a[1].clone().abs());
168        let lower = if a[0] <= 0 && a[1] >= 0 {
169            Float::with_val(Self::ENCLOSURE_PRECISION, 0)
170        } else {
171            a[0].clone().abs().min(&a[1].clone().abs())
172        };
173        [
174            Float::with_val_round(Self::ENCLOSURE_PRECISION, &lower * &lower, Round::Down).0,
175            Float::with_val_round(Self::ENCLOSURE_PRECISION, &upper * &upper, Round::Up).0,
176        ]
177    }
178    fn enclosed_divide(a: &[Float; 2], b: &[Float; 2]) -> [Float; 2] {
179        debug_assert!(b[0] > 0);
180        let reciprocal = [
181            Float::with_val_round(Self::ENCLOSURE_PRECISION, b[1].recip_ref(), Round::Down).0,
182            Float::with_val_round(Self::ENCLOSURE_PRECISION, b[0].recip_ref(), Round::Up).0,
183        ];
184        Self::enclosed_mul(a, &reciprocal)
185    }
186
187    fn enclosed_native(value: &F<T>) -> [Float; 2] {
188        let (lo, hi) = value.0.mpfr_enclosure(Self::ENCLOSURE_PRECISION);
189        [lo, hi]
190    }
191
192    fn enclosed_sum(terms: impl Iterator<Item = (i64, [Float; 2])>) -> [Float; 2] {
193        let zero = Float::with_val(Self::ENCLOSURE_PRECISION, 0);
194        terms.fold([zero.clone(), zero], |sum, (coefficient, value)| {
195            let coefficient = Float::with_val(Self::ENCLOSURE_PRECISION, coefficient);
196            Self::enclosed_add(
197                &sum,
198                &Self::enclosed_mul(&[coefficient.clone(), coefficient], &value),
199            )
200        })
201    }
202
203    fn evaluate_enclosed(
204        energies: impl Iterator<Item = EnclosedRayEnergy>,
205        shift: [Float; 2],
206        radius: &[Float; 2],
207        with_derivative: bool,
208    ) -> Result<([Float; 2], [Float; 2])> {
209        let zero = Float::with_val(Self::ENCLOSURE_PRECISION, 0);
210        let point = |x: &Float| [x.clone(), x.clone()];
211        let uncertain = |detail: &str| SamplingEvaluationError::UncertainGeometry {
212            detail: format!("LU host adoption: {detail}"),
213        };
214        let mut value = shift;
215        let mut slope = point(&zero);
216        for (velocity, offset, mass) in energies {
217            let mut norm = Self::enclosed_square(&mass);
218            let mut dot = point(&zero);
219            for (v, b) in velocity.into_iter().zip(offset) {
220                let q = Self::enclosed_add(&Self::enclosed_mul(&v, radius), &b);
221                norm = Self::enclosed_add(&norm, &Self::enclosed_square(&q));
222                dot = Self::enclosed_add(&dot, &Self::enclosed_mul(&v, &q));
223            }
224            if norm.iter().chain(&dot).any(|x| !x.is_finite()) {
225                return Err(SamplingEvaluationError::Unrepresentable {
226                    operation: "LU host certificate",
227                    detail: "nonfinite directed energy or numerator".into(),
228                }
229                .into());
230            }
231            let energy = [
232                Float::with_val_round(Self::ENCLOSURE_PRECISION, norm[0].sqrt_ref(), Round::Down).0,
233                Float::with_val_round(Self::ENCLOSURE_PRECISION, norm[1].sqrt_ref(), Round::Up).0,
234            ];
235            value = Self::enclosed_add(&value, &energy);
236            if with_derivative {
237                if energy[0] <= 0 {
238                    return Err(uncertain(
239                        "energy lower bound touches zero on the fixed candidate interval",
240                    )
241                    .into());
242                }
243                slope = Self::enclosed_add(&slope, &Self::enclosed_divide(&dot, &energy));
244            }
245        }
246        if value.iter().chain(&slope).any(|x| !x.is_finite()) {
247            return Err(SamplingEvaluationError::Unrepresentable {
248                operation: "LU host certificate",
249                detail: "nonfinite directed residual or derivative".into(),
250            }
251            .into());
252        }
253        Ok((value, slope))
254    }
255
256    /// Compare original isotropic normal equations on the actual completed
257    /// canonical/native LU points. Half the relative budget controls their
258    /// displacement; half controls the native point's host constraint defect.
259    /// This does not certify arbitrary multiplier functions or raised jets.
260    pub(crate) fn verify_normal_alignment(
261        canonical_normals: [[Float; 2]; 2],
262        native_normals: [[Float; 2]; 2],
263        native_host_residual: [Float; 2],
264        tolerance: f64,
265    ) -> Result<()> {
266        if !tolerance.is_finite() || tolerance <= 0.0 {
267            return Err(eyre!(
268                "hosted normal alignment requires a finite positive budget"
269            ));
270        }
271        let bounds = canonical_normals
272            .iter()
273            .chain(&native_normals)
274            .chain(std::iter::once(&native_host_residual));
275        for bound in bounds {
276            if bound.iter().any(|x| !x.is_finite()) {
277                return Err(SamplingEvaluationError::Unrepresentable {
278                    operation: "hosted normal alignment",
279                    detail: "nonfinite directed normal or host residual".into(),
280                }
281                .into());
282            }
283            if bound[0] > bound[1] {
284                return Err(eyre!("hosted normal alignment requires ordered bounds"));
285            }
286        }
287        let radius_squared = Self::enclosed_add(
288            &Self::enclosed_square(&canonical_normals[0]),
289            &Self::enclosed_square(&canonical_normals[1]),
290        );
291        let radius_lower = Float::with_val_round(
292            Self::ENCLOSURE_PRECISION,
293            radius_squared[0].sqrt_ref(),
294            Round::Down,
295        )
296        .0;
297        if radius_lower <= 0 {
298            return Err(SamplingEvaluationError::UncertainGeometry {
299                detail: "hosted normal alignment has no strictly positive canonical radius bound"
300                    .into(),
301            }
302            .into());
303        }
304        let difference: [_; 2] = std::array::from_fn(|axis| {
305            let canonical = &canonical_normals[axis];
306            Self::enclosed_add(
307                &native_normals[axis],
308                &[-canonical[1].clone(), -canonical[0].clone()],
309            )
310        });
311        let error_squared = Self::enclosed_add(
312            &Self::enclosed_square(&difference[0]),
313            &Self::enclosed_square(&difference[1]),
314        );
315        let error_upper = Float::with_val_round(
316            Self::ENCLOSURE_PRECISION,
317            error_squared[1].sqrt_ref(),
318            Round::Up,
319        )
320        .0;
321        let budget = Float::with_val(Self::ENCLOSURE_PRECISION, tolerance);
322        let half_budget =
323            Float::with_val_round(Self::ENCLOSURE_PRECISION, &budget / 2, Round::Down).0;
324        let allowed = Float::with_val_round(
325            Self::ENCLOSURE_PRECISION,
326            &half_budget * &radius_lower,
327            Round::Down,
328        )
329        .0;
330        let host_upper = native_host_residual[0]
331            .clone()
332            .abs()
333            .max(&native_host_residual[1].clone().abs());
334        if [&radius_lower, &error_upper, &host_upper, &allowed]
335            .iter()
336            .any(|x| !x.is_finite())
337        {
338            return Err(SamplingEvaluationError::Unrepresentable {
339                operation: "hosted normal alignment",
340                detail: "nonfinite directed comparison".into(),
341            }
342            .into());
343        }
344        if error_upper > allowed || host_upper > allowed {
345            return Err(SamplingEvaluationError::UncertainGeometry {
346                detail: "original normal displacement or completed host defect exceeds its half sampling budget".into(),
347            }.into());
348        }
349        Ok(())
350    }
351
352    #[inline]
353    pub(crate) fn evaluate(&self, radius: &F<T>) -> (F<T>, F<T>) {
354        let zero = radius.zero();
355        let (derivative, energy_sum) = self
356            .energies
357            .iter()
358            .map(|(_, velocity, offset, mass)| {
359                let momentum = velocity * radius + offset;
360                let energy = (momentum.norm_squared() + mass * mass).sqrt();
361                // At a massless endpoint E(r)=r|v| the radial right derivative is
362                // |v|, whereas the two-sided formula q.v/E would evaluate 0/0.
363                // Share this convention with sampling charts, using exact source
364                // zeros: a computed E=0 can instead be numerical underflow.
365                let derivative = if radius == &zero
366                    && mass == &zero
367                    && momentum.px == zero
368                    && momentum.py == zero
369                    && momentum.pz == zero
370                {
371                    velocity.norm_squared().sqrt()
372                } else {
373                    momentum * velocity / &energy
374                };
375                (derivative, energy)
376            })
377            .fold(
378                (zero.clone(), zero.clone()),
379                |(der_sum, en_sum), (der, en)| (der_sum + der, en_sum + en),
380            );
381        (energy_sum + &self.shift, derivative)
382    }
383
384    /// The sole LU seed/Newton policy, also used by native partial host rays.
385    pub(crate) fn solve_lu_cut(
386        &self,
387        e_cm: &F<T>,
388        diagnostics: &mut RadialRootDiagnostics,
389        identity: &RadialRootIdentity,
390    ) -> std::result::Result<NewtonIterationResult<T>, SafeguardedNewtonError<T>> {
391        let guess = Esurface::radius_guess_from_terms(
392            &self.shift,
393            self.energies
394                .iter()
395                .map(|(_, v, b, _)| (v.norm_squared(), v.clone() * b)),
396        );
397        crate::debug_tags!(#integration, #cut, #solver;
398            radial_root = %identity,
399            initial_guess = %guess,
400            residual_tolerance = %(e_cm * guess.epsilon() * guess.from_i64((4 * self.energies.len() + 1) as i64)),
401            "LU radial root setup"
402        );
403        // The energy equation is a sum of on-shell square roots.  Include a
404        // small forward-error budget for the momentum norm, square root, and
405        // accumulation of each edge instead of treating the whole sum as one
406        // elementary operation.  This remains a caller-specific tolerance;
407        // generic safeguarded-Newton users retain their original tolerance.
408        let residual_tolerance = guess.from_i64((4 * self.energies.len() + 1) as i64);
409        diagnostics.solve(
410            identity,
411            &guess.zero(),
412            &guess,
413            |t| self.evaluate(t),
414            &residual_tolerance,
415            2000,
416            64,
417            e_cm,
418        )
419    }
420
421    /// Certify this retained candidate against both represented rays. This is
422    /// a fixed directed check, not another root solve. The exponent bounds all
423    /// pulled-back host-null dimensions; it need not be the actual map dimension.
424    /// Higher raised jets and complete conditional densities are not enclosed.
425    pub(crate) fn verify_lu_candidate(
426        &self,
427        completed: &Self,
428        solution: &NewtonIterationResult<T>,
429        dimension_bound: usize,
430        tolerance: &F<T>,
431    ) -> Result<()> {
432        let precision = Self::ENCLOSURE_PRECISION;
433        let uncertain = |detail: &str| SamplingEvaluationError::UncertainGeometry {
434            detail: format!("LU host adoption: {detail}"),
435        };
436        if self
437            .energies
438            .iter()
439            .map(|entry| entry.0)
440            .ne(completed.energies.iter().map(|entry| entry.0))
441        {
442            return Err(eyre!(
443                "LU host adoption has incompatible ordered energy occurrences"
444            ));
445        }
446        for ray in [self, completed] {
447            if !ray.shift.0.is_finite()
448                || ray.energies.iter().any(|(_, v, b, m)| {
449                    [&v.px, &v.py, &v.pz, &b.px, &b.py, &b.pz, m]
450                        .iter()
451                        .any(|x| !x.0.is_finite())
452                })
453            {
454                return Err(SamplingEvaluationError::Unrepresentable {
455                    operation: "LU host adoption",
456                    detail: "nonfinite represented ray coefficients".into(),
457                }
458                .into());
459            }
460        }
461        let t = &solution.solution;
462        let derivative = &solution.derivative_at_solution;
463        if [t, derivative, tolerance].iter().any(|x| !x.0.is_finite()) {
464            return Err(SamplingEvaluationError::Unrepresentable {
465                operation: "LU host adoption",
466                detail: "nonfinite candidate, derivative or budget".into(),
467            }
468            .into());
469        }
470        if t <= &t.zero() || derivative <= &t.zero() || tolerance <= &t.zero() {
471            return Err(uncertain("candidate, derivative and budget must be positive").into());
472        }
473        let native = Self::enclosed_native;
474        let point = |x: &Float| [x.clone(), x.clone()];
475        let zero = Float::with_val(precision, 0);
476        let one = Float::with_val(precision, 1);
477        let evaluate = |ray: &Self, radius: &[Float; 2], with_derivative: bool| {
478            Self::evaluate_enclosed(
479                ray.energies.iter().map(|(_, velocity, offset, mass)| {
480                    (
481                        [&velocity.px, &velocity.py, &velocity.pz].map(native),
482                        [&offset.px, &offset.py, &offset.pz].map(native),
483                        native(mass),
484                    )
485                }),
486                native(&ray.shift),
487                radius,
488                with_derivative,
489            )
490        };
491        let delta = native(tolerance)[0]
492            .clone()
493            .min(&(Float::with_val(precision, 1) >> 2));
494        let divisor = Float::with_val(precision, 4 * (dimension_bound + 1));
495        let r = Float::with_val_round(precision, &delta / &divisor, Round::Down).0;
496        let t0 = native(t);
497        let lo_factor = Float::with_val_round(precision, &one - &r, Round::Down).0;
498        let hi_factor = Float::with_val_round(precision, &one + &r, Round::Up).0;
499        let interval = [
500            Float::with_val_round(precision, &t0[0] * &lo_factor, Round::Down).0,
501            Float::with_val_round(precision, &t0[1] * &hi_factor, Round::Up).0,
502        ];
503        if interval
504            .iter()
505            .chain(&t0)
506            .chain([&delta, &r])
507            .any(|x| !x.is_finite())
508        {
509            return Err(SamplingEvaluationError::Unrepresentable {
510                operation: "LU host certificate",
511                detail: "nonfinite directed candidate interval".into(),
512            }
513            .into());
514        }
515        if interval[0] <= 0 || interval[0] >= interval[1] {
516            return Err(uncertain("fixed candidate interval is not positive and distinct").into());
517        }
518        let allowed = [
519            Float::with_val_round(precision, &one - &delta, Round::Up).0,
520            Float::with_val_round(precision, &one + &delta, Round::Down).0,
521        ];
522        let residual_budget = Float::with_val_round(precision, &t0[0] * &r, Round::Down).0;
523        for ray in [self, completed] {
524            if evaluate(ray, &point(&zero), false)?.0[1] >= 0
525                || evaluate(ray, &point(&interval[0]), false)?.0[1] >= 0
526                || evaluate(ray, &point(&interval[1]), false)?.0[0] <= 0
527            {
528                return Err(uncertain("origin/interior/exterior signs are not certified on the fixed candidate interval").into());
529            }
530            let (_, slope) = evaluate(ray, &interval, true)?;
531            if slope[0] <= 0 {
532                return Err(uncertain(
533                    "radial derivative is not certified positive throughout the interval",
534                )
535                .into());
536            }
537            let residual = evaluate(ray, &t0, false)?.0;
538            let residual = residual[0].clone().abs().max(&residual[1].clone().abs());
539            if Float::with_val_round(precision, &residual / &slope[0], Round::Up).0
540                > residual_budget
541            {
542                return Err(uncertain(
543                    "candidate error exceeds its allocated relative root budget",
544                )
545                .into());
546            }
547            let ratio = Self::enclosed_divide(&slope, &native(derivative));
548            if ratio.iter().any(|x| !x.is_finite())
549                || ratio[0] < allowed[0]
550                || ratio[1] > allowed[1]
551            {
552                return Err(
553                    uncertain("first derivative variation exceeds the sampling budget").into(),
554                );
555            }
556        }
557        // Bound implemented/exact and its reciprocal explicitly, so the
558        // statement does not depend on the chosen relative-error denominator.
559        for ratio in [
560            Self::enclosed_divide(&interval, &t0),
561            Self::enclosed_divide(&t0, &interval),
562        ] {
563            let mut determinant_ratio = point(&one);
564            for _ in 0..dimension_bound {
565                determinant_ratio = Self::enclosed_mul(&determinant_ratio, &ratio);
566            }
567            if determinant_ratio.iter().any(|x| !x.is_finite())
568                || determinant_ratio[0] < allowed[0]
569                || determinant_ratio[1] > allowed[1]
570            {
571                return Err(uncertain(
572                    "worst-case LU inverse-volume variation exceeds the sampling budget",
573                )
574                .into());
575            }
576        }
577        Ok(())
578    }
579
580    /// Original eta jet at the same represented coefficients used by the root.
581    /// The caller retains the existing dual shape and factorial convention.
582    #[inline]
583    pub(crate) fn evaluate_dual(&self, radius: &HyperDual<F<T>>) -> HyperDual<F<T>> {
584        let energy_sum = self
585            .energies
586            .iter()
587            .map(|(_, velocity, offset, mass)| {
588                let momentum = velocity.map_ref(&|v| new_constant(radius, v) * radius)
589                    + offset.map_ref(&|b| new_constant(radius, b));
590                (momentum.norm_squared() + mass * mass).sqrt()
591            })
592            .reduce(|sum, energy| sum + energy)
593            .unwrap_or_else(|| radius.zero());
594        energy_sum + new_constant(radius, &self.shift)
595    }
596}
597
598impl<T: FloatLike> Rotatable for EsurfaceRay<T> {
599    fn rotate(&self, rotation: &Rotation) -> Self {
600        Self {
601            energies: self
602                .energies
603                .iter()
604                .map(|(edge, v, b, mass)| {
605                    (*edge, v.rotate(rotation), b.rotate(rotation), mass.clone())
606                })
607                .collect(),
608            shift: self.shift.clone(),
609        }
610    }
611}
612
613#[derive(Debug, Clone, Copy, PartialEq, Eq)]
614pub(crate) enum NonExistingEsurfaceReason {
615    NoExternalShift,
616    ShiftNotNegative,
617    NoRadialDependence,
618    NoRealZero,
619}
620
621#[derive(Debug, Clone)]
622pub(crate) enum EsurfaceExistence<T: FloatLike> {
623    NonExisting {
624        normalized_margin: Option<F<T>>,
625        reason: NonExistingEsurfaceReason,
626    },
627    Pinched {
628        normalized_margin: F<T>,
629    },
630    Existing {
631        normalized_margin: F<T>,
632    },
633}
634
635/// Pointwise existence classification exposed without the internal diagnostic margin.
636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
637pub enum EsurfaceExistenceStatus {
638    NonExisting,
639    Pinched,
640    Existing,
641}
642
643impl<T: FloatLike> EsurfaceExistence<T> {
644    fn status(&self) -> EsurfaceExistenceStatus {
645        match self {
646            Self::NonExisting { .. } => EsurfaceExistenceStatus::NonExisting,
647            Self::Pinched { .. } => EsurfaceExistenceStatus::Pinched,
648            Self::Existing { .. } => EsurfaceExistenceStatus::Existing,
649        }
650    }
651
652    pub(crate) fn is_existing(&self) -> bool {
653        matches!(self, Self::Existing { .. })
654    }
655
656    pub(crate) fn normalized_margin(&self) -> Option<&F<T>> {
657        match self {
658            Self::NonExisting {
659                normalized_margin, ..
660            } => normalized_margin.as_ref(),
661            Self::Pinched { normalized_margin } | Self::Existing { normalized_margin } => {
662                Some(normalized_margin)
663            }
664        }
665    }
666
667    pub(crate) fn label(&self) -> &'static str {
668        match self {
669            Self::NonExisting { .. } => "non_existing",
670            Self::Pinched { .. } => "pinched",
671            Self::Existing { .. } => "existing",
672        }
673    }
674
675    pub(crate) fn non_existing_reason(&self) -> Option<NonExistingEsurfaceReason> {
676        match self {
677            Self::NonExisting { reason, .. } => Some(*reason),
678            Self::Pinched { .. } | Self::Existing { .. } => None,
679        }
680    }
681}
682
683pub(crate) fn esurface_value_is_strictly_inside<T: FloatLike>(value: &F<T>, e_cm: &F<T>) -> bool {
684    let interior_tolerance = value.epsilon() * value.from_i64(8) * e_cm;
685    !value.is_nan() && !value.is_infinite() && value < &(-interior_tolerance)
686}
687
688impl PartialEq for Esurface {
689    fn eq(&self, other: &Self) -> bool {
690        self.energies == other.energies && self.external_shift == other.external_shift
691    }
692}
693
694impl Eq for Esurface {}
695
696impl Esurface {
697    /// Match two energy sums on one active spatial momentum. Their shared
698    /// energy is identified by a single sign of the full internal routing,
699    /// exact external spatial shift and mass expression, never by edge identity
700    /// or a rounded-momentum tolerance.
701    /// The returned common route has active coefficient +1: the kernel uses
702    /// x=L+c0, so the existing affine embedding must return L=x-c0.
703    // Physical binders must transport one prerequisite-only disk policy
704    // through native precision retries alongside this reconstructed geometry.
705    #[allow(clippy::too_many_arguments)]
706    pub(crate) fn sampling_joint_geometry_in_subspace<T: FloatLike>(
707        &self,
708        other: &Self,
709        subspace: &SubspaceData,
710        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
711        graph: &Graph,
712        masses: &EdgeVec<F<T>>,
713        external_momenta: &ExternalFourMomenta<F<T>>,
714        complement: &[LoopIndex],
715    ) -> Result<(
716        crate::integrands::process::sampling::joint::SharedEnergyJointGeometryEvaluator<T>,
717        crate::momentum::signature::LoopExtSignature,
718    )> {
719        use crate::integrands::process::{
720            SharedEnergyJointGeometry, sampling::maps::SamplingEvaluationError,
721        };
722        use crate::momentum::{SignOrZero, signature::LoopExtSignature};
723        use std::sync::Arc;
724
725        let lmb = subspace.get_lmb(all_lmbs);
726        let active = subspace.iter_lmb_indices().collect_vec();
727        let [active] = active.as_slice() else {
728            return Err(eyre!(
729                "joint energy sampling requires exactly one active loop cycle"
730            ));
731        };
732        let active = *active;
733        let mut covered = complement.iter().copied().chain([active]).collect_vec();
734        covered.sort();
735        if covered.iter().any(|index| index.0 >= lmb.loop_edges.len())
736            || covered.windows(2).any(|pair| pair[0] == pair[1])
737        {
738            return Err(eyre!(
739                "joint energy sampling has repeated or invalid active/prior loop indices"
740            ));
741        }
742        if external_momenta.is_empty() || external_momenta.len() != lmb.ext_edges.len() {
743            return Err(eyre!(
744                "joint energy sampling requires all {} external ports, received {}",
745                lmb.ext_edges.len(),
746                external_momenta.len()
747            ));
748        }
749        let surfaces = [self, other];
750        for &surface in &surfaces {
751            for &edge in &surface.energies {
752                let signature = &lmb.edge_signatures[edge];
753                for index in (0..lmb.loop_edges.len())
754                    .map(LoopIndex)
755                    .filter(|index| !covered.contains(index))
756                {
757                    if signature.internal[index] != SignOrZero::Zero {
758                        return Err(eyre!(
759                            "joint energy surface {:?} depends on unsampled parent edge {}",
760                            surface.energies,
761                            lmb.loop_edges[index]
762                        ));
763                    }
764                }
765                if !masses[edge].0.is_finite() {
766                    return Err(SamplingEvaluationError::Unrepresentable {
767                        operation: "joint energy mass",
768                        detail: format!("non-finite mass on edge {edge}"),
769                    }
770                    .into());
771                }
772                if masses[edge] < masses[edge].zero() {
773                    return Err(eyre!(
774                        "joint energy edge {edge} requires a nonnegative mass"
775                    ));
776                }
777            }
778        }
779        let varying = surfaces.map(|surface| {
780            surface
781                .energies
782                .iter()
783                .copied()
784                .filter(|edge| lmb.edge_signatures[*edge].internal[active] != SignOrZero::Zero)
785                .sorted_by_key(|edge| edge.0)
786                .collect_vec()
787        });
788        if varying.iter().any(|edges| edges.len() != 2) {
789            return Err(eyre!(
790                "joint energy sampling requires exactly two varying energy occurrences per equation, got {:?}",
791                varying
792            ));
793        }
794        for (surface, varying) in surfaces.iter().zip(&varying) {
795            let contained = subspace.contains(&surface.energies, graph).collect_vec();
796            if varying.iter().any(|edge| !contained.contains(edge)) {
797                return Err(eyre!(
798                    "joint varying energy routing is inconsistent with its selected cycle subgraph"
799                ));
800            }
801        }
802        // Keep the original global equations. Replacing this shift by a
803        // cut-eliminated identity would move the target by a finite LU residual.
804        let shifts =
805            surfaces.map(|surface| surface.compute_shift_part_from_momenta(external_momenta, lmb));
806        let externals: ExternalThreeMomenta<F<T>> =
807            external_momenta.iter().map(|p| p.spatial.clone()).collect();
808        if shifts
809            .iter()
810            .chain(externals.iter().flat_map(|p| [&p.px, &p.py, &p.pz]))
811            .any(|value| !value.0.is_finite())
812        {
813            return Err(SamplingEvaluationError::Unrepresentable {
814                operation: "joint external data",
815                detail: "non-finite external spatial vector or original energy shift".into(),
816            }
817            .into());
818        }
819        let mut shared = Vec::new();
820        for (left, &a) in varying[0].iter().enumerate() {
821            for (right, &b) in varying[1].iter().enumerate() {
822                if graph[a].mass_atom() == graph[b].mass_atom()
823                    && lmb.edge_signatures[a]
824                        .spatial_equality_up_to_sign(&lmb.edge_signatures[b], &externals)?
825                {
826                    shared.push((left, right));
827                }
828            }
829        }
830        let [(left, right)] = shared.as_slice() else {
831            return Err(eyre!(
832                "joint energy sampling requires exactly one common full signed routing in the supplied spatial frame and mass expression; found {} candidates for {:?}",
833                shared.len(),
834                varying
835            ));
836        };
837        let edges = [
838            varying[0][*left],
839            varying[0][1 - *left],
840            varying[1][1 - *right],
841        ];
842        if masses[edges[0]] != masses[varying[1][*right]] {
843            return Err(eyre!(
844                "joint shared mass expression has inconsistent native values on edges {} and {}",
845                edges[0],
846                varying[1][*right]
847            ));
848        }
849        let canonical = |edge| {
850            let signature = &lmb.edge_signatures[edge];
851            if signature.internal[active] == SignOrZero::Minus {
852                LoopExtSignature {
853                    internal: signature.internal.iter().map(|sign| -*sign).collect(),
854                    external: signature.external.iter().map(|sign| -*sign).collect(),
855                }
856            } else {
857                signature.clone()
858            }
859        };
860        let routes = edges.map(canonical);
861        let common = routes[0].clone();
862        let fixed = surfaces.map(|surface| {
863            surface
864                .energies
865                .iter()
866                .copied()
867                .filter(|edge| lmb.edge_signatures[*edge].internal[active] == SignOrZero::Zero)
868                .collect_vec()
869        });
870        let lmb = lmb.clone();
871        let masses = masses.clone();
872        let complement = complement.to_vec();
873        let geometry = Arc::new(move |context: &[T]| {
874            if context.len() != 3 * complement.len() {
875                return Err(eyre!(
876                    "joint energy context has {} components, expected {}",
877                    context.len(),
878                    3 * complement.len()
879                ));
880            }
881            if context.iter().any(|x| !x.is_finite()) {
882                return Err(SamplingEvaluationError::Unrepresentable {
883                    operation: "joint energy context",
884                    detail: "non-finite declared prerequisite".into(),
885                }
886                .into());
887            }
888            let zero = masses[edges[0]].zero();
889            let mut loops = LoopMomenta::from_iter(
890                (0..lmb.loop_edges.len())
891                    .map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
892            );
893            for (&index, point) in complement.iter().zip(context.as_chunks::<3>().0.iter()) {
894                loops[index] = ThreeMomentum::new(
895                    F(point[0].clone()),
896                    F(point[1].clone()),
897                    F(point[2].clone()),
898                );
899            }
900            let offsets: [ThreeMomentum<F<T>>; 3] = routes
901                .each_ref()
902                .map(|route| route.compute_momentum(&loops, &externals));
903            let differences = [&offsets[1] - &offsets[0], &offsets[2] - &offsets[0]];
904            let sums = fixed.iter().enumerate().map(|(index, fixed)| {
905                let fixed_sum = fixed.iter().try_fold(zero.clone(), |sum, &edge| -> Result<F<T>> {
906                    let momentum: ThreeMomentum<F<T>> = lmb.edge_signatures[edge].compute_momentum(&loops, &externals);
907                    let energy = (momentum.norm_squared()+masses[edge].square()).sqrt();
908                    if energy == zero && (masses[edge] != zero || momentum.px != zero || momentum.py != zero || momentum.pz != zero) {
909                        return Err(SamplingEvaluationError::Unrepresentable { operation: "joint fixed energy", detail: format!("nonzero mass or momentum on edge {edge} produced zero energy") }.into());
910                    }
911                    Ok(sum+energy)
912                })?;
913                Ok((-(&shifts[index]+fixed_sum)).0)
914            }).collect::<Result<Vec<T>>>()?;
915            let energy_sums = [sums[0].clone(), sums[1].clone()];
916            let geometry = SharedEnergyJointGeometry {
917                shifts: differences.map(|v| [v.px.0, v.py.0, v.pz.0]),
918                masses: edges.map(|edge| masses[edge].0.clone()),
919                energy_sums,
920            };
921            if geometry
922                .shifts
923                .iter()
924                .flatten()
925                .chain(&geometry.energy_sums)
926                .any(|x| !x.is_finite())
927            {
928                return Err(SamplingEvaluationError::Unrepresentable {
929                    operation: "joint prepared energies",
930                    detail: "non-finite routed offset or fixed energy sum".into(),
931                }
932                .into());
933            }
934            Ok(geometry)
935        });
936        Ok((geometry, common))
937    }
938
939    /// Compile an exact radial chart around this graph-routed energy surface.
940    /// The callback retains the complete parent frame, masses and external
941    /// data in the evaluation precision; its ray root is the same cut equation
942    /// used by LU, without promoting already rounded lower-precision vectors.
943    pub(crate) fn sampling_radial_map<T: FloatLike>(
944        &self,
945        lmb: &LoopMomentumBasis,
946        masses: &EdgeVec<F<T>>,
947        external_momenta: &ExternalFourMomenta<F<T>>,
948        beta: f64,
949        power: f64,
950    ) -> Result<ImplicitSurfaceRadialMap<T>> {
951        let dimension = 3 * lmb.loop_edges.len();
952        let surface = self.clone();
953        let lmb = lmb.clone();
954        let masses = masses.clone();
955        let external_momenta = external_momenta.clone();
956        let zero = F::<T>::from_f64(0.0);
957        let center = LoopMomenta::from_iter(
958            (0..dimension / 3)
959                .map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
960        );
961        let evaluator = std::sync::Arc::new(move |direction: &[T], radius: T| {
962            let radius = F(radius);
963            let unit_loops =
964                LoopMomenta::from_iter(direction.as_chunks::<3>().0.iter().map(|components| {
965                    ThreeMomentum::new(
966                        F(components[0].clone()),
967                        F(components[1].clone()),
968                        F(components[2].clone()),
969                    )
970                }));
971            let (value, derivative) = surface.compute_self_and_r_derivative(
972                &radius,
973                &unit_loops,
974                &center,
975                &external_momenta,
976                &masses,
977                &lmb,
978            );
979            Ok((value.0, derivative.0))
980        });
981        ImplicitSurfaceRadialMap::new(dimension, vec![zero.0; dimension], beta, power, evaluator)
982    }
983
984    /// Bind a routed energy surface on active coordinates, conditional on an
985    /// already sampled complement in the same complete parent LMB. Later
986    /// coordinates may be omitted only when every energy row vanishes on them.
987    /// Preparation
988    /// never consumes active cube coordinates, so its derivatives occupy only
989    /// the off-diagonal block of an ordered composition's Jacobian. Active
990    /// outputs follow `subspace.iter_lmb_indices()` (parent-index order), while
991    /// complement inputs follow the explicit `complement` order. A compiler
992    /// must permute these blocks before attaching user-ordered edge metadata.
993    #[allow(clippy::too_many_arguments)]
994    pub(crate) fn sampling_radial_map_in_subspace<T: FloatLike>(
995        &self,
996        subspace: &SubspaceData,
997        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
998        graph: &Graph,
999        masses: &EdgeVec<F<T>>,
1000        external_momenta: &ExternalFourMomenta<F<T>>,
1001        complement: &[LoopIndex],
1002        settings: &crate::settings::RuntimeSettings,
1003        beta: f64,
1004        power: f64,
1005    ) -> Result<ImplicitSurfaceRadialMap<T>> {
1006        use crate::integrands::process::PreparedSurfaceStatus;
1007        use crate::integrands::process::sampling::maps::SamplingEvaluationError;
1008        use crate::momentum::SignOrZero;
1009        use crate::subtraction::overlap_subspace::{OverlapInput, find_center};
1010        use std::sync::Arc;
1011        use three_dimensional_reps::utils::rank_i64;
1012
1013        if !settings.kinematics.e_cm.is_finite() || settings.kinematics.e_cm <= 0.0 {
1014            return Err(eyre!("sampling fiber requires finite positive e_cm"));
1015        }
1016        let lmb = subspace.get_lmb(all_lmbs);
1017        let active = subspace.iter_lmb_indices().collect_vec();
1018        let mut covered = active.iter().chain(complement).copied().collect_vec();
1019        covered.sort();
1020        if active.is_empty()
1021            || covered.windows(2).any(|pair| pair[0] == pair[1])
1022            || covered.iter().any(|index| index.0 >= lmb.loop_edges.len())
1023            || external_momenta.len() != lmb.ext_edges.len()
1024            || external_momenta.is_empty()
1025        {
1026            return Err(eyre!(
1027                "sampling fiber requires disjoint active/complement slots in its parent frame and complete external momenta: active {active:?}, complement {complement:?}, parent {:?}, external count {} (expected {})",
1028                lmb.loop_edges,
1029                external_momenta.len(),
1030                lmb.ext_edges.len(),
1031            ));
1032        }
1033        // Later blocks may be absent from this prefix only when their exact
1034        // routed rows vanish. Their zero placeholders then represent proven
1035        // spectators, never unknown active coordinates of the energy equation.
1036        for index in (0..lmb.loop_edges.len())
1037            .map(LoopIndex)
1038            .filter(|index| !covered.contains(index))
1039        {
1040            if self
1041                .energies
1042                .iter()
1043                .any(|edge| lmb.edge_signatures[*edge].internal[index] != SignOrZero::Zero)
1044            {
1045                return Err(eyre!(
1046                    "sampling fiber depends on unsampled parent edge {}",
1047                    lmb.loop_edges[index]
1048                ));
1049            }
1050        }
1051        for momentum in external_momenta {
1052            if !momentum.temporal.value.0.is_finite()
1053                || [
1054                    &momentum.spatial.px,
1055                    &momentum.spatial.py,
1056                    &momentum.spatial.pz,
1057                ]
1058                .iter()
1059                .any(|value| !value.0.is_finite())
1060            {
1061                return Err(eyre!("sampling fiber external momenta must be finite"));
1062            }
1063        }
1064        let rows = self
1065            .energies
1066            .iter()
1067            .map(|edge| {
1068                active
1069                    .iter()
1070                    .map(|&index| match lmb.edge_signatures[*edge].internal[index] {
1071                        SignOrZero::Minus => -1_i64,
1072                        SignOrZero::Zero => 0,
1073                        SignOrZero::Plus => 1,
1074                    })
1075                    .collect_vec()
1076            })
1077            .collect_vec();
1078        let rank = rank_i64(&rows);
1079        if rank != active.len() {
1080            return Err(eyre!(
1081                "sampling fiber {:?} has active routing rank {rank}, expected {}; spectator directions require a complement block",
1082                self.energies,
1083                active.len()
1084            ));
1085        }
1086        let varying = self
1087            .energies
1088            .iter()
1089            .zip(&rows)
1090            .filter_map(|(&edge, row)| row.iter().any(|&sign| sign != 0).then_some(edge))
1091            .collect_vec();
1092        let subspace_energies = subspace.contains(&self.energies, graph).collect_vec();
1093        if varying.iter().any(|edge| !subspace_energies.contains(edge)) {
1094            return Err(eyre!(
1095                "sampling fiber routing is inconsistent with its selected cycle subgraph"
1096            ));
1097        }
1098        for &edge in &self.energies {
1099            let mass = &masses[edge];
1100            if !mass.0.is_finite() || mass < &mass.zero() {
1101                return Err(eyre!(
1102                    "sampling fiber edge {edge} requires a finite nonnegative mass"
1103                ));
1104            }
1105        }
1106        let surface = Arc::new(self.clone());
1107        let lmbs = Arc::new(all_lmbs.clone());
1108        let graph = Arc::new(graph.clone());
1109        let subspace = Arc::new(subspace.clone());
1110        let masses = Arc::new(masses.clone());
1111        let externals = Arc::new(external_momenta.clone());
1112        let settings = Arc::new(settings.clone());
1113        let complement = Arc::new(complement.to_vec());
1114        let active = Arc::new(active);
1115        let zero = external_momenta[ExternalIndex(0)].temporal.value.zero();
1116        let dimension = active.len() * 3;
1117        let freeze_context = complement.is_empty();
1118        let n_loops = lmb.loop_edges.len();
1119        // One shared routed-ray evaluator serves both full and conditional
1120        // charts. Complement entries have zero radial velocity.
1121        let evaluator = {
1122            let (surface, lmbs, subspace, masses, externals, complement, active) = (
1123                surface.clone(),
1124                lmbs.clone(),
1125                subspace.clone(),
1126                masses.clone(),
1127                externals.clone(),
1128                complement.clone(),
1129                active.clone(),
1130            );
1131            Arc::new(
1132                move |direction: &[T], radius: T, center: &[T], context: &[T]| {
1133                    let zero = F(radius.zero());
1134                    let mut loops = LoopMomenta::from_iter(
1135                        (0..n_loops)
1136                            .map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
1137                    );
1138                    let mut velocity = loops.clone();
1139                    for (&index, components) in
1140                        complement.iter().zip(context.as_chunks::<3>().0.iter())
1141                    {
1142                        loops[index] = ThreeMomentum::new(
1143                            F(components[0].clone()),
1144                            F(components[1].clone()),
1145                            F(components[2].clone()),
1146                        );
1147                    }
1148                    for ((&index, components), unit) in active
1149                        .iter()
1150                        .zip(center.as_chunks::<3>().0.iter())
1151                        .zip(direction.as_chunks::<3>().0.iter())
1152                    {
1153                        loops[index] = ThreeMomentum::new(
1154                            F(components[0].clone()),
1155                            F(components[1].clone()),
1156                            F(components[2].clone()),
1157                        );
1158                        velocity[index] = ThreeMomentum::new(
1159                            F(unit[0].clone()),
1160                            F(unit[1].clone()),
1161                            F(unit[2].clone()),
1162                        );
1163                    }
1164                    let (value, derivative) = surface.compute_self_and_r_derivative(
1165                        &F(radius),
1166                        &velocity,
1167                        &loops,
1168                        &externals,
1169                        &masses,
1170                        subspace.get_lmb(&lmbs),
1171                    );
1172                    Ok((value.0, derivative.0))
1173                },
1174            )
1175        };
1176        let preparer = Arc::new(move |context: &[T]| {
1177            let zero = externals[ExternalIndex(0)].temporal.value.zero();
1178            if context.len() != complement.len() * 3
1179                || context.iter().any(|value| !value.is_finite())
1180            {
1181                return Err(eyre!(
1182                    "sampling fiber expected {} finite complement components, got {}",
1183                    complement.len() * 3,
1184                    context.len()
1185                ));
1186            }
1187            let lmb = subspace.get_lmb(&lmbs);
1188            let spatial: ExternalThreeMomenta<F<T>> =
1189                externals.iter().map(|p| p.spatial.clone()).collect();
1190            let mut center = LoopMomenta::from_iter(
1191                (0..n_loops).map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
1192            );
1193            for (&index, components) in complement.iter().zip(context.as_chunks::<3>().0.iter()) {
1194                center[index] = ThreeMomentum::new(
1195                    F(components[0].clone()),
1196                    F(components[1].clone()),
1197                    F(components[2].clone()),
1198                );
1199            }
1200            let zero_velocity = LoopMomenta::from_iter(
1201                (0..n_loops).map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
1202            );
1203            let mut constant = surface.compute_shift_part_from_momenta(&externals, lmb);
1204            // Include the inputs to routed differences, not just their small
1205            // residuals. Large common spatial shifts may otherwise round an
1206            // actually nonzero separation to zero before minimum testing.
1207            let mut scale = center
1208                .iter()
1209                .flat_map(|p| [&p.px, &p.py, &p.pz])
1210                .chain(externals.iter().flat_map(|p| {
1211                    [
1212                        &p.temporal.value,
1213                        &p.spatial.px,
1214                        &p.spatial.py,
1215                        &p.spatial.pz,
1216                    ]
1217                }))
1218                .fold(constant.abs(), |sum, component| sum + component.abs());
1219            for &edge in &surface.energies {
1220                if !varying.contains(&edge) {
1221                    let momentum = lmb.edge_signatures[edge].compute_momentum(&center, &spatial);
1222                    let energy = (momentum.norm_squared() + masses[edge].square()).sqrt();
1223                    constant += &energy;
1224                    scale += energy;
1225                }
1226            }
1227            let minimum = if active.len() == 1 && varying.len() == 2 {
1228                // For q_i = s_i k + b_i, s_i = +/-1, the exact convex minimum
1229                // is sqrt((b1-s1*s2*b2)^2 + (m1+m2)^2). With zero total mass
1230                // the minimizing set is a segment; its midpoint is a valid
1231                // proposal center, not a claim that the pinch is isolated.
1232                let [first, second] = [varying[0], varying[1]];
1233                let index = active[0];
1234                let sign = |edge| match lmb.edge_signatures[edge].internal[index] {
1235                    SignOrZero::Plus => zero.one(),
1236                    SignOrZero::Minus => -zero.one(),
1237                    SignOrZero::Zero => unreachable!("two varying rank-one energies"),
1238                };
1239                let first_shift = lmb.edge_signatures[first].compute_momentum(&center, &spatial);
1240                let second_shift = lmb.edge_signatures[second].compute_momentum(&center, &spatial);
1241                let separation = &first_shift - &(second_shift * (sign(first) * sign(second)));
1242                let total_mass = &masses[first] + &masses[second];
1243                let fraction = if total_mass > zero {
1244                    &masses[first] / &total_mass
1245                } else {
1246                    zero.one() / zero.from_i64(2)
1247                };
1248                center[index] = (&separation * fraction - first_shift) * sign(first);
1249                let energy = (separation.norm_squared() + total_mass.square()).sqrt();
1250                scale += &energy;
1251                Some(energy + &constant)
1252            } else {
1253                None
1254            };
1255            let tolerance = zero.epsilon()
1256                * zero.from_usize(64 * (surface.energies.len() + 1))
1257                * scale.max(zero.one());
1258            let normalized = |value: &F<T>| -> Result<T> {
1259                let margin = -value / F::<T>::from_f64(settings.kinematics.e_cm);
1260                if !margin.0.is_finite() {
1261                    return Err(SamplingEvaluationError::Unrepresentable {
1262                        operation: "normalized sampling surface margin",
1263                        detail: "finite surface value and energy scale produce a nonfinite margin"
1264                            .to_owned(),
1265                    }
1266                    .into());
1267                }
1268                Ok(margin.0)
1269            };
1270            let status = if let Some(minimum) = minimum {
1271                if !minimum.0.is_finite() || minimum.abs() <= tolerance {
1272                    return Err(SamplingEvaluationError::UncertainGeometry { detail: format!("two-energy fiber minimum is not sign-certified: minimum={minimum}, tolerance={tolerance}, edges={:?}", surface.energies) }.into());
1273                }
1274                if minimum > zero {
1275                    PreparedSurfaceStatus::absent(format!(
1276                        "positive exact two-energy minimum {minimum}"
1277                    ))?
1278                } else {
1279                    PreparedSurfaceStatus::existing(Some(normalized(&minimum)?))?
1280                }
1281            } else {
1282                // The sum of masses is a necessary lower bound for every
1283                // routed topology. A positive bound certifies absence; a
1284                // failed approximate optimizer by itself never does.
1285                let lower = varying
1286                    .iter()
1287                    .fold(constant, |sum, &edge| sum + &masses[edge]);
1288                if lower > tolerance {
1289                    PreparedSurfaceStatus::absent(format!("positive energy lower bound {lower}"))?
1290                } else {
1291                    let value = surface
1292                        .compute_self_and_r_derivative(
1293                            &zero,
1294                            &zero_velocity,
1295                            &center,
1296                            &externals,
1297                            &masses,
1298                            lmb,
1299                        )
1300                        .0;
1301                    if !esurface_value_is_strictly_inside(
1302                        &value,
1303                        &F::<T>::from_f64(settings.kinematics.e_cm),
1304                    ) {
1305                        let thresholds = EsurfaceCollection::from(vec![surface.as_ref().clone()]);
1306                        let existing = ExistingThresholds::from(vec![EsurfaceID(0)]);
1307                        let input = OverlapInput {
1308                            graph: &graph,
1309                            settings: &settings,
1310                            subspace: &subspace,
1311                            threshold_subspaces: None,
1312                            lmbs: &lmbs,
1313                            thresholds: &thresholds,
1314                            edge_masses: masses.iter().map(|(_, mass)| mass.into_ff64()).collect(),
1315                            surface_kinematics: None,
1316                        };
1317                        let loops: LoopMomenta<F<f64>> =
1318                            center.iter().map(ThreeMomentum::to_f64).collect();
1319                        let external: ExternalFourMomenta<F<f64>> =
1320                            externals.iter().map(|p| p.to_f64()).collect();
1321                        if loops
1322                            .iter()
1323                            .flat_map(|p| [&p.px, &p.py, &p.pz])
1324                            .any(|v| !v.0.is_finite())
1325                            || external
1326                                .iter()
1327                                .flat_map(|p| {
1328                                    [
1329                                        &p.temporal.value,
1330                                        &p.spatial.px,
1331                                        &p.spatial.py,
1332                                        &p.spatial.pz,
1333                                    ]
1334                                })
1335                                .any(|v| !v.0.is_finite())
1336                            || input
1337                                .edge_masses
1338                                .iter()
1339                                .any(|(_, mass)| !mass.0.is_finite())
1340                        {
1341                            return Err(SamplingEvaluationError::UncertainGeometry {
1342                                detail: "native fiber data exceed the finite range of the approximate f64 SOCP center seed".to_owned(),
1343                            }.into());
1344                        }
1345                        let candidate = find_center(&input, &[ExistingEsurfaceId(0)], &existing, &loops, &external, false)
1346                            .map_err(|error| SamplingEvaluationError::UncertainGeometry { detail: format!("fiber center seed failed without an absence certificate: {error}") })?
1347                            .ok_or_else(|| SamplingEvaluationError::UncertainGeometry { detail: "fiber SOCP reported infeasibility without a native absence certificate".to_owned() })?;
1348                        for &index in active.iter() {
1349                            center[index] = ThreeMomentum::from_ff64(candidate[index]);
1350                        }
1351                    }
1352                    let value = surface
1353                        .compute_self_and_r_derivative(
1354                            &zero,
1355                            &zero_velocity,
1356                            &center,
1357                            &externals,
1358                            &masses,
1359                            lmb,
1360                        )
1361                        .0;
1362                    if !esurface_value_is_strictly_inside(
1363                        &value,
1364                        &F::<T>::from_f64(settings.kinematics.e_cm),
1365                    ) {
1366                        return Err(SamplingEvaluationError::UncertainGeometry {
1367                            detail: format!(
1368                                "fiber center is not natively certified inside: value={value}"
1369                            ),
1370                        }
1371                        .into());
1372                    }
1373                    PreparedSurfaceStatus::existing(Some(normalized(&value)?))?
1374                }
1375            };
1376            if status.is_existing() {
1377                // The analytic minimum does not excuse a rounded minimizing
1378                // point: the actual bound center must also be strictly inside.
1379                let value = surface
1380                    .compute_self_and_r_derivative(
1381                        &zero,
1382                        &zero_velocity,
1383                        &center,
1384                        &externals,
1385                        &masses,
1386                        lmb,
1387                    )
1388                    .0;
1389                if !esurface_value_is_strictly_inside(
1390                    &value,
1391                    &F::<T>::from_f64(settings.kinematics.e_cm),
1392                ) || value >= -&tolerance
1393                {
1394                    return Err(SamplingEvaluationError::UncertainGeometry {
1395                        detail: format!(
1396                            "prepared fiber center is not natively certified inside: value={value}, tolerance={tolerance}"
1397                        ),
1398                    }
1399                    .into());
1400                }
1401            }
1402            let center = active
1403                .iter()
1404                .flat_map(|&index| {
1405                    [
1406                        center[index].px.0.clone(),
1407                        center[index].py.0.clone(),
1408                        center[index].pz.0.clone(),
1409                    ]
1410                })
1411                .collect();
1412            Ok((center, status))
1413        });
1414        ImplicitSurfaceRadialMap::new(
1415            dimension,
1416            vec![zero.0; dimension],
1417            beta,
1418            power,
1419            Arc::new(|_, _| Err(eyre!("sampling fiber requires prepared complement data"))),
1420        )
1421        .and_then(|map| {
1422            let map = map
1423                .with_context_evaluator(evaluator)
1424                .with_context_preparer(preparer);
1425            if freeze_context {
1426                map.freeze_context(&[])
1427            } else {
1428                Ok(map)
1429            }
1430        })
1431    }
1432
1433    pub(crate) fn has_radial_dependence_in_subspace(
1434        &self,
1435        subspace: &SubspaceData,
1436        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
1437        graph: &Graph,
1438    ) -> bool {
1439        let lmb = subspace.get_lmb(all_lmbs);
1440        subspace.contains(&self.energies, graph).any(|index| {
1441            subspace
1442                .project_loop_signature(&lmb.edge_signatures[index].internal)
1443                .any(|sign| sign.is_sign())
1444        })
1445    }
1446
1447    pub(crate) fn external_shift_is_strictly_negative_for_positive_energies(
1448        &self,
1449        incoming_edges: &[EdgeIndex],
1450        outgoing_edges: &[EdgeIndex],
1451    ) -> bool {
1452        if incoming_edges.is_empty()
1453            || outgoing_edges.is_empty()
1454            || incoming_edges
1455                .iter()
1456                .any(|edge| outgoing_edges.contains(edge))
1457            || self
1458                .external_shift
1459                .iter()
1460                .any(|(edge, _)| !incoming_edges.contains(edge) && !outgoing_edges.contains(edge))
1461        {
1462            return false;
1463        }
1464
1465        let coefficient = |edge: &EdgeIndex| {
1466            self.external_shift
1467                .iter()
1468                .filter(|(shift_edge, _)| shift_edge == edge)
1469                .map(|(_, coefficient)| i128::from(*coefficient))
1470                .sum::<i128>()
1471        };
1472
1473        // Energy conservation makes external-energy coefficient vectors `c` and
1474        // `c + lambda * sigma` equivalent, where sigma is +1 for incoming and -1
1475        // for outgoing momenta. The shift is strictly negative for all positive
1476        // external energies if one representative is component-wise non-positive
1477        // and not identically zero.
1478        let lambda_lower_bound = outgoing_edges
1479            .iter()
1480            .map(&coefficient)
1481            .max()
1482            .expect("outgoing external edges were checked to be non-empty");
1483        let lambda_upper_bound = incoming_edges
1484            .iter()
1485            .map(|edge| -coefficient(edge))
1486            .min()
1487            .expect("incoming external edges were checked to be non-empty");
1488
1489        if lambda_lower_bound > lambda_upper_bound {
1490            return false;
1491        }
1492
1493        let lambda = lambda_lower_bound;
1494        let adjusted_coefficients = incoming_edges
1495            .iter()
1496            .map(|edge| coefficient(edge) + lambda)
1497            .chain(outgoing_edges.iter().map(|edge| coefficient(edge) - lambda));
1498        let mut has_strictly_negative_coefficient = false;
1499        for adjusted_coefficient in adjusted_coefficients {
1500            if adjusted_coefficient > 0 {
1501                return false;
1502            }
1503            has_strictly_negative_coefficient |= adjusted_coefficient < 0;
1504        }
1505
1506        has_strictly_negative_coefficient
1507    }
1508
1509    pub(crate) fn to_atom(&self, cut_edges: &[EdgeIndex]) -> Atom {
1510        let symbolic_energies = self
1511            .energies
1512            .iter()
1513            .map(|i| {
1514                if cut_edges.contains(i) {
1515                    cut_energy(*i)
1516                } else {
1517                    ose_atom_from_index(*i)
1518                }
1519            })
1520            .collect_vec();
1521
1522        let symbolic_shift = self
1523            .external_shift
1524            .iter()
1525            .fold(Atom::new(), |sum, (i, sign)| {
1526                external_energy_atom_from_index(*i) * &Atom::num(*sign) + &sum
1527            });
1528
1529        let builder_atom = Atom::new();
1530        let energy_sum = symbolic_energies
1531            .iter()
1532            .fold(builder_atom, |acc, energy| acc + energy);
1533
1534        energy_sum + &symbolic_shift
1535    }
1536
1537    #[inline]
1538    pub(crate) fn compute_from_dual_momenta<T: FloatLike>(
1539        &self,
1540        lmb: &LoopMomentumBasis,
1541        real_mass_vector: &EdgeVec<F<T>>,
1542        dual_loop_moms: &LoopMomenta<HyperDual<F<T>>>,
1543        dual_external_moms: &ExternalFourMomenta<HyperDual<F<T>>>,
1544    ) -> HyperDual<F<T>> {
1545        let spatial_part_of_externals = dual_external_moms
1546            .iter()
1547            .map(|mom| mom.spatial.clone())
1548            .collect::<TiVec<ExternalIndex, _>>();
1549
1550        let energy_sum = self
1551            .energies
1552            .iter()
1553            .map(|index| {
1554                let signature = &lmb.edge_signatures[*index];
1555                let momentum = signature
1556                    .try_compute_momentum(&dual_loop_moms.0, &spatial_part_of_externals.raw)
1557                    .unwrap_or_else(|| unreachable!());
1558                let mass = &real_mass_vector[*index];
1559
1560                (momentum.norm_squared() + mass * mass).sqrt()
1561            })
1562            .reduce(|acc, x| acc + x)
1563            .unwrap_or_else(|| dual_loop_moms[LoopIndex(0)].px.zero());
1564
1565        let shift_part = self
1566            .external_shift
1567            .iter()
1568            .map(|(index, sign)| {
1569                let external_signature = &lmb.edge_signatures[*index].external;
1570                let sign = energy_sum.values[0].from_i64(*sign);
1571                new_constant(&energy_sum, &sign)
1572                    * external_signature
1573                        .try_apply(&dual_external_moms.raw)
1574                        .map(|mom| mom.temporal.value)
1575                        .unwrap_or_else(|| energy_sum.zero())
1576            })
1577            .reduce(|acc, x| acc + x)
1578            .unwrap_or_else(|| energy_sum.zero());
1579
1580        energy_sum + shift_part
1581    }
1582
1583    /// Compute the value of the esurface from the momenta, needed to check if an arbitrary point
1584    /// is inside the esurface
1585    #[inline]
1586    pub(crate) fn compute_from_momenta<T: FloatLike>(
1587        &self,
1588        lmb: &LoopMomentumBasis,
1589        real_mass_vector: &EdgeVec<F<T>>,
1590        loop_moms: &LoopMomenta<F<T>>,
1591        external_moms: &ExternalFourMomenta<F<T>>,
1592    ) -> F<T> {
1593        let spatial_part_of_externals = external_moms
1594            .iter()
1595            .map(|mom| mom.spatial.clone())
1596            .collect::<TiVec<ExternalIndex, _>>();
1597
1598        let energy_sum = self
1599            .energies
1600            .iter()
1601            .map(|index| {
1602                let signature = &lmb.edge_signatures[*index];
1603                let momentum = signature.compute_momentum(loop_moms, &spatial_part_of_externals);
1604                let mass = &real_mass_vector[*index];
1605
1606                (momentum.norm_squared() + mass * mass).sqrt()
1607            })
1608            .reduce(|acc, x| acc + x)
1609            .unwrap_or_else(|| loop_moms[LoopIndex(0)].px.zero());
1610
1611        let shift_part = self
1612            .external_shift
1613            .iter()
1614            .map(|(index, sign)| {
1615                let external_signature = &lmb.edge_signatures[*index].external;
1616                energy_sum.from_i64(*sign)
1617                    * compute_t_part_of_shift_part(external_signature, external_moms)
1618            })
1619            .reduce(|acc, x| acc + x)
1620            .unwrap_or_else(|| energy_sum.zero());
1621
1622        energy_sum + shift_part
1623    }
1624
1625    fn classify_invariant_margin<T: FloatLike>(
1626        shift_part: &F<T>,
1627        invariant_margin: F<T>,
1628        e_cm: &F<T>,
1629        normalized_margin_tolerance: &F<T>,
1630    ) -> EsurfaceExistence<T> {
1631        // /!\ In alphaLoop this boundary needed special care so that `mul_unit` could not
1632        // create a discontinuous non-pinched-to-pinched transition for a massless 2->2
1633        // E-surface sandwich. Such a surface is `Existing` at non-collinear kinematics, where
1634        // its invariant margin is positive, and `Pinched` only at the collinear null boundary.
1635        // Here the normalized tolerance band around that boundary is classified as `Pinched`,
1636        // so perturbations within the band cannot toggle threshold subtraction. GammaLoop's
1637        // statuses are exclusive and only `Existing` surfaces are subtraction targets.
1638        // Keep the decision in the original, dimensionful scale. Besides avoiding an
1639        // unnecessary division, this preserves the previous existence boundary exactly.
1640        // Deserialization rejects invalid configured tolerances. Keep this defensive fallback for
1641        // settings mutated programmatically (for example through a language binding), so an
1642        // invalid sign can never invert the existence test.
1643        let normalized_margin_tolerance =
1644            if normalized_margin_tolerance.is_nan() || normalized_margin_tolerance.is_infinite() {
1645                F::from_f64(DEFAULT_ESURFACE_EXISTENCE_THRESHOLD)
1646            } else {
1647                normalized_margin_tolerance.abs()
1648            };
1649        let invariant_tolerance = &normalized_margin_tolerance * e_cm * e_cm;
1650        let normalized_margin = &invariant_margin / (e_cm * e_cm);
1651        let shift_tolerance = F::from_f64(ESURFACE_SHIFT_THRESHOLD) * e_cm;
1652
1653        if invariant_margin.abs() <= invariant_tolerance && shift_part <= &shift_tolerance {
1654            return EsurfaceExistence::Pinched { normalized_margin };
1655        }
1656
1657        if shift_part >= &(-shift_tolerance) {
1658            return EsurfaceExistence::NonExisting {
1659                normalized_margin: Some(normalized_margin),
1660                reason: NonExistingEsurfaceReason::ShiftNotNegative,
1661            };
1662        }
1663
1664        if invariant_margin > invariant_tolerance {
1665            EsurfaceExistence::Existing { normalized_margin }
1666        } else if invariant_margin >= -invariant_tolerance {
1667            EsurfaceExistence::Pinched { normalized_margin }
1668        } else {
1669            EsurfaceExistence::NonExisting {
1670                normalized_margin: Some(normalized_margin),
1671                reason: NonExistingEsurfaceReason::NoRealZero,
1672            }
1673        }
1674    }
1675
1676    #[inline]
1677    #[allow(clippy::too_many_arguments)]
1678    /// Classify a surface in an active loop-momentum subspace. Pinched and
1679    /// non-existing surfaces remain distinct and are never subtraction targets.
1680    pub(crate) fn classify_existence_subspace<T: FloatLike>(
1681        &self,
1682        loop_moms: &LoopMomenta<F<T>>,
1683        external_moms: &ExternalFourMomenta<F<T>>,
1684        subspace: &SubspaceData,
1685        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
1686        graph: &Graph,
1687        real_mass_vector: &EdgeVec<F<T>>,
1688        reversed_edges: &[EdgeIndex],
1689        e_cm: &F<T>,
1690        normalized_margin_tolerance: &F<T>,
1691    ) -> EsurfaceExistence<T> {
1692        //todo!("refactor for subspaces");
1693        if self.external_shift.is_empty() {
1694            debug!("esurface has no external shift, cannot exist");
1695            return EsurfaceExistence::NonExisting {
1696                normalized_margin: None,
1697                reason: NonExistingEsurfaceReason::NoExternalShift,
1698            };
1699        }
1700
1701        let shift_part = self.compute_shift_part_from_momenta_in_subspace(
1702            loop_moms,
1703            external_moms,
1704            subspace,
1705            all_lmbs,
1706            graph,
1707            real_mass_vector,
1708        );
1709
1710        let subspace_energy_indices = subspace.contains(&self.energies, graph).collect_vec();
1711
1712        if !self.has_radial_dependence_in_subspace(subspace, all_lmbs, graph) {
1713            debug!(
1714                "esurface has no radial energy in this subspace, cannot bound a threshold region"
1715            );
1716            return EsurfaceExistence::NonExisting {
1717                normalized_margin: None,
1718                reason: NonExistingEsurfaceReason::NoRadialDependence,
1719            };
1720        }
1721
1722        let lmb = subspace.get_lmb(all_lmbs);
1723        let mass_sum: F<T> = subspace_energy_indices
1724            .iter()
1725            .map(|&index| &real_mass_vector[index])
1726            .fold(F::from_f64(0.0), |acc, x| acc + x);
1727
1728        let zero_vector = ThreeMomentum::new(e_cm.zero(), e_cm.zero(), e_cm.zero());
1729
1730        let graph_vector = self
1731            .external_shift
1732            .iter()
1733            .map(|(index, sign)| {
1734                let external_signature = &lmb.edge_signatures[*index].external;
1735                compute_shift_part(external_signature, external_moms).spatial
1736                    * F::from_f64(*sign as f64)
1737            })
1738            .reduce(|acc, x| acc + x)
1739            .unwrap_or_else(|| zero_vector.clone());
1740
1741        let other_part = subspace
1742            .does_not_contain(&self.energies, graph)
1743            .map(|index| {
1744                let signature = &lmb.edge_signatures[index];
1745                let sign = if reversed_edges.contains(&index) {
1746                    -F::from_f64(1.0)
1747                } else {
1748                    F::from_f64(1.0)
1749                };
1750
1751                signature.compute_momentum(
1752                    loop_moms,
1753                    &external_moms
1754                        .iter()
1755                        .map(|mom| mom.spatial.clone())
1756                        .collect::<TiVec<ExternalIndex, _>>(),
1757                ) * sign
1758            })
1759            .reduce(|acc, x| acc + x)
1760            .unwrap_or_else(|| zero_vector.clone());
1761
1762        let shift_vector_sq = (&graph_vector + &other_part).norm_squared();
1763        let invariant_margin = &shift_part * &shift_part - &shift_vector_sq - &mass_sum * &mass_sum;
1764        let classification = Self::classify_invariant_margin(
1765            &shift_part,
1766            invariant_margin,
1767            e_cm,
1768            normalized_margin_tolerance,
1769        );
1770
1771        if !classification.is_existing() {
1772            debug!(
1773                "subspace esurface classified as {}: shift_part^2: {}, shift_vector_sq: {}, mass_sum^2: {}, normalized_margin: {:?}",
1774                classification.label(),
1775                &shift_part * &shift_part,
1776                shift_vector_sq,
1777                &mass_sum * &mass_sum,
1778                classification.normalized_margin(),
1779            );
1780        }
1781
1782        classification
1783    }
1784
1785    #[inline]
1786    /// Classify a full-space surface. Pinched and non-existing surfaces remain
1787    /// distinct and are never subtraction targets.
1788    pub(crate) fn classify_existence<T: FloatLike>(
1789        &self,
1790        external_moms: &ExternalFourMomenta<F<T>>,
1791        lmb: &LoopMomentumBasis,
1792        real_mass_vector: &EdgeVec<F<T>>,
1793        e_cm: &F<T>,
1794        normalized_margin_tolerance: &F<T>,
1795    ) -> EsurfaceExistence<T> {
1796        //todo!("refactor for subspaces");
1797        if self.external_shift.is_empty() {
1798            return EsurfaceExistence::NonExisting {
1799                normalized_margin: None,
1800                reason: NonExistingEsurfaceReason::NoExternalShift,
1801            };
1802        }
1803
1804        let shift_part = self.compute_shift_part_from_momenta(external_moms, lmb);
1805        let mass_sum: F<T> = self
1806            .energies
1807            .iter()
1808            .map(|index| &real_mass_vector[*index])
1809            .fold(F::from_f64(0.0), |acc, x| acc + x);
1810
1811        let zero_vector = ThreeMomentum::new(e_cm.zero(), e_cm.zero(), e_cm.zero());
1812
1813        let shift_vector = self
1814            .external_shift
1815            .iter()
1816            .map(|(index, sign)| {
1817                let external_signature = &lmb.edge_signatures[*index].external;
1818                compute_shift_part(external_signature, external_moms).spatial
1819                    * F::from_f64(*sign as f64)
1820            })
1821            .reduce(|acc, x| acc + x)
1822            .unwrap_or_else(|| zero_vector.clone());
1823
1824        let shift_vector_sq = shift_vector.norm_squared();
1825        let invariant_margin = &shift_part * &shift_part - shift_vector_sq - &mass_sum * &mass_sum;
1826
1827        Self::classify_invariant_margin(
1828            &shift_part,
1829            invariant_margin,
1830            e_cm,
1831            normalized_margin_tolerance,
1832        )
1833    }
1834
1835    /// Classify a full-space surface while keeping normalized margins and rejection reasons
1836    /// internal to the subtraction implementation.
1837    pub fn existence_status<T: FloatLike>(
1838        &self,
1839        external_moms: &ExternalFourMomenta<F<T>>,
1840        lmb: &LoopMomentumBasis,
1841        real_mass_vector: &EdgeVec<F<T>>,
1842        e_cm: &F<T>,
1843        normalized_margin_tolerance: &F<T>,
1844    ) -> EsurfaceExistenceStatus {
1845        self.classify_existence(
1846            external_moms,
1847            lmb,
1848            real_mass_vector,
1849            e_cm,
1850            normalized_margin_tolerance,
1851        )
1852        .status()
1853    }
1854
1855    /// Only compute the shift part, useful for center finding.
1856    pub(crate) fn compute_shift_part_from_momenta_in_subspace<T: FloatLike>(
1857        &self,
1858        loop_moms: &LoopMomenta<F<T>>,
1859        external_moms: &ExternalFourMomenta<F<T>>,
1860        subspace: &SubspaceData,
1861        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
1862        graph: &Graph,
1863        masses: &EdgeVec<F<T>>,
1864    ) -> F<T> {
1865        let lmb = subspace.get_lmb(all_lmbs);
1866
1867        let full_external_shift = self
1868            .external_shift
1869            .iter()
1870            .map(|(index, sign)| {
1871                let external_signature = &lmb.edge_signatures[*index].external;
1872                external_moms[ExternalIndex(0)]
1873                    .temporal
1874                    .value
1875                    .from_i64(*sign)
1876                    * compute_t_part_of_shift_part(external_signature, external_moms)
1877            })
1878            .reduce(|acc, x| acc + x)
1879            .unwrap_or_else(|| external_moms[ExternalIndex(0)].temporal.value.zero());
1880
1881        let spatial_externals = external_moms
1882            .iter()
1883            .map(|mom| mom.spatial.clone())
1884            .collect::<TiVec<ExternalIndex, _>>();
1885
1886        let remaining_shift = subspace
1887            .does_not_contain(&self.energies, graph)
1888            .map(|index| {
1889                let signature = &lmb.edge_signatures[index];
1890                //panic!("signature: {:?}", signature);
1891                let momentum = signature.compute_momentum(loop_moms, &spatial_externals);
1892                let mass = &masses[index];
1893
1894                (momentum.norm_squared() + mass * mass).sqrt()
1895            })
1896            .reduce(|acc, x| acc + x)
1897            .unwrap_or_else(|| full_external_shift.zero());
1898
1899        full_external_shift + remaining_shift
1900    }
1901
1902    /// Only compute the shift part, useful for center finding.
1903    pub(crate) fn compute_shift_part_from_momenta<T: FloatLike>(
1904        &self,
1905        external_moms: &ExternalFourMomenta<F<T>>,
1906        lmb: &LoopMomentumBasis,
1907    ) -> F<T> {
1908        self.external_shift
1909            .iter()
1910            .map(|(index, sign)| {
1911                let external_signature = &lmb.edge_signatures[*index].external;
1912                external_moms[ExternalIndex(0)]
1913                    .temporal
1914                    .value
1915                    .from_i64(*sign)
1916                    * compute_t_part_of_shift_part(external_signature, external_moms)
1917            })
1918            .reduce(|acc, x| acc + x)
1919            .unwrap_or_else(|| external_moms[ExternalIndex(0)].temporal.value.zero())
1920    }
1921
1922    #[inline]
1923    #[allow(clippy::too_many_arguments)]
1924    pub(crate) fn compute_self_and_r_derivative_subspace<T: FloatLike>(
1925        &self,
1926        radius: &F<T>,
1927        shifted_unit_loops_in_subspace: &LoopMomenta<F<T>>,
1928        center_in_subspace: &LoopMomenta<F<T>>,
1929        external_moms: &ExternalFourMomenta<F<T>>,
1930        real_mass_vector: &EdgeVec<F<T>>,
1931        subspace: &SubspaceData,
1932        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
1933        graph: &Graph,
1934    ) -> (F<T>, F<T>) {
1935        let spatial_part_of_externals: ExternalThreeMomenta<F<T>> = external_moms
1936            .iter()
1937            .map(|mom| mom.spatial.clone())
1938            .collect();
1939
1940        let loops: LoopMomenta<F<T>> = shifted_unit_loops_in_subspace
1941            .iter_enumerated()
1942            .map(|(loop_index, shifted_unit_momenta)| {
1943                if subspace.contains_loop_index(loop_index) {
1944                    shifted_unit_momenta * radius + &center_in_subspace[loop_index]
1945                } else {
1946                    shifted_unit_momenta.clone()
1947                }
1948            })
1949            .collect();
1950
1951        let shift = self.compute_shift_part_from_momenta_in_subspace(
1952            shifted_unit_loops_in_subspace,
1953            external_moms,
1954            subspace,
1955            all_lmbs,
1956            graph,
1957            real_mass_vector,
1958        );
1959
1960        let lmb = subspace.get_lmb(all_lmbs);
1961        let (derivative, energy_sum) = subspace
1962            .contains(&self.energies, graph)
1963            .map(|index| {
1964                let signature = &lmb.edge_signatures[index];
1965
1966                let momentum = signature.compute_momentum(&loops, &spatial_part_of_externals);
1967                let unit_loop_part = compute_loop_part_subspace(
1968                    &signature.internal,
1969                    shifted_unit_loops_in_subspace,
1970                    subspace,
1971                );
1972
1973                let energy = (momentum.norm_squared()
1974                    + &real_mass_vector[index] * &real_mass_vector[index])
1975                    .sqrt();
1976
1977                let numerator = momentum * &unit_loop_part;
1978
1979                (numerator / &energy, energy)
1980            })
1981            .fold(
1982                (radius.zero(), radius.zero()),
1983                |(der_sum, en_sum), (der, en)| (der_sum + der, en_sum + en),
1984            );
1985
1986        (energy_sum + shift, derivative)
1987    }
1988
1989    /// Shared routed ray evaluation for physical roots and full-space/proper-fiber charts.
1990    #[inline]
1991    pub(crate) fn compute_self_and_r_derivative<T: FloatLike>(
1992        &self,
1993        radius: &F<T>,
1994        shifted_unit_loops: &LoopMomenta<F<T>>,
1995        center: &LoopMomenta<F<T>>,
1996        external_moms: &ExternalFourMomenta<F<T>>,
1997        real_mass_vector: &EdgeVec<F<T>>,
1998        lmb: &LoopMomentumBasis,
1999    ) -> (F<T>, F<T>) {
2000        let spatial_part_of_externals: ExternalThreeMomenta<F<T>> = external_moms
2001            .iter()
2002            .map(|mom| mom.spatial.clone())
2003            .collect();
2004
2005        let loops: LoopMomenta<F<T>> = shifted_unit_loops
2006            .iter()
2007            .zip(center.iter())
2008            .map(|(momentum, center)| momentum * radius + center)
2009            .collect();
2010
2011        let shift = self.compute_shift_part_from_momenta(external_moms, lmb);
2012
2013        let (derivative, energy_sum) = self
2014            .energies
2015            .iter()
2016            .map(|&index| {
2017                let signature = &lmb.edge_signatures[index];
2018
2019                let momentum = signature.compute_momentum(&loops, &spatial_part_of_externals);
2020                let unit_loop_part = compute_loop_part(&signature.internal, shifted_unit_loops);
2021
2022                let energy = (momentum.norm_squared()
2023                    + &real_mass_vector[index] * &real_mass_vector[index])
2024                    .sqrt();
2025
2026                // At a massless endpoint E(r)=r|v| the radial right derivative is
2027                // |v|, whereas the two-sided formula q.v/E would evaluate 0/0.
2028                // Share this convention with sampling charts, using exact source
2029                // zeros: a computed E=0 can instead be numerical underflow.
2030                let zero = radius.zero();
2031                let derivative = if radius == &zero
2032                    && real_mass_vector[index] == zero
2033                    && momentum.px == zero
2034                    && momentum.py == zero
2035                    && momentum.pz == zero
2036                {
2037                    unit_loop_part.norm_squared().sqrt()
2038                } else {
2039                    momentum * &unit_loop_part / &energy
2040                };
2041
2042                (derivative, energy)
2043            })
2044            .fold(
2045                (radius.zero(), radius.zero()),
2046                |(der_sum, en_sum), (der, en)| (der_sum + der, en_sum + en),
2047            );
2048
2049        (energy_sum + shift, derivative)
2050    }
2051
2052    /// Enclose the original graph equation at `radius * loop_momenta`.
2053    /// Route integer coefficients before rounding energy sums; importing an
2054    /// already represented `routed_ray` would omit this routing error. True
2055    /// external shifts and every repeated energy occurrence remain unchanged.
2056    pub(crate) fn evaluate_routed_enclosed<T: FloatLike>(
2057        &self,
2058        radius: &F<T>,
2059        loop_momenta: &LoopMomenta<F<T>>,
2060        external_momenta: &ExternalFourMomenta<F<T>>,
2061        masses: &EdgeVec<F<T>>,
2062        lmb: &LoopMomentumBasis,
2063    ) -> Result<[Float; 2]> {
2064        use linnet::num_traits::SignOrZero;
2065        if loop_momenta.len() != lmb.loop_edges.len()
2066            || external_momenta.len() != lmb.ext_edges.len()
2067        {
2068            return Err(eyre!(
2069                "routed E-surface enclosure requires the complete parent and external frame"
2070            ));
2071        }
2072        if !radius.0.is_finite()
2073            || loop_momenta
2074                .iter()
2075                .flat_map(|p| [&p.px, &p.py, &p.pz])
2076                .chain(external_momenta.iter().flat_map(|p| {
2077                    [
2078                        &p.temporal.value,
2079                        &p.spatial.px,
2080                        &p.spatial.py,
2081                        &p.spatial.pz,
2082                    ]
2083                }))
2084                .any(|x| !x.0.is_finite())
2085        {
2086            return Err(SamplingEvaluationError::Unrepresentable {
2087                operation: "routed E-surface enclosure",
2088                detail: "nonfinite original radius or momentum component".into(),
2089            }
2090            .into());
2091        }
2092        for edge in self
2093            .energies
2094            .iter()
2095            .chain(self.external_shift.iter().map(|(edge, _)| edge))
2096        {
2097            let signature = lmb.edge_signatures.get(*edge).ok_or_else(|| {
2098                eyre!("routed E-surface enclosure has no signature for edge {edge}")
2099            })?;
2100            // Empty signatures are exact zero routes; nonempty signatures must
2101            // describe the full frame, never a silently truncated prefix.
2102            if (!signature.internal.is_empty() && signature.internal.len() != loop_momenta.len())
2103                || (!signature.external.is_empty()
2104                    && signature.external.len() != external_momenta.len())
2105            {
2106                return Err(eyre!(
2107                    "routed E-surface enclosure has an incomplete signature for edge {edge}"
2108                ));
2109            }
2110        }
2111        let coefficient = |sign: &SignOrZero| match sign {
2112            SignOrZero::Plus => 1,
2113            SignOrZero::Minus => -1,
2114            SignOrZero::Zero => 0,
2115        };
2116        let spatial = |p: &ThreeMomentum<F<T>>, axis| {
2117            EsurfaceRay::<T>::enclosed_native([&p.px, &p.py, &p.pz][axis])
2118        };
2119        let mut energies = Vec::with_capacity(self.energies.len());
2120        for &edge in &self.energies {
2121            let mass = masses
2122                .get(edge)
2123                .ok_or_else(|| eyre!("routed E-surface enclosure has no mass for edge {edge}"))?;
2124            if !mass.0.is_finite() {
2125                return Err(SamplingEvaluationError::Unrepresentable {
2126                    operation: "routed E-surface enclosure",
2127                    detail: format!("nonfinite original mass on edge {edge}"),
2128                }
2129                .into());
2130            }
2131            let signature = &lmb.edge_signatures[edge];
2132            let velocity = std::array::from_fn(|axis| {
2133                EsurfaceRay::<T>::enclosed_sum(
2134                    signature
2135                        .internal
2136                        .iter()
2137                        .zip(loop_momenta.iter())
2138                        .map(|(sign, p)| (coefficient(sign), spatial(p, axis))),
2139                )
2140            });
2141            let offset = std::array::from_fn(|axis| {
2142                EsurfaceRay::<T>::enclosed_sum(
2143                    signature
2144                        .external
2145                        .iter()
2146                        .zip(external_momenta.iter())
2147                        .map(|(sign, p)| (coefficient(sign), spatial(&p.spatial, axis))),
2148                )
2149            });
2150            energies.push((velocity, offset, EsurfaceRay::<T>::enclosed_native(mass)));
2151        }
2152        let shift =
2153            EsurfaceRay::<T>::enclosed_sum(self.external_shift.iter().map(|(edge, scale)| {
2154                let temporal = EsurfaceRay::<T>::enclosed_sum(
2155                    lmb.edge_signatures[*edge]
2156                        .external
2157                        .iter()
2158                        .zip(external_momenta.iter())
2159                        .map(|(sign, p)| {
2160                            (
2161                                coefficient(sign),
2162                                EsurfaceRay::<T>::enclosed_native(&p.temporal.value),
2163                            )
2164                        }),
2165                );
2166                (*scale, temporal)
2167            }));
2168        Ok(EsurfaceRay::<T>::evaluate_enclosed(
2169            energies.into_iter(),
2170            shift,
2171            &EsurfaceRay::<T>::enclosed_native(radius),
2172            false,
2173        )?
2174        .0)
2175    }
2176
2177    /// Explicit prepared-ray boundary for LU root and jet evaluation. Routing
2178    /// before radial scaling changes finite-precision association. Existing
2179    /// non-LU amplitude/static/fiber evaluation keeps scale-then-route semantics.
2180    /// The center is supplied explicitly; physical LU uses generation zero.
2181    pub(crate) fn routed_ray<T: FloatLike>(
2182        &self,
2183        velocity: &LoopMomenta<F<T>>,
2184        center: &LoopMomenta<F<T>>,
2185        external_moms: &ExternalFourMomenta<F<T>>,
2186        masses: &EdgeVec<F<T>>,
2187        lmb: &LoopMomentumBasis,
2188    ) -> EsurfaceRay<T> {
2189        let spatial: ExternalThreeMomenta<F<T>> = external_moms
2190            .iter()
2191            .map(|momentum| momentum.spatial.clone())
2192            .collect();
2193        EsurfaceRay {
2194            energies: self
2195                .energies
2196                .iter()
2197                .map(|&edge| {
2198                    let signature = &lmb.edge_signatures[edge];
2199                    (
2200                        edge,
2201                        compute_loop_part(&signature.internal, velocity),
2202                        signature.compute_momentum(center, &spatial),
2203                        masses[edge].clone(),
2204                    )
2205                })
2206                .collect(),
2207            shift: self.compute_shift_part_from_momenta(external_moms, lmb),
2208        }
2209    }
2210
2211    /// Solve the physical LU scaling about zero generation momentum. Sampling
2212    /// hosts use the same equation and policy; the caller owns the identity and
2213    /// precision history, so sharing this entry does not imply shared root data.
2214    #[allow(clippy::too_many_arguments)]
2215    pub(crate) fn solve_lu_cut<T: FloatLike>(
2216        &self,
2217        loop_momenta: &LoopMomenta<F<T>>,
2218        external_momenta: &ExternalFourMomenta<F<T>>,
2219        masses: &EdgeVec<F<T>>,
2220        lmb: &LoopMomentumBasis,
2221        e_cm: &F<T>,
2222        diagnostics: &mut RadialRootDiagnostics,
2223        identity: &RadialRootIdentity,
2224    ) -> std::result::Result<(EsurfaceRay<T>, NewtonIterationResult<T>), SafeguardedNewtonError<T>>
2225    {
2226        let zero = loop_momenta[LoopIndex(0)].px.zero();
2227        let center = LoopMomenta::from_iter(
2228            (0..loop_momenta.len())
2229                .map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
2230        );
2231        let ray = self.routed_ray(loop_momenta, &center, external_momenta, masses, lmb);
2232        let solution = ray.solve_lu_cut(e_cm, diagnostics, identity)?;
2233        Ok((ray, solution))
2234    }
2235
2236    // #[inline]
2237    /// the "loops_unit_in_subspace" means that the loop momenta that are part of the subspace are jointly normalized to unit length
2238    /// A ray with no radial dependence has no isolated root and returns zero guesses.
2239    pub(crate) fn get_radius_guess_subspace<T: FloatLike>(
2240        &self,
2241        loops_unit_in_subspace: &LoopMomenta<F<T>>,
2242        external_moms: &ExternalFourMomenta<F<T>>,
2243        subspace: &SubspaceData,
2244        all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
2245        graph: &Graph,
2246        masses: &EdgeVec<F<T>>,
2247    ) -> (F<T>, F<T>) {
2248        let const_builder = &loops_unit_in_subspace[LoopIndex(0)].px;
2249
2250        let esurface_shift = self.compute_shift_part_from_momenta_in_subspace(
2251            loops_unit_in_subspace,
2252            external_moms,
2253            subspace,
2254            all_lmbs,
2255            graph,
2256            masses,
2257        );
2258
2259        debug!("shift part for radius guess: {}", esurface_shift);
2260
2261        let mut radius_guess = const_builder.zero();
2262        let mut denominator = const_builder.zero();
2263
2264        let lmb = subspace.get_lmb(all_lmbs);
2265
2266        debug!("unit loops in subspace: {:?}", loops_unit_in_subspace);
2267        let external_3_momenta = external_moms.iter().map(|x| x.spatial.clone()).collect();
2268
2269        //println!("got to energy loop");
2270        for energy in subspace.contains(&self.energies, graph) {
2271            debug!("computing contribution for energy {:?}", energy);
2272            let signature = &lmb.edge_signatures[energy];
2273            //println!("signature {:?}", signature);
2274
2275            let unit_loop_part =
2276                compute_loop_part_subspace(&signature.internal, loops_unit_in_subspace, subspace);
2277            //println!("computed_loop_part {:?}", unit_loop_part);
2278
2279            let three_shift = compute_shift_part_subspace(
2280                &signature.internal,
2281                &signature.external,
2282                loops_unit_in_subspace,
2283                &external_3_momenta,
2284                subspace,
2285            );
2286            //./bprintln!("computed_shift {:?}", shift);
2287
2288            let norm_unit_loop_part_squared = unit_loop_part.norm_squared();
2289            // An energy with zero radial momentum is constant along this ray. It contributes
2290            // no large-radius growth or directional shift, even when its mass is nonzero.
2291            if norm_unit_loop_part_squared == const_builder.zero() {
2292                continue;
2293            }
2294            let loop_dot_shift = &unit_loop_part * three_shift;
2295
2296            debug!(
2297                "norm unit loop part squared: {}",
2298                norm_unit_loop_part_squared
2299            );
2300
2301            radius_guess += loop_dot_shift.abs() / &norm_unit_loop_part_squared;
2302            debug!("current radius guess: {}", radius_guess);
2303            denominator += norm_unit_loop_part_squared.sqrt();
2304            debug!("current denominator: {}", denominator);
2305        }
2306
2307        if denominator != const_builder.zero() {
2308            radius_guess += esurface_shift.abs() / denominator;
2309        }
2310        debug!("final radius guess: {}", radius_guess);
2311        let negative_radius = radius_guess.ref_neg();
2312        (radius_guess, negative_radius)
2313    }
2314
2315    /// A ray with no radial dependence has no isolated root and returns zero guesses.
2316    pub(crate) fn get_radius_guess<T: FloatLike>(
2317        &self,
2318        unit_loops: &LoopMomenta<F<T>>,
2319        external_moms: &ExternalFourMomenta<F<T>>,
2320        lmb: &LoopMomentumBasis,
2321    ) -> (F<T>, F<T>) {
2322        let shift = self.compute_shift_part_from_momenta(external_moms, lmb);
2323        //println!("got to energy loop");
2324        let terms = self.energies.iter().map(|&energy| {
2325            //println!("computing contribution for energy {:?}", energy);
2326            let signature = &lmb.edge_signatures[energy];
2327            //println!("signature {:?}", signature);
2328            let unit_loop_part = compute_loop_part(&signature.internal, unit_loops);
2329            //println!("computed_loop_part {:?}", unit_loop_part);
2330            let shift = compute_shift_part(&signature.external, external_moms).spatial;
2331            //./bprintln!("computed_shift {:?}", shift);
2332            (unit_loop_part.norm_squared(), &unit_loop_part * shift)
2333        });
2334        // Existing CT callers intentionally estimate with true external offsets,
2335        // even when their later root evaluation uses a nonzero overlap center.
2336        let guess = Self::radius_guess_from_terms(&shift, terms);
2337        let negative = guess.ref_neg();
2338        (guess, negative)
2339    }
2340
2341    fn radius_guess_from_terms<T: FloatLike>(
2342        shift: &F<T>,
2343        terms: impl Iterator<Item = (F<T>, F<T>)>,
2344    ) -> F<T> {
2345        let zero = shift.zero();
2346        let mut guess = zero.clone();
2347        let mut denominator = zero.clone();
2348        // Each pair is (|v|^2, v.b); LU supplies its represented ray, while
2349        // non-LU callers preserve the existing external-only seed convention.
2350        for (norm_squared, dot_offset) in terms {
2351            // Constant energies have no directional contribution to the radius estimate.
2352            if norm_squared == zero {
2353                continue;
2354            }
2355            guess += dot_offset.abs() / &norm_squared;
2356            denominator += norm_squared.sqrt();
2357        }
2358        if denominator != zero {
2359            guess += shift.abs() / denominator;
2360        }
2361        guess
2362    }
2363
2364    pub(crate) fn canonicalize_shift(&mut self, shift_rewrite: &ShiftRewrite) {
2365        if let Some(dep_mom_pos) = self
2366            .external_shift
2367            .iter()
2368            .position(|(index, _)| *index == shift_rewrite.dependent_momentum)
2369        {
2370            let (_, dep_mom_sign) = self.external_shift.remove(dep_mom_pos);
2371
2372            let external_shift = shift_rewrite
2373                .dependent_momentum_expr
2374                .iter()
2375                .map(|(index, sign)| (*index, dep_mom_sign * sign))
2376                .collect();
2377
2378            self.external_shift = add_external_shifts(&self.external_shift, &external_shift);
2379        }
2380    }
2381
2382    pub(crate) fn new_from_cut_left<E, V, H>(
2383        graph: &HedgeGraph<E, V, H>,
2384        cut: &CrossSectionCut,
2385        initial_state_cut: Option<&OrientedCut>,
2386    ) -> Self {
2387        let edges = graph
2388            .iter_edges_of(&cut.cut)
2389            .map(|(_, id, _)| id)
2390            .sorted()
2391            .collect();
2392
2393        let external_shift = if let Some(is_cut) = initial_state_cut {
2394            graph
2395                .iter_edges_of(is_cut)
2396                .map(|(_, edge_index, __)| (edge_index, -1))
2397                .sorted_by(|a, b| a.0.cmp(&b.0))
2398                .collect()
2399        } else {
2400            graph
2401                .iter_edges_of(&cut.left)
2402                .filter_map(|(hedge_pair, edge_index, _)| match hedge_pair {
2403                    HedgePair::Unpaired { flow, .. } => match flow {
2404                        Flow::Sink => Some((edge_index, -1)),
2405                        Flow::Source => Some((edge_index, 1)),
2406                    },
2407                    _ => None,
2408                })
2409                .sorted_by(|a, b| a.0.cmp(&b.0))
2410                .collect()
2411        };
2412
2413        let vertex_set = graph
2414            .iter_nodes_of(&cut.left)
2415            .map(|(node_id, _, _)| VertexSet::from_usize(node_id.into()))
2416            .reduce(|acc, v| acc.join(&v))
2417            .unwrap();
2418
2419        Self {
2420            energies: edges,
2421            external_shift,
2422            vertex_set,
2423            //subspace_graph: graph.full_graph(),
2424        }
2425    }
2426
2427    pub(crate) fn lmb_atom(&self, graph: &Graph, lmb_reps: &[Replacement]) -> Atom {
2428        self.energies
2429            .iter()
2430            .map(|index| {
2431                let mass_symbol = graph.underlying[*index].mass_atom();
2432                let emr_symbols = (0..3)
2433                    .map(|i| function!(GS.emr_mom, usize::from(*index), i + 1))
2434                    .collect_vec();
2435
2436                (&emr_symbols[0] * &emr_symbols[0]
2437                    + &emr_symbols[1] * &emr_symbols[1]
2438                    + &emr_symbols[2] * &emr_symbols[2]
2439                    + &mass_symbol * &mass_symbol)
2440                    .sqrt()
2441            })
2442            .chain(self.external_shift.iter().map(|(index, sign)| {
2443                function!(GS.emr_mom, usize::from(*index), 0) * Atom::num(*sign)
2444            }))
2445            .reduce(|sum, atom| sum + atom)
2446            .unwrap_or_else(Atom::new)
2447            .replace_multiple(lmb_reps)
2448            .replace(parse!("ZERO"))
2449            .with(Atom::new())
2450            .expand() // ensure canonical form
2451    }
2452
2453    // more readable version for debugging, because it doesn't write out components
2454    pub(crate) fn lmb_atom_simplified(&self, graph: &Graph, lmb_reps: &[Replacement]) -> Atom {
2455        self.energies
2456            .iter()
2457            .map(|index| {
2458                let mass_symbol = graph.underlying[*index].mass_atom();
2459                let emr_symbol = function!(GS.emr_mom, usize::from(*index));
2460
2461                (&emr_symbol * &emr_symbol + &mass_symbol * &mass_symbol).sqrt()
2462            })
2463            .chain(self.external_shift.iter().map(|(index, sign)| {
2464                function!(GS.emr_mom, usize::from(*index), 0) * Atom::num(*sign)
2465            }))
2466            .reduce(|sum, atom| sum + atom)
2467            .unwrap_or_else(Atom::new)
2468            .replace_multiple(lmb_reps)
2469            .replace(parse!("ZERO"))
2470            .with(Atom::new())
2471            .expand() // ensure canonical form
2472    }
2473}
2474
2475define_index! {pub struct GroupEsurfaceId;}
2476
2477pub type EsurfaceCollection = TiVec<EsurfaceID, Esurface>;
2478
2479pub type EsurfaceCache<T> = TiVec<EsurfaceID, T>;
2480
2481/// Container for esurfaces that exist at a given point in the phase space
2482pub type ExistingEsurfaces = TiVec<ExistingEsurfaceId, GroupEsurfaceId>;
2483pub type ExistingThresholds = TiVec<ExistingEsurfaceId, EsurfaceID>;
2484
2485pub(crate) fn get_representative<T: Copy>(
2486    esurface_map: &TiVec<GraphGroupPosition, Option<T>>,
2487) -> Result<(GraphGroupPosition, T)> {
2488    for (group_pos, esurface_option) in esurface_map.iter_enumerated() {
2489        if let Some(esurface_id) = esurface_option {
2490            return Ok((group_pos, *esurface_id));
2491        }
2492    }
2493
2494    Err(eyre!(
2495        "No representative esurface found, esurface map corrupted"
2496    ))
2497}
2498
2499/// Index in the list of all existing esurfaces, essentially a pointer to a pointer to an esurface
2500#[derive(
2501    Debug,
2502    From,
2503    Into,
2504    Copy,
2505    Clone,
2506    Serialize,
2507    Deserialize,
2508    PartialEq,
2509    Eq,
2510    Hash,
2511    PartialOrd,
2512    Ord,
2513    Encode,
2514    Decode,
2515)]
2516pub struct ExistingEsurfaceId(usize);
2517
2518impl Display for ExistingEsurfaceId {
2519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2520        write!(f, "ExistingEsurfaceID({})", self.0)
2521    }
2522}
2523
2524pub type ExternalShift = Vec<(EdgeIndex, i64)>;
2525
2526/// add two external shifts, eliminates zero signs and sorts
2527pub(crate) fn add_external_shifts(lhs: &ExternalShift, rhs: &ExternalShift) -> ExternalShift {
2528    let mut res = lhs.clone();
2529
2530    for rhs_element in rhs.iter() {
2531        if let Some(lhs_element) = res
2532            .iter_mut()
2533            .find(|lhs_element| rhs_element.0 == lhs_element.0)
2534        {
2535            lhs_element.1 += rhs_element.1;
2536        } else {
2537            res.push(*rhs_element)
2538        }
2539    }
2540
2541    res.retain(|(_index, sign)| *sign != 0);
2542    res.sort_by_key(|(index, _)| *index);
2543    res
2544}
2545
2546define_index!(
2547    pub struct RaisedEsurfaceId;
2548);
2549
2550#[derive(Debug, Clone, Encode, Decode)]
2551#[trait_decode(trait = GammaLoopContext)]
2552pub struct RaisedEsurfaceData {
2553    pub raised_groups: TiVec<RaisedEsurfaceId, RaisedEsurfaceGroup>,
2554    pub pass_two_evaluator: Option<Vec<GenericEvaluator>>,
2555}
2556
2557#[derive(Debug, Clone, Encode, Decode, PartialEq, Hash, Eq, PartialOrd, Ord)]
2558pub struct RaisedEsurfaceGroup {
2559    pub esurface_ids: Vec<EsurfaceID>,
2560    pub max_occurence: usize,
2561}
2562
2563impl RaisedEsurfaceGroupView for RaisedEsurfaceGroup {
2564    fn esurface_ids(&self) -> &[EsurfaceID] {
2565        &self.esurface_ids
2566    }
2567
2568    fn max_occurrence(&self) -> usize {
2569        self.max_occurence
2570    }
2571}
2572
2573impl RaisedEsurfaceDataView for RaisedEsurfaceData {
2574    fn for_each_raised_group(&self, mut f: impl FnMut(&dyn RaisedEsurfaceGroupView)) {
2575        for group in &self.raised_groups {
2576            f(group);
2577        }
2578    }
2579}
2580
2581impl Graph {
2582    pub(crate) fn normalize_esurface_with_raised_edge_groups(
2583        esurface: &Esurface,
2584        raised_edges: &[Vec<EdgeIndex>],
2585    ) -> Esurface {
2586        let mut normalized = esurface.clone();
2587        for energy in normalized.energies.iter_mut() {
2588            if let Some(representative) = raised_edges
2589                .iter()
2590                .find(|group| group.contains(energy))
2591                .and_then(|group| group.first())
2592            {
2593                *energy = *representative;
2594            }
2595        }
2596        normalized.energies.sort();
2597        normalized
2598    }
2599
2600    pub(crate) fn determine_raised_esurfaces_from_expression(
2601        &self,
2602        expr: &CFFExpression<OrientationID>,
2603    ) -> RaisedEsurfaceData {
2604        let raised_edges = self.get_raised_edge_groups();
2605        let normalized_cut_esurfaces = expr
2606            .surfaces
2607            .esurface_cache
2608            .iter()
2609            .map(|esurface| {
2610                Self::normalize_esurface_with_raised_edge_groups(esurface, &raised_edges)
2611            })
2612            .collect::<TiVec<EsurfaceID, _>>();
2613
2614        let mut raised_groups = TiVec::<RaisedEsurfaceId, RaisedEsurfaceGroup>::new();
2615
2616        for (esurface_id, normalized_cut_esurface) in normalized_cut_esurfaces.iter_enumerated() {
2617            let raised_esurface_group_id = raised_groups.iter_enumerated().find_map(
2618                |(raised_esurface_group_id, esurface_group)| {
2619                    if esurface_group
2620                        .esurface_ids
2621                        .iter()
2622                        .all(|esurface_id_in_group| {
2623                            normalized_cut_esurfaces[*esurface_id_in_group].energies
2624                                == normalized_cut_esurface.energies
2625                                && normalized_cut_esurfaces[*esurface_id_in_group].external_shift
2626                                    == normalized_cut_esurface.external_shift
2627                        })
2628                    {
2629                        Some(raised_esurface_group_id)
2630                    } else {
2631                        None
2632                    }
2633                },
2634            );
2635
2636            if let Some(found_group_id) = raised_esurface_group_id {
2637                raised_groups[found_group_id].esurface_ids.push(esurface_id);
2638            } else {
2639                raised_groups.push(RaisedEsurfaceGroup {
2640                    esurface_ids: vec![esurface_id],
2641                    max_occurence: 0,
2642                });
2643            }
2644        }
2645
2646        let mut result = RaisedEsurfaceData {
2647            raised_groups,
2648            pass_two_evaluator: None,
2649        };
2650
2651        let mut expression_copy = expr.clone();
2652        expression_copy.normalize_wrt_all_raisings(&result);
2653
2654        for cut_group in result.raised_groups.iter_mut() {
2655            let representative_esurface_id = cut_group.esurface_ids[0];
2656
2657            let max_occurence_for_this_id = expression_copy
2658                .orientations
2659                .iter()
2660                .map(|orientation_expression| {
2661                    orientation_expression.max_effective_denominator_value_count_on_branch(
2662                        &crate::cff::surface::HybridSurfaceID::Esurface(representative_esurface_id),
2663                    )
2664                })
2665                .max()
2666                .unwrap_or(0);
2667
2668            cut_group.max_occurence = max_occurence_for_this_id;
2669        }
2670
2671        result
2672    }
2673}
2674
2675#[cfg(test)]
2676mod tests {
2677    use itertools::Itertools;
2678    use linnet::half_edge::HedgeGraph;
2679    use linnet::half_edge::builder::HedgeGraphBuilder;
2680    use linnet::half_edge::involution::{EdgeIndex, Flow, Orientation};
2681    use linnet::half_edge::nodestore::NodeStorageVec;
2682    use linnet::half_edge::subgraph::{SuBitGraph, SubSetLike};
2683    use symbolica::atom::{Atom, AtomCore};
2684    use symbolica::parse;
2685
2686    use crate::cff::VertexSet;
2687    use crate::graph::{Graph, LmbIndex, LoopMomentumBasis, parse::from_dot::IntoGraph};
2688    use crate::initialisation::test_initialise;
2689    use crate::integrands::process::SamplingMapAffine;
2690    use crate::momentum::{
2691        FourMomentum, ThreeMomentum,
2692        sample::{ExternalFourMomenta, LoopMomenta, SubspaceData},
2693        signature::LoopExtSignature,
2694    };
2695    use crate::processes::CrossSectionCut;
2696    use crate::{
2697        cff::{esurface::Esurface, generation::ShiftRewrite},
2698        dot,
2699        utils::{
2700            ArbPrec, DEFAULT_ESURFACE_EXISTENCE_THRESHOLD, ESURFACE_SHIFT_THRESHOLD, F, FloatLike,
2701            QuadFloat,
2702            newton_solver::{
2703                RadialRootDiagnostics, RadialRootIdentity, SafeguardedNewtonError,
2704                safeguarded_newton_iteration_and_derivative,
2705            },
2706            test_utils::dummy_hedge_graph,
2707        },
2708    };
2709    use typed_index_collections::ti_vec;
2710
2711    use super::{EsurfaceExistence, add_external_shifts};
2712
2713    #[test]
2714    fn canonical_ray_embedding_preserves_ordered_occurrences_and_native_coefficients() {
2715        use super::EsurfaceRay;
2716        use crate::utils::SamplingFloat;
2717
2718        fn check<T: FloatLike>(marker: F<T>) {
2719            let one = marker.one();
2720            let velocity = ThreeMomentum::new(marker.clone(), -marker.clone(), one.zero());
2721            let offset = ThreeMomentum::new(
2722                one.from_usize(2),
2723                &marker / one.from_usize(3),
2724                F(T::from_f64_exact_binary(-0.0)),
2725            );
2726            let repeated = (EdgeIndex(4), velocity.clone(), offset.clone(), one.clone());
2727            let ray = EsurfaceRay {
2728                energies: vec![
2729                    repeated.clone(),
2730                    (EdgeIndex(7), offset, velocity, marker.clone()),
2731                    repeated,
2732                ],
2733                shift: -marker.clone(),
2734            };
2735            let canonical = ray.to_arb_exact().unwrap();
2736            assert_eq!(
2737                canonical.energies.iter().map(|entry| entry.0).collect_vec(),
2738                [EdgeIndex(4), EdgeIndex(7), EdgeIndex(4)]
2739            );
2740            assert_eq!(canonical.energies[0], canonical.energies[2]);
2741            assert_eq!(canonical.energies[0].1.px, marker.to_arb_exact().unwrap());
2742            // Preserve the native scalar's zero convention; a Quad sum need
2743            // not retain the sign of the binary64 token used to construct it.
2744            assert_eq!(
2745                canonical.energies[0]
2746                    .2
2747                    .pz
2748                    .0
2749                    .mpfr_enclosure(1000)
2750                    .0
2751                    .is_sign_negative(),
2752                ray.energies[0].2.pz.0.into_f64().is_sign_negative()
2753            );
2754            let roundtrip = canonical.materialize::<T>().unwrap();
2755            assert_eq!(roundtrip.energies, ray.energies);
2756            assert_eq!(roundtrip.shift, ray.shift);
2757            for field in 0..4 {
2758                let mut invalid = ray.clone();
2759                let nonfinite = F(T::from_f64_exact_binary(f64::INFINITY));
2760                match field {
2761                    0 => invalid.energies[0].1.px = nonfinite,
2762                    1 => invalid.energies[0].2.py = nonfinite,
2763                    2 => invalid.energies[0].3 = nonfinite,
2764                    _ => invalid.shift = nonfinite,
2765                }
2766                assert!(invalid.to_arb_exact().is_err());
2767            }
2768        }
2769        let one = F::<QuadFloat>::default().one();
2770        check(one + one.from_i64(2).powi(-80));
2771        let one = F::<SamplingFloat>::default().one();
2772        check(&one + one.from_i64(2).powi(-180) + one.from_i64(2).powi(-240));
2773    }
2774
2775    #[test]
2776    fn joint_sampling_matches_distinct_reversed_edges_and_unequal_masses() {
2777        test_initialise().unwrap();
2778        // Splitting the kite's common line through E gives distinct edge IDs
2779        // with opposite complete affine routes, without altering its two cycles.
2780        let graph: Graph = dot!(digraph joint_serial_energy {
2781            ext_in [style=invis]
2782            ext_out [style=invis]
2783            node [num=1]
2784            edge [num=1 mass=1]
2785            ext_in -> A:0 [id=0 mass=0]
2786            C:1 -> ext_out [id=1 mass=0]
2787            A -> B [id=2 mass=2]
2788            B -> C [id=3]
2789            C -> D [id=4 lmb_id=0]
2790            D -> A [id=5 mass=3]
2791            B -> E [id=6 lmb_id=1]
2792            D -> E [id=7]
2793        })
2794        .unwrap();
2795        let lmbs = ti_vec![graph.loop_momentum_basis.clone()];
2796        let id = LmbIndex::from(0);
2797        let lmb = &lmbs[id];
2798        let subspace = SubspaceData::new_from_parent_basis_edges(
2799            &[EdgeIndex(6)],
2800            &graph.full_filter(),
2801            id,
2802            &graph,
2803            &lmbs,
2804        )
2805        .unwrap();
2806        let active = subspace.iter_lmb_indices().next().unwrap();
2807        let prior = lmb
2808            .loop_edges
2809            .iter_enumerated()
2810            .find_map(|(index, edge)| (*edge == EdgeIndex(4)).then_some(index))
2811            .unwrap();
2812        let left = Esurface {
2813            energies: vec![EdgeIndex(2), EdgeIndex(4), EdgeIndex(6)],
2814            external_shift: vec![(EdgeIndex(0), -1)],
2815            vertex_set: VertexSet::dummy(),
2816        };
2817        let right = Esurface {
2818            energies: vec![EdgeIndex(3), EdgeIndex(5), EdgeIndex(7)],
2819            ..left.clone()
2820        };
2821        let masses = graph
2822            .underlying
2823            .new_edgevec_from_iter([0., 0., 2., 1., 1., 3., 1., 1.].map(F))
2824            .unwrap();
2825        let externals = ExternalFourMomenta::from_iter(
2826            [FourMomentum::from_args(F(26_f64.sqrt()), F(0.), F(0.), F(1.)); 2],
2827        );
2828        let (prepare, common) = left
2829            .sampling_joint_geometry_in_subspace(
2830                &right,
2831                &subspace,
2832                &lmbs,
2833                &graph,
2834                &masses,
2835                &externals,
2836                &[prior],
2837            )
2838            .unwrap();
2839        let geometry = prepare(&[1., 0., -0.5]).unwrap();
2840        assert_eq!(geometry.masses, [1., 2., 3.]);
2841        assert_eq!(geometry.shifts, [[1., 0., 0.5], [1., 0., -0.5]]);
2842        assert_ne!(
2843            lmb.edge_signatures[EdgeIndex(6)],
2844            lmb.edge_signatures[EdgeIndex(7)]
2845        );
2846        assert!(lmb.edges_are_raised(EdgeIndex(6), EdgeIndex(7)));
2847        assert_eq!(common, lmb.edge_signatures[EdgeIndex(6)]);
2848        let mut loops = LoopMomenta::from_iter([ThreeMomentum::new(F(0.), F(0.), F(0.)); 2]);
2849        loops[prior] = ThreeMomentum::new(F(1.), F(0.), F(-0.5));
2850        for components in [[0.5, -1., 1.], [-0.7, 0.3, -0.4]] {
2851            let x = ThreeMomentum::new(F(components[0]), F(components[1]), F(components[2]));
2852            loops[active] = x;
2853            let common_energy = (x.norm_squared() + F(1.)).sqrt();
2854            for (i, surface) in [&left, &right].into_iter().enumerate() {
2855                let [a, b, c] = geometry.shifts[i];
2856                let partner = x + ThreeMomentum::new(F(a), F(b), F(c));
2857                let expected = common_energy
2858                    + (partner.norm_squared() + F(geometry.masses[i + 1]).square()).sqrt()
2859                    - F(geometry.energy_sums[i]);
2860                assert!(
2861                    (surface.compute_from_momenta(lmb, &masses, &loops, &externals) - expected)
2862                        .abs()
2863                        < F(1e-12)
2864                );
2865            }
2866        }
2867        // Fixed energy multiplicities remain part of the original equation.
2868        let mut repeated = left.clone();
2869        repeated.energies.push(EdgeIndex(4));
2870        let (prepare_repeated, _) = repeated
2871            .sampling_joint_geometry_in_subspace(
2872                &right,
2873                &subspace,
2874                &lmbs,
2875                &graph,
2876                &masses,
2877                &externals,
2878                &[prior],
2879            )
2880            .unwrap();
2881        assert!(
2882            (prepare_repeated(&[1., 0., -0.5]).unwrap().energy_sums[0]
2883                - (geometry.energy_sums[0] - 1.5))
2884                .abs()
2885                < 1e-12
2886        );
2887
2888        let mut inconsistent_masses = masses.clone();
2889        inconsistent_masses[EdgeIndex(7)] = F(2.);
2890        assert!(
2891            left.sampling_joint_geometry_in_subspace(
2892                &right,
2893                &subspace,
2894                &lmbs,
2895                &graph,
2896                &inconsistent_masses,
2897                &externals,
2898                &[prior],
2899            )
2900            .err()
2901            .unwrap()
2902            .to_string()
2903            .contains("inconsistent native values")
2904        );
2905        // Changing only the symbolic mass distinguishes formal identity from
2906        // accidentally equal numeric values in a supplied mass cache.
2907        let mut different_mass_graph = graph.clone();
2908        different_mass_graph.underlying[EdgeIndex(7)].particle =
2909            crate::graph::edge::PossibleParticle::JustMass { expr: parse!("2") };
2910        assert!(
2911            left.sampling_joint_geometry_in_subspace(
2912                &right,
2913                &subspace,
2914                &lmbs,
2915                &different_mass_graph,
2916                &masses,
2917                &externals,
2918                &[prior],
2919            )
2920            .err()
2921            .unwrap()
2922            .to_string()
2923            .contains("found 0 candidates")
2924        );
2925    }
2926
2927    #[test]
2928    fn joint_sampling_matches_exact_external_cm_relation_only() {
2929        test_initialise().unwrap();
2930        // The two extra external ports between the distinct common-energy
2931        // edges sum to zero spatially in CM, but carry nonzero total energy.
2932        let graph: Graph = dot!(digraph joint_external_energy {
2933            ext_in [style=invis]
2934            ext_out [style=invis]
2935            beam_a [style=invis]
2936            beam_b [style=invis]
2937            node [num=1]
2938            edge [num=1 mass=1]
2939            ext_in -> A:0 [id=0 mass=0]
2940            C:1 -> ext_out [id=1 mass=0]
2941            A -> B [id=2 mass=2]
2942            B -> C [id=3]
2943            C -> D [id=4 lmb_id=0]
2944            D -> A [id=5 mass=3]
2945            B -> E [id=6 lmb_id=1]
2946            D -> E [id=7]
2947            beam_a -> E [id=8 mass=0]
2948            beam_b -> E [id=9 mass=0]
2949        })
2950        .unwrap();
2951        fn check<T: FloatLike>(graph: &Graph) {
2952            let one = F::<T>::default().one();
2953            let zero = one.zero();
2954            let lmbs = ti_vec![graph.loop_momentum_basis.clone()];
2955            let id = LmbIndex::from(0);
2956            let lmb = &lmbs[id];
2957            let subspace = SubspaceData::new_from_parent_basis_edges(
2958                &[EdgeIndex(6)],
2959                &graph.full_filter(),
2960                id,
2961                graph,
2962                &lmbs,
2963            )
2964            .unwrap();
2965            let active = subspace.iter_lmb_indices().next().unwrap();
2966            let prior = lmb
2967                .loop_edges
2968                .iter_enumerated()
2969                .find_map(|(index, edge)| (*edge == EdgeIndex(4)).then_some(index))
2970                .unwrap();
2971            let left = Esurface {
2972                energies: vec![EdgeIndex(2), EdgeIndex(4), EdgeIndex(6)],
2973                external_shift: vec![(EdgeIndex(0), -1)],
2974                vertex_set: VertexSet::dummy(),
2975            };
2976            let right = Esurface {
2977                energies: vec![EdgeIndex(3), EdgeIndex(5), EdgeIndex(7)],
2978                ..left.clone()
2979            };
2980            let masses = graph
2981                .underlying
2982                .new_edgevec_from_iter(
2983                    [0, 0, 2, 1, 1, 3, 1, 1, 0, 0].map(|mass| one.from_usize(mass)),
2984                )
2985                .unwrap();
2986            assert!(
2987                !lmb.edge_signatures[EdgeIndex(6)]
2988                    .equality_up_to_sign(&lmb.edge_signatures[EdgeIndex(7)])
2989            );
2990            assert!(!lmb.edges_are_raised(EdgeIndex(6), EdgeIndex(7)));
2991            for boosted in [false, true] {
2992                let externals = ExternalFourMomenta::from_iter(lmb.ext_edges.iter().map(|edge| {
2993                    let (energy, z) = match edge.0 {
2994                        0 => (20, 0),
2995                        1 => (26, 0),
2996                        8 => (3, 2),
2997                        9 => (3, -2),
2998                        _ => unreachable!(),
2999                    };
3000                    let (energy, z) = (one.from_i64(energy), one.from_i64(z));
3001                    // Exact rational Lorentz boost: gamma=5/4, gamma*v=3/4.
3002                    let (energy, z) = if boosted {
3003                        (
3004                            (one.from_i64(5) * &energy + one.from_i64(3) * &z) / one.from_i64(4),
3005                            (one.from_i64(3) * energy + one.from_i64(5) * z) / one.from_i64(4),
3006                        )
3007                    } else {
3008                        (energy, z)
3009                    };
3010                    FourMomentum::from_args(energy, zero.clone(), zero.clone(), z)
3011                }));
3012                let matched = left.sampling_joint_geometry_in_subspace(
3013                    &right,
3014                    &subspace,
3015                    &lmbs,
3016                    graph,
3017                    &masses,
3018                    &externals,
3019                    &[prior],
3020                );
3021                if boosted {
3022                    assert!(
3023                        matched
3024                            .err()
3025                            .unwrap()
3026                            .to_string()
3027                            .contains("found 0 candidates")
3028                    );
3029                    continue;
3030                }
3031                let (prepare, common) = matched.unwrap();
3032                let prior_values = [one.clone(), zero.clone(), -(&one / one.from_i64(2))];
3033                let geometry = prepare(&prior_values.clone().map(|x| x.0)).unwrap();
3034                assert_eq!(
3035                    geometry.masses,
3036                    [one.0.clone(), one.from_i64(2).0, one.from_i64(3).0]
3037                );
3038                let mut loops = LoopMomenta::from_iter(
3039                    (0..2).map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
3040                );
3041                loops[prior] = ThreeMomentum::new(
3042                    prior_values[0].clone(),
3043                    prior_values[1].clone(),
3044                    prior_values[2].clone(),
3045                );
3046                let spatial: crate::momentum::sample::ExternalThreeMomenta<F<T>> =
3047                    externals.iter().map(|p| p.spatial.clone()).collect();
3048                for values in [[1, -2, 3], [-3, 1, -1]] {
3049                    let values = values.map(|value| one.from_i64(value) / one.from_i64(4));
3050                    loops[active] =
3051                        ThreeMomentum::new(values[0].clone(), values[1].clone(), values[2].clone());
3052                    let x: ThreeMomentum<F<T>> = common.compute_momentum(&loops, &spatial);
3053                    let e0 = (x.norm_squared() + F(geometry.masses[0].clone()).square()).sqrt();
3054                    for (index, surface) in [&left, &right].into_iter().enumerate() {
3055                        let shift = geometry.shifts[index].each_ref().map(|x| F(x.clone()));
3056                        let partner = &x
3057                            + &ThreeMomentum::new(
3058                                shift[0].clone(),
3059                                shift[1].clone(),
3060                                shift[2].clone(),
3061                            );
3062                        let expected = &e0
3063                            + (partner.norm_squared()
3064                                + F(geometry.masses[index + 1].clone()).square())
3065                            .sqrt()
3066                            - F(geometry.energy_sums[index].clone());
3067                        let original =
3068                            surface.compute_from_momenta(lmb, &masses, &loops, &externals);
3069                        assert!(
3070                            (original - expected).abs() < one.epsilon().sqrt() * one.from_i64(100)
3071                        );
3072                    }
3073                }
3074                // External temporal shifts and every fixed occurrence survive
3075                // the spatial alias; no on-cut energy substitution is made.
3076                let fixed: ThreeMomentum<F<T>> =
3077                    lmb.edge_signatures[EdgeIndex(4)].compute_momentum(&loops, &spatial);
3078                let fixed_energy = (fixed.norm_squared() + masses[EdgeIndex(4)].square()).sqrt();
3079                assert!(
3080                    (F(geometry.energy_sums[0].clone())
3081                        + fixed_energy
3082                        + left.compute_shift_part_from_momenta(&externals, lmb))
3083                    .abs()
3084                        < one.epsilon().sqrt() * one.from_i64(100)
3085                );
3086            }
3087        }
3088        check::<f64>(&graph);
3089        check::<QuadFloat>(&graph);
3090        check::<ArbPrec>(&graph);
3091    }
3092
3093    #[test]
3094    fn routed_enclosure_preserves_original_terms_and_native_rescale() {
3095        use super::{EsurfaceRay, SamplingEvaluationError};
3096        use crate::momentum::sample::LoopIndex;
3097        use rug::Float;
3098
3099        fn check<T: FloatLike>() {
3100            let one = F::<T>::default().one();
3101            let zero = one.zero();
3102            let graph = dummy_hedge_graph(11);
3103            let mut routes = (0..5)
3104                .map(|i| {
3105                    let mut internal = vec![0; 5];
3106                    internal[i] = 1;
3107                    LoopExtSignature::from((internal, vec![0; 2]))
3108                })
3109                .collect::<Vec<_>>();
3110            routes.extend([
3111                LoopExtSignature::from((vec![1, 1, 1, -1, -1], vec![1, 0])),
3112                LoopExtSignature::from((vec![0; 5], vec![0, 1])),
3113                LoopExtSignature::from((vec![0; 5], vec![0, 0])),
3114                LoopExtSignature::from((vec![0; 5], vec![1, -1])),
3115                LoopExtSignature::from((vec![0; 5], vec![1, 0])),
3116                LoopExtSignature::from((vec![0; 5], vec![0, 1])),
3117            ]);
3118            let lmb = LoopMomentumBasis {
3119                tree: SuBitGraph::empty(0),
3120                loop_edges: (0..5).map(EdgeIndex).collect(),
3121                ext_edges: vec![EdgeIndex(9), EdgeIndex(10)].into(),
3122                edge_signatures: graph.new_edgevec_from_iter(routes).unwrap(),
3123            };
3124            let big = one.clone() / one.epsilon().square();
3125            let middle = big.sqrt();
3126            let vector = |x| ThreeMomentum::new(x, zero.clone(), zero.clone());
3127            // The complete signed route is exactly one. Three separated
3128            // scales also expose cancellation in a two-limb Quad sum.
3129            let loops = LoopMomenta::from_iter(
3130                [big.clone(), middle.clone(), one.clone(), middle, big].map(&vector),
3131            );
3132            let externals = ExternalFourMomenta::from_iter([
3133                FourMomentum::from_args(
3134                    one.from_i64(7),
3135                    one.from_i64(3),
3136                    zero.clone(),
3137                    zero.clone(),
3138                ),
3139                FourMomentum::from_args(
3140                    one.from_i64(11),
3141                    one.from_i64(4),
3142                    zero.clone(),
3143                    zero.clone(),
3144                ),
3145            ]);
3146            let mut masses = graph
3147                .new_edgevec_from_iter(vec![one.from_i64(3); 11])
3148                .unwrap();
3149            masses[EdgeIndex(7)] = zero.clone();
3150            let surface = Esurface {
3151                energies: vec![EdgeIndex(5), EdgeIndex(5), EdgeIndex(6), EdgeIndex(7)],
3152                external_shift: vec![(EdgeIndex(8), 3), (EdgeIndex(6), -1)],
3153                vertex_set: VertexSet::dummy(),
3154            };
3155            // Two sqrt(4^2+3^2), one fixed sqrt(4^2+3^2), one exact
3156            // massless zero, and shift 3*(7-11)-11 give exactly -8.
3157            let bounds = surface
3158                .evaluate_routed_enclosed(&one, &loops, &externals, &masses, &lmb)
3159                .unwrap();
3160            assert_eq!(
3161                bounds,
3162                [Float::with_val(2048, -8), Float::with_val(2048, -8)]
3163            );
3164            let center = LoopMomenta::from_iter((0..5).map(|_| vector(zero.clone())));
3165            let represented = surface.routed_ray(&loops, &center, &externals, &masses, &lmb);
3166            assert_eq!(represented.energies[0].1.px, zero);
3167            let old_value = represented.evaluate(&one).0;
3168            assert!(
3169                old_value < one.from_i64(-9),
3170                "rounded routing must fail the exact -8 oracle"
3171            );
3172
3173            // Include the physical scalar multiplication itself: evaluating
3174            // exact tau*K is different from evaluating the rounded rescale.
3175            let tau = &one + one.epsilon();
3176            let mut loops = center;
3177            loops[LoopIndex(0)] = vector(tau.clone());
3178            let scalar = Esurface {
3179                energies: vec![EdgeIndex(0)],
3180                external_shift: vec![],
3181                vertex_set: VertexSet::dummy(),
3182            };
3183            masses[EdgeIndex(0)] = zero.clone();
3184            let mathematical = scalar
3185                .evaluate_routed_enclosed(&tau, &loops, &externals, &masses, &lmb)
3186                .unwrap();
3187            let completed = loops.rescale(&tau, None);
3188            let actual = scalar
3189                .evaluate_routed_enclosed(&one, &completed, &externals, &masses, &lmb)
3190                .unwrap();
3191            assert!(
3192                mathematical[0] > actual[1],
3193                "the omitted epsilon^2 term must be resolved"
3194            );
3195            let actual_native = EsurfaceRay::<T>::enclosed_native(&completed[LoopIndex(0)].px);
3196            assert!(actual[0] <= actual_native[0] && actual[1] >= actual_native[1]);
3197
3198            let missing = LoopMomenta::from_iter(loops.iter().take(4).cloned());
3199            assert!(
3200                scalar
3201                    .evaluate_routed_enclosed(&one, &missing, &externals, &masses, &lmb)
3202                    .is_err()
3203            );
3204            loops[LoopIndex(0)].px = zero.clone() / zero.clone();
3205            assert!(matches!(
3206                scalar
3207                    .evaluate_routed_enclosed(&one, &loops, &externals, &masses, &lmb)
3208                    .unwrap_err()
3209                    .downcast_ref::<SamplingEvaluationError>(),
3210                Some(SamplingEvaluationError::Unrepresentable { .. })
3211            ));
3212        }
3213        check::<f64>();
3214        check::<QuadFloat>();
3215        check::<ArbPrec>();
3216    }
3217
3218    #[test]
3219    fn hosted_normal_alignment_enforces_directed_half_budgets() {
3220        use super::{EsurfaceRay, SamplingEvaluationError};
3221        use rug::Float;
3222        let point = |value: i32| {
3223            let x = Float::with_val(2048, value);
3224            [x.clone(), x]
3225        };
3226        let canonical = [point(3), point(4)];
3227        let half_budget: Float = Float::with_val(2048, 5) / 16;
3228        let shifted: Float = Float::with_val(2048, 3) + &half_budget;
3229        let native = [[shifted.clone(), shifted], point(4)];
3230        let host = [-half_budget.clone(), -half_budget.clone()];
3231        // R=5 and epsilon=1/8 allocate exactly 5/16 to each check.
3232        EsurfaceRay::<f64>::verify_normal_alignment(
3233            canonical.clone(),
3234            native.clone(),
3235            host.clone(),
3236            0.125,
3237        )
3238        .unwrap();
3239        let beyond: Float = Float::with_val(2048, 1) >> 1000;
3240        let mut bad_normal = native;
3241        bad_normal[0][1] += &beyond;
3242        let mut bad_host = host;
3243        bad_host[0] -= beyond;
3244        for (native, host) in [(bad_normal, point(0)), (canonical.clone(), bad_host)] {
3245            assert!(matches!(
3246                EsurfaceRay::<f64>::verify_normal_alignment(canonical.clone(), native, host, 0.125)
3247                    .unwrap_err()
3248                    .downcast_ref::<SamplingEvaluationError>(),
3249                Some(SamplingEvaluationError::UncertainGeometry { .. })
3250            ));
3251        }
3252        for canonical in [
3253            [point(0), point(0)],
3254            [
3255                [Float::with_val(2048, -1), Float::with_val(2048, 1)],
3256                point(0),
3257            ],
3258        ] {
3259            assert!(matches!(
3260                EsurfaceRay::<f64>::verify_normal_alignment(
3261                    canonical.clone(),
3262                    canonical,
3263                    point(0),
3264                    0.125
3265                )
3266                .unwrap_err()
3267                .downcast_ref::<SamplingEvaluationError>(),
3268                Some(SamplingEvaluationError::UncertainGeometry { .. })
3269            ));
3270        }
3271        assert!(
3272            EsurfaceRay::<f64>::verify_normal_alignment(
3273                canonical.clone(),
3274                canonical.clone(),
3275                point(0),
3276                0.0
3277            )
3278            .is_err()
3279        );
3280        assert!(matches!(
3281            EsurfaceRay::<f64>::verify_normal_alignment(
3282                canonical.clone(),
3283                canonical,
3284                std::array::from_fn(|_| Float::with_val(2048, rug::float::Special::Infinity)),
3285                0.125
3286            )
3287            .unwrap_err()
3288            .downcast_ref::<SamplingEvaluationError>(),
3289            Some(SamplingEvaluationError::Unrepresentable { .. })
3290        ));
3291    }
3292
3293    #[test]
3294    fn retained_lu_candidate_requires_directed_native_agreement() {
3295        use super::EsurfaceRay;
3296        use crate::{
3297            integrands::process::sampling::maps::SamplingEvaluationError,
3298            momentum::{Rotatable, Rotation, RotationMethod},
3299        };
3300        fn check<T: FloatLike>() {
3301            let one = F::<T>::default().one();
3302            let f = |n| one.from_usize(n);
3303            let vector = |x| ThreeMomentum::new(x, one.zero(), one.zero());
3304            // eta(t)=sqrt((3t+1)^2+9)-6, whose positive root is
3305            // (sqrt(27)-1)/3. The independent radical is not a Newton result.
3306            let ray = EsurfaceRay {
3307                energies: vec![(EdgeIndex(1), vector(f(3)), vector(one.clone()), f(3))],
3308                shift: -f(6),
3309            };
3310            let solution = ray
3311                .solve_lu_cut(
3312                    &f(6),
3313                    &mut RadialRootDiagnostics::default(),
3314                    &RadialRootIdentity::new("directed native host fixture".into()),
3315                )
3316                .unwrap();
3317            let expected = (f(27).sqrt() - &one) / f(3);
3318            assert!((&solution.solution - expected).abs() < one.epsilon() * f(100));
3319            let budget = &one / f(1_000_000);
3320            ray.verify_lu_candidate(&ray, &solution, 6, &budget)
3321                .unwrap();
3322            let rotation = Rotation::new(RotationMethod::EulerAngles(0.31, -0.17, 0.23));
3323            ray.rotate(&rotation)
3324                .verify_lu_candidate(&ray, &solution, 6, &budget)
3325                .unwrap();
3326
3327            // The certificate encloses represented coefficients directly. A
3328            // shallow positive slope and tiny nonzero masses remain resolvable
3329            // even where native squaring would underflow; no Newton claim is
3330            // made for that underflowing native evaluator in this direct check.
3331            let tiny = &one / f(10).powi(200);
3332            let mut shallow = ray.clone();
3333            for (_, v, b, mass) in &mut shallow.energies {
3334                *v = &*v * &tiny;
3335                *b = &*b * &tiny;
3336                *mass *= &tiny;
3337            }
3338            shallow.shift *= &tiny;
3339            let mut shallow_solution = solution.clone();
3340            shallow_solution.derivative_at_solution *= &tiny;
3341            shallow_solution.error_of_function *= &tiny;
3342            shallow
3343                .verify_lu_candidate(&shallow, &shallow_solution, 6, &budget)
3344                .unwrap();
3345            let mut tiny_energy = ray.clone();
3346            tiny_energy
3347                .energies
3348                .push((EdgeIndex(2), vector(one.zero()), vector(one.zero()), tiny));
3349            tiny_energy
3350                .verify_lu_candidate(&tiny_energy, &solution, 6, &budget)
3351                .unwrap();
3352            tiny_energy.energies[1].3 = one.zero();
3353            let error = tiny_energy
3354                .verify_lu_candidate(&tiny_energy, &solution, 6, &budget)
3355                .unwrap_err();
3356            assert!(
3357                matches!(
3358                    error.downcast_ref::<SamplingEvaluationError>(),
3359                    Some(SamplingEvaluationError::UncertainGeometry { .. })
3360                ),
3361                "{error:?}"
3362            );
3363            assert!(
3364                error
3365                    .to_string()
3366                    .contains("energy lower bound touches zero"),
3367                "{error:?}"
3368            );
3369
3370            let mut wrong = ray.clone();
3371            wrong.shift -= &one / f(100);
3372            let error = ray
3373                .verify_lu_candidate(&wrong, &solution, 6, &budget)
3374                .unwrap_err();
3375            assert!(
3376                matches!(
3377                    error.downcast_ref::<SamplingEvaluationError>(),
3378                    Some(SamplingEvaluationError::UncertainGeometry { .. })
3379                ),
3380                "{error:?}"
3381            );
3382            let mut wrong_derivative = solution.clone();
3383            wrong_derivative.derivative_at_solution *= f(11) / f(10);
3384            assert!(
3385                ray.verify_lu_candidate(&ray, &wrong_derivative, 6, &budget)
3386                    .is_err()
3387            );
3388            wrong = ray.clone();
3389            wrong.energies[0].0 = EdgeIndex(2);
3390            assert!(
3391                ray.verify_lu_candidate(&wrong, &solution, 6, &budget)
3392                    .unwrap_err()
3393                    .to_string()
3394                    .contains("ordered energy occurrences")
3395            );
3396            wrong = ray.clone();
3397            wrong.energies[0].3 = one.zero() / one.zero();
3398            assert!(matches!(
3399                ray.verify_lu_candidate(&wrong, &solution, 6, &budget)
3400                    .unwrap_err()
3401                    .downcast_ref::<SamplingEvaluationError>(),
3402                Some(SamplingEvaluationError::Unrepresentable { .. })
3403            ));
3404
3405            // One rounded residual value cannot certify this much smaller
3406            // budget. Native Quad/Arb resolve the same irrational root, while
3407            // binary64 must ask for original-source precision rescue.
3408            let tight = &one / f(10).powi(25);
3409            let tight_result = ray.verify_lu_candidate(&ray, &solution, 6, &tight);
3410            let large = f(10).powi(20);
3411            let cancellation = (&large + &one) - &large;
3412            let mut completed = ray.clone();
3413            completed.energies[0].1.px += &cancellation - &one;
3414            let cancellation_result = ray.verify_lu_candidate(&completed, &solution, 6, &budget);
3415            if T::sampling_precision() == crate::utils::SamplingPrecision::Double {
3416                assert_eq!(cancellation, one.zero());
3417                for result in [tight_result, cancellation_result] {
3418                    assert!(matches!(
3419                        result
3420                            .unwrap_err()
3421                            .downcast_ref::<SamplingEvaluationError>(),
3422                        Some(SamplingEvaluationError::UncertainGeometry { .. })
3423                    ));
3424                }
3425            } else {
3426                assert_eq!(cancellation, one);
3427                tight_result.unwrap();
3428                cancellation_result.unwrap();
3429            }
3430        }
3431        check::<f64>();
3432        check::<QuadFloat>();
3433        check::<ArbPrec>();
3434    }
3435
3436    #[test]
3437    fn radial_guesses_and_lu_roots_handle_constant_massive_energies() {
3438        test_initialise().unwrap();
3439        // A ttH cut with back-to-back tops and a Higgs at rest has the analytic root
3440        // sqrt(((Q-mH)/2)^2-mt^2)/|p|. The Higgs momentum vanishes by cancellation of
3441        // two nonzero basis momenta, not because its graph signature is constant.
3442        let graph: Graph = dot!(digraph massive_energy_at_rest {
3443            ext [style=invis]
3444            node [num=1]
3445            edge [num=1 mass=0]
3446            ext -> a:0 [id=0]
3447            a -> b [id=1 lmb_id=0]
3448            a -> b [id=2 lmb_id=1]
3449            a -> b [id=3]
3450            b:1 -> ext [id=4]
3451        })
3452        .unwrap();
3453        let lmbs = ti_vec![graph.loop_momentum_basis.clone()];
3454        let lmb = &lmbs[LmbIndex::from(0)];
3455        let subspace = SubspaceData::new_from_parent_basis_edges(
3456            &[EdgeIndex(1), EdgeIndex(2)],
3457            &graph.underlying.full_filter(),
3458            LmbIndex::from(0),
3459            &graph,
3460            &lmbs,
3461        )
3462        .unwrap();
3463        let surface = Esurface {
3464            energies: vec![EdgeIndex(1), EdgeIndex(2), EdgeIndex(3)],
3465            external_shift: vec![(EdgeIndex(0), -1)],
3466            vertex_set: VertexSet::dummy(),
3467        };
3468        let masses = graph
3469            .underlying
3470            .new_edgevec_from_iter([F(0.0), F(173.0), F(173.0), F(125.0), F(0.0)])
3471            .unwrap();
3472        // Supply both external ports as future-directed momenta; the graph's incoming and
3473        // outgoing edge flows provide their relative sign in momentum conservation.
3474        let externals = ExternalFourMomenta::from_iter(
3475            [FourMomentum::from_args(F(1000.0), F(0.0), F(0.0), F(0.0)); 2],
3476        );
3477        let center = LoopMomenta::from_iter([ThreeMomentum::new(F(0.0), F(0.0), F(0.0)); 2]);
3478
3479        for momentum in [0.0, 100.0] {
3480            let loops = LoopMomenta::from_iter([
3481                ThreeMomentum::new(F(momentum), F(0.0), F(0.0)),
3482                ThreeMomentum::new(F(-momentum), F(0.0), F(0.0)),
3483            ]);
3484            let guesses = [
3485                surface.get_radius_guess(&loops, &externals, lmb),
3486                surface.get_radius_guess_subspace(
3487                    &loops, &externals, &subspace, &lmbs, &graph, &masses,
3488                ),
3489            ];
3490            for (positive, negative) in guesses {
3491                assert!(positive.0.is_finite());
3492                assert_eq!(negative, -positive);
3493                let result = safeguarded_newton_iteration_and_derivative(
3494                    &F(0.0),
3495                    &positive,
3496                    |radius| {
3497                        surface.compute_self_and_r_derivative(
3498                            radius, &loops, &center, &externals, &masses, lmb,
3499                        )
3500                    },
3501                    &F(1.0),
3502                    2000,
3503                    64,
3504                    &F(1000.0),
3505                );
3506                if momentum == 0.0 {
3507                    assert_eq!(positive, F(0.0));
3508                    // The constant surface is negative everywhere: no finite radial root
3509                    // exists. Reject the ray instead of propagating a NaN or inventing a root.
3510                    assert!(matches!(
3511                        result,
3512                        Err(SafeguardedNewtonError::InvalidOutside { .. })
3513                    ));
3514                } else {
3515                    let result = result.unwrap();
3516                    let top_energy = (1000.0_f64 - 125.0) / 2.0;
3517                    let expected = (top_energy.powi(2) - 173.0_f64.powi(2)).sqrt() / momentum;
3518                    assert!((result.solution.0 - expected).abs() < 1.0e-14);
3519                    assert!(result.derivative_at_solution.0 > 0.0);
3520                    assert!(result.error_of_function.0.abs() < 1.0e-12);
3521                }
3522            }
3523        }
3524
3525        fn check_native<T: FloatLike>(graph: &Graph, surface: &Esurface) {
3526            let one = F::<T>::default().one();
3527            let zero = one.zero();
3528            let f = |value| one.from_i64(value);
3529            let lmb = &graph.loop_momentum_basis;
3530            let mut masses = graph
3531                .underlying
3532                .new_edgevec_from_iter([0, 173, 173, 125, 0].map(f))
3533                .unwrap();
3534            let e_cm = f(1000);
3535            let externals = ExternalFourMomenta::from_iter((0..2).map(|_| {
3536                FourMomentum::from_args(e_cm.clone(), zero.clone(), zero.clone(), zero.clone())
3537            }));
3538            let zero_vector = ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone());
3539            let center = LoopMomenta::from_iter([zero_vector.clone(), zero_vector.clone()]);
3540            let perturbation = &one / f(10).powi(25);
3541            let p = f(100) + &perturbation;
3542            let loops = LoopMomenta::from_iter([
3543                ThreeMomentum::new(p.clone(), zero.clone(), zero.clone()),
3544                ThreeMomentum::new(-&p, zero.clone(), zero.clone()),
3545            ]);
3546            let identity = RadialRootIdentity::new("native shared LU host".into());
3547            let mut diagnostics = RadialRootDiagnostics::default();
3548            let mut prior_policy = diagnostics.clone();
3549            let (ray, result) = surface
3550                .solve_lu_cut(
3551                    &loops,
3552                    &externals,
3553                    &masses,
3554                    lmb,
3555                    &e_cm,
3556                    &mut diagnostics,
3557                    &identity,
3558                )
3559                .unwrap();
3560            // The extracted entry must preserve the existing physical policy on
3561            // identical routed inputs and histories, including diagnostic data.
3562            let guess = surface.get_radius_guess(&loops, &externals, lmb).0;
3563            assert_eq!(
3564                Esurface::radius_guess_from_terms(
3565                    &ray.shift,
3566                    ray.energies
3567                        .iter()
3568                        .map(|(_, v, b, _)| (v.norm_squared(), v.clone() * b)),
3569                ),
3570                guess
3571            );
3572            let prior = prior_policy
3573                .solve(
3574                    &identity,
3575                    &zero,
3576                    &guess,
3577                    |t| {
3578                        surface.compute_self_and_r_derivative(
3579                            t, &loops, &center, &externals, &masses, lmb,
3580                        )
3581                    },
3582                    &one,
3583                    2000,
3584                    64,
3585                    &e_cm,
3586                )
3587                .unwrap();
3588            assert_eq!(result.solution, prior.solution);
3589            assert_eq!(result.derivative_at_solution, prior.derivative_at_solution);
3590            assert_eq!(result.error_of_function, prior.error_of_function);
3591            assert_eq!(result.num_iterations_used, prior.num_iterations_used);
3592            let top_energy = (&e_cm - f(125)) / f(2);
3593            let radial_momentum = (top_energy.square() - f(173).square()).sqrt();
3594            assert!((&result.solution - &radial_momentum / &p).abs() < one.epsilon() * f(128));
3595            if perturbation > one.epsilon() * f(1000) {
3596                assert!(
3597                    (&result.solution - radial_momentum / f(100)).abs() > &one / f(10).powi(28)
3598                );
3599            }
3600
3601            // A retained ray preserves energy multiplicity and a nonzero affine
3602            // center. Independently eta(t)=2*sqrt((3*t+1)^2+9)+5-e_cm.
3603            use crate::utils::hyperdual_utils::{
3604                extract_t_derivatives, new_constant, simple_n_deriv_shape,
3605            };
3606            use symbolica::domains::dual::HyperDual;
3607            let mut jet_surface = surface.clone();
3608            jet_surface.energies = vec![EdgeIndex(1), EdgeIndex(1), EdgeIndex(3)];
3609            let mut jet_masses = masses.clone();
3610            jet_masses[EdgeIndex(1)] = f(3);
3611            jet_masses[EdgeIndex(3)] = f(4);
3612            let jet_velocity = LoopMomenta::from_iter([
3613                ThreeMomentum::new(f(3), zero.clone(), zero.clone()),
3614                ThreeMomentum::new(-f(3), zero.clone(), zero.clone()),
3615            ]);
3616            let jet_center = LoopMomenta::from_iter([
3617                ThreeMomentum::new(one.clone(), zero.clone(), zero.clone()),
3618                ThreeMomentum::new(-&one, zero.clone(), zero.clone()),
3619            ]);
3620            let jet_externals =
3621                ExternalFourMomenta::from_iter((0..2).map(|_| {
3622                    FourMomentum::from_args(e_cm.clone(), zero.clone(), f(3), zero.clone())
3623                }));
3624            let jet_ray = jet_surface.routed_ray(
3625                &jet_velocity,
3626                &jet_center,
3627                &jet_externals,
3628                &jet_masses,
3629                lmb,
3630            );
3631            assert_eq!(
3632                jet_ray.energies.iter().map(|term| term.0).collect_vec(),
3633                jet_surface.energies
3634            );
3635            assert_eq!(jet_ray.energies[2].1.norm_squared(), zero);
3636            assert_eq!(jet_ray.energies[2].2.norm_squared(), f(9));
3637            // A CT seed still ignores the center, whereas the explicit LU ray
3638            // seed consumes its actual affine offsets. Here the difference is2/3.
3639            let old_seed = jet_surface
3640                .get_radius_guess(&jet_velocity, &jet_externals, lmb)
3641                .0;
3642            assert_eq!(old_seed, &e_cm / f(6));
3643            let ray_seed = Esurface::radius_guess_from_terms(
3644                &jet_ray.shift,
3645                jet_ray
3646                    .energies
3647                    .iter()
3648                    .map(|(_, v, b, _)| (v.norm_squared(), v.clone() * b)),
3649            );
3650            assert!((ray_seed - old_seed - f(2) / f(3)).abs() < one.epsilon() * f(1024));
3651            let expected = [
3652                f(15) - &e_cm,
3653                f(24) / f(5),
3654                f(162) / f(125),
3655                -f(5832) / f(3125),
3656            ];
3657            let scalar = jet_ray.evaluate(&one);
3658            assert_eq!(scalar.0, expected[0]);
3659            assert!((&scalar.1 - &expected[1]).abs() < one.epsilon() * f(128));
3660            for order in 1..=3 {
3661                let t =
3662                    HyperDual::<F<T>>::new(simple_n_deriv_shape(order)).variable(0, one.clone());
3663                let actual = extract_t_derivatives(jet_ray.evaluate_dual(&t));
3664                // Independent legacy path: first scale complete loop jets, then
3665                // route original Esurface energies and true external ports.
3666                let dual_loops = LoopMomenta::from_iter(
3667                    jet_velocity.iter().zip(jet_center.iter()).map(|(v, b)| {
3668                        v.map_ref(&|x| new_constant(&t, x) * &t)
3669                            + b.map_ref(&|x| new_constant(&t, x))
3670                    }),
3671                );
3672                let dual_externals = jet_externals
3673                    .iter()
3674                    .map(|p| p.map_ref(&|x| new_constant(&t, x)))
3675                    .collect();
3676                let original = extract_t_derivatives(jet_surface.compute_from_dual_momenta(
3677                    lmb,
3678                    &jet_masses,
3679                    &dual_loops,
3680                    &dual_externals,
3681                ));
3682                for ((actual, original), expected) in actual.iter().zip(&original).zip(&expected) {
3683                    let tolerance = one.epsilon() * f(2048) * (one.clone() + expected.abs());
3684                    assert!((actual - expected).abs() < tolerance);
3685                    assert!((actual - original).abs() < tolerance);
3686                }
3687            }
3688
3689            // Reassociation is deliberately confined to explicit LU preparation.
3690            // With exact binary64 t=0.1 and k=[10^16,-10^16+2], the old
3691            // scale-then-route momentum is 1/8; route-then-scale gives 2*t.
3692            // This is a negative equivalence control, not a tolerance failure.
3693            let mut cancellation = surface.clone();
3694            cancellation.energies = vec![EdgeIndex(3)];
3695            cancellation.external_shift.clear();
3696            let large = f(10).powi(16);
3697            let velocity = LoopMomenta::from_iter([
3698                ThreeMomentum::new(large.clone(), zero.clone(), zero.clone()),
3699                ThreeMomentum::new(-large + f(2), zero.clone(), zero.clone()),
3700            ]);
3701            let t = F(T::from_f64_exact_binary(0.1));
3702            for (offset, mass) in [(0, 0), (3, 0), (3, 4)] {
3703                let center = LoopMomenta::from_iter([
3704                    ThreeMomentum::new(f(offset), zero.clone(), zero.clone()),
3705                    zero_vector.clone(),
3706                ]);
3707                let mut case_masses = masses.clone();
3708                case_masses[EdgeIndex(3)] = f(mass);
3709                let represented =
3710                    cancellation.routed_ray(&velocity, &center, &externals, &case_masses, lmb);
3711                let prepared = represented.evaluate(&t);
3712                let original = cancellation.compute_self_and_r_derivative(
3713                    &t,
3714                    &velocity,
3715                    &center,
3716                    &externals,
3717                    &case_masses,
3718                    lmb,
3719                );
3720                let prepared_p = f(2) * &t + f(offset);
3721                let prepared_e = (prepared_p.square() + f(mass).square()).sqrt();
3722                let tolerance = one.epsilon() * f(128);
3723                assert!((&prepared.0 - &prepared_e).abs() < tolerance);
3724                assert!((&prepared.1 - f(2) * &prepared_p / &prepared_e).abs() < tolerance);
3725                if one.epsilon() > &one / f(10).powi(20) {
3726                    let original_p = &one / f(8) + f(offset);
3727                    let original_e = (original_p.square() + f(mass).square()).sqrt();
3728                    assert!((&original.0 - &original_e).abs() < tolerance);
3729                    assert!((&original.1 - f(2) * original_p / original_e).abs() < tolerance);
3730                    assert!((&prepared.0 - &original.0).abs() > &one / f(100));
3731                } else {
3732                    // Quad/Arb have enough mantissa for these exact represented
3733                    // products; they recover the same point by either ordering.
3734                    assert!((&original.0 - &prepared.0).abs() < tolerance);
3735                    assert!((&original.1 - &prepared.1).abs() < tolerance);
3736                }
3737            }
3738
3739            // At the origin combine a massless moving energy with a shifted
3740            // massive energy E=sqrt(4^2+3^2). Their right derivative is exactly5.
3741            let mut endpoint = surface.clone();
3742            endpoint.energies = vec![EdgeIndex(1), EdgeIndex(2)];
3743            masses[EdgeIndex(1)] = zero.clone();
3744            masses[EdgeIndex(2)] = f(3);
3745            let velocity = LoopMomenta::from_iter([
3746                ThreeMomentum::new(f(3), f(4), zero.clone()),
3747                zero_vector.clone(),
3748            ]);
3749            let mut shifted_center = LoopMomenta::from_iter([
3750                zero_vector.clone(),
3751                ThreeMomentum::new(f(4), zero.clone(), zero.clone()),
3752            ]);
3753            let (value, derivative) = endpoint.compute_self_and_r_derivative(
3754                &zero,
3755                &velocity,
3756                &shifted_center,
3757                &externals,
3758                &masses,
3759                lmb,
3760            );
3761            assert_eq!(value, f(5) - &e_cm);
3762            assert_eq!(derivative, f(5));
3763            let prepared_endpoint = endpoint
3764                .routed_ray(&velocity, &shifted_center, &externals, &masses, lmb)
3765                .evaluate(&zero);
3766            assert_eq!(prepared_endpoint, (f(5) - &e_cm, f(5)));
3767            let stationary = LoopMomenta::from_iter([zero_vector.clone(), zero_vector]);
3768            assert_eq!(
3769                endpoint
3770                    .compute_self_and_r_derivative(
3771                        &zero,
3772                        &stationary,
3773                        &shifted_center,
3774                        &externals,
3775                        &masses,
3776                        lmb,
3777                    )
3778                    .1,
3779                zero
3780            );
3781            assert!(matches!(
3782                endpoint.solve_lu_cut(
3783                    &stationary,
3784                    &externals,
3785                    &masses,
3786                    lmb,
3787                    &e_cm,
3788                    &mut diagnostics,
3789                    &identity,
3790                ),
3791                Err(SafeguardedNewtonError::InvalidOutside { .. })
3792            ));
3793
3794            // Squaring a nonzero source can underflow in Double/Quad. It must
3795            // never activate the exact massless/zero-momentum endpoint rule.
3796            let tiny = &one / f(10).powi(200);
3797            masses[EdgeIndex(1)] = tiny.clone();
3798            let derivative = endpoint
3799                .compute_self_and_r_derivative(
3800                    &zero,
3801                    &velocity,
3802                    &shifted_center,
3803                    &externals,
3804                    &masses,
3805                    lmb,
3806                )
3807                .1;
3808            let prepared_derivative = endpoint
3809                .routed_ray(&velocity, &shifted_center, &externals, &masses, lmb)
3810                .evaluate(&zero)
3811                .1;
3812            for derivative in [derivative, prepared_derivative] {
3813                if tiny.square() == zero {
3814                    assert!(derivative.is_nan() || derivative.is_infinite());
3815                } else {
3816                    assert_eq!(derivative, zero);
3817                }
3818            }
3819            masses[EdgeIndex(1)] = zero.clone();
3820            for (axis, expected) in [3, 4, 0].into_iter().enumerate() {
3821                shifted_center[crate::momentum::sample::LoopIndex(0)] = ThreeMomentum::new(
3822                    if axis == 0 {
3823                        tiny.clone()
3824                    } else {
3825                        zero.clone()
3826                    },
3827                    if axis == 1 {
3828                        tiny.clone()
3829                    } else {
3830                        zero.clone()
3831                    },
3832                    if axis == 2 {
3833                        tiny.clone()
3834                    } else {
3835                        zero.clone()
3836                    },
3837                );
3838                let derivative = endpoint
3839                    .compute_self_and_r_derivative(
3840                        &zero,
3841                        &velocity,
3842                        &shifted_center,
3843                        &externals,
3844                        &masses,
3845                        lmb,
3846                    )
3847                    .1;
3848                let prepared_derivative = endpoint
3849                    .routed_ray(&velocity, &shifted_center, &externals, &masses, lmb)
3850                    .evaluate(&zero)
3851                    .1;
3852                for derivative in [derivative, prepared_derivative] {
3853                    if tiny.square() == zero {
3854                        assert!(derivative.is_nan() || derivative.is_infinite());
3855                    } else {
3856                        assert!((derivative - f(expected)).abs() < one.epsilon() * f(128));
3857                    }
3858                }
3859            }
3860        }
3861        check_native::<f64>(&graph, &surface);
3862        check_native::<QuadFloat>(&graph, &surface);
3863        check_native::<ArbPrec>(&graph, &surface);
3864    }
3865
3866    #[test]
3867    fn canonical_physical_lu_root_resolves_retained_bracket_endpoints() {
3868        use crate::graph::FeynmanGraph;
3869
3870        test_initialise().unwrap();
3871        // Baseline seed20011, worker3/local239 (global5156), source path[0,5].
3872        // Cut group3 is physical Cut0, with its original ordered four energies.
3873        // These are the captured Arb generation momenta, without the radial
3874        // normalization used by the separate foreign-inverse fixture below.
3875        let point_tokens = [
3876            "-4877.19776949134799589974803772414639433359513358235797727662621078949562551592647720875226397943918395045206756176248678175688837208619050791653582445021695271333556149595551567501518142933108041137862389137548159866615474120165599255689473831877256382039353906809830046294831502842769433115811496491403",
3877            "-4788.44733559297503338424048189604502643310578502030319826258363405583106128848247171958913511942781252928660066253754883065799513281132098117063664934324987293459853837736080937683626624511055230677016357961716720986212711446609804288980413634695779067271806710579027258896440419592959858535084502130291",
3878            "3449.85408446744743054600317691517351068755983604965291557283080813569562933280322362036960861849284721431171841172810290687504905161462871600062758043010601204035005302723970251590824854187235628530596853916572662417827550093988644061689354024522549573776864100886485399680047404526188593294956650329940",
3879            "70.3244241810218751702648147574915682722146332904085090192206088762519914112850473659048343153323638641871729435254245633882918549420905038752476781927385371433230303313665642902999745355570884098566729530821574404942919072073238328487435044514999060063382762063688077567584466375120955594649302749638168",
3880            "-64.7328550651196388632156519795545666707240287311537237777794147531761978347671932403072110940638881039840834582472804137358931989273824856974894575353144549925155422489503341679707811903159498611335677205735964537298380313103709897338608109368125932232793979426015474979910153144543330056886932939528991",
3881            "-169.977204441411724276834141865099406683621147453741490083449379145954509733051600240055550798830052895001473550375457779362103819932642751869589067397325831680056922569024797824237561594653585076675793132823695793970660453958165008987223868047366419040597994417711631570323172275534429627197335733541788",
3882            "-5600.61087887521441317726380154507077804125909265810807210495844671671183617788774688843570145751202477847145016280355634030258604974973758865789530484255123560331590332195975778091351865880370425224431732203854524561456548571792312894398114116052933817481418085818812657379218499842947239271592857749704",
3883            "-4836.61374618501855280967146959499945066804232727342341015614259662078809271214898964547576447118364148478316361672773076137899768111124298404537445866753685338684537418353640716742530483139927834572180382685089716954278888134794244645196893419036539682711826176677044169785265849490484473737712006654560",
3884            "2145.99486696308129706260024015689439317496385917360897844622508358240397624209431777021609681539543126318894513773663359142436896535260327930425968202779550650784950814483566205233631415943233817778512294707166462039932281821355935955750715055287115993275252104683925707031965340569344306593459551977201",
3885            "-5604.88372374262804889294640877016787850008172170528245661491078660880869657816973625663719378778526660563427937564485272926173353092285235941079848809391075384808960803275406005472826675962967469084057925785326996659483564308288383384251734955274712244877309369072980850467041673500458058150368522257495",
3886            "-4334.45541003902767715452592577800941446238649756064773722205221184221360633788778921553682091482858995046598733903274399368838883790258941797495609397561874360921914142839313072883007139115101612942134481476731356575319298406454126144036222827728908340679754158679444863572311869978356712039675439410147",
3887            "2429.79731946564390222685915546149324045671034707482980645420787377878023354031617617014193097522475262733934128132427049128461231717208810751567187554211866137651923930428300030230687501307593042508234180738169107139713264377038173966078706808479870077056431717387171214082081679052824398764469662844001",
3888        ];
3889        let point = point_tokens
3890            .iter()
3891            .map(|token| {
3892                let value = token.parse::<ArbPrec>().unwrap();
3893                assert_eq!(value.to_string(), *token);
3894                F(value)
3895            })
3896            .collect_vec();
3897        let one = point[0].one();
3898        let zero = one.zero();
3899        let model = crate::utils::load_generic_model("sm");
3900        let graph: Graph = include_str!("../../../../tests/resources/graphs/GL638.dot")
3901            .into_graph(&model)
3902            .unwrap();
3903        let lmb = &graph.loop_momentum_basis;
3904        assert_eq!(
3905            lmb.loop_edges.raw,
3906            vec![EdgeIndex(3), EdgeIndex(4), EdgeIndex(7), EdgeIndex(10)]
3907        );
3908        let surface = Esurface {
3909            energies: vec![EdgeIndex(2), EdgeIndex(6), EdgeIndex(12), EdgeIndex(13)],
3910            external_shift: vec![(EdgeIndex(0), -1), (EdgeIndex(1), -1)],
3911            vertex_set: VertexSet::dummy(),
3912        };
3913        let masses = graph.get_real_mass_vector::<ArbPrec>(&model);
3914        for (edge, mass) in [(2, 125), (6, 173), (12, 173), (13, 0)] {
3915            assert_eq!(masses[EdgeIndex(edge)], one.from_i64(mass));
3916        }
3917        let externals = ExternalFourMomenta::from_iter([1, -1].map(|sign| {
3918            FourMomentum::from_args(
3919                one.from_i64(500),
3920                zero.clone(),
3921                zero.clone(),
3922                one.from_i64(500 * sign),
3923            )
3924        }));
3925        assert_eq!(externals.len(), lmb.ext_edges.len());
3926        let momentum = LoopMomenta::from_iter(
3927            point
3928                .as_chunks::<3>()
3929                .0
3930                .iter()
3931                .map(|v| ThreeMomentum::new(v[0].clone(), v[1].clone(), v[2].clone())),
3932        );
3933        let center = LoopMomenta::from_iter(
3934            (0..4).map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
3935        );
3936        let e_cm = one.from_i64(1000);
3937        let identity =
3938            RadialRootIdentity::new("canonical physical overlap graph 'GL638' cut group 3".into());
3939        let ray = surface.routed_ray(&momentum, &center, &externals, &masses, lmb);
3940        let guess = Esurface::radius_guess_from_terms(
3941            &ray.shift,
3942            ray.energies
3943                .iter()
3944                .map(|(_, v, b, _)| (v.norm_squared(), v.clone() * b)),
3945        );
3946        let strict = safeguarded_newton_iteration_and_derivative(
3947            &zero,
3948            &guess,
3949            |r| ray.evaluate(r),
3950            &one,
3951            2000,
3952            64,
3953            &e_cm,
3954        )
3955        .unwrap_err();
3956        assert_eq!(
3957            format!(
3958                "sampling root is not certified at the current precision: {identity}: {strict:?}"
3959            ),
3960            "sampling root is not certified at the current precision: canonical physical overlap graph 'GL638' cut group 3: DidNotConverge { result: NewtonIterationResult { solution: F(VarFloat { float: 4.50096019182270793871156294720615053060112718361364618170932397734514711080987392331940322755100771019674059655008057236752740928154857006112161128562114855544422368074316508870600192219593565649427544978773164779121432253631794038806540811255813327701451237828689832176990555566921873234752788019943785e-2 }), derivative_at_solution: F(VarFloat { float: 15947.7515847915197382229906009716238684820639052766863102939247189292647433796806064815357943825914286672680520425869860307247589322869726693916868899181132224865883496823643904736774712938449859097464691001739892743096176224973672876047140085992981227937651521443373989485856759341750304597183394266484 }), error_of_function: F(VarFloat { float: -2.86698583604188839625755508139156634506370492325388705163790645185321035051758274904404068484469074644867580906595607334158997766559958751695315195045047528315210956637693486164171287342869042640210070772612642749923485444545310860535350764889727519890646541984079814999901126931044646673003826046088028e-298 }), num_iterations_used: 10 }, lower_bound: F(VarFloat { float: 4.50096019182270793871156294720615053060112718361364618170932397734514711080987392331940322755100771019674059655008057236752740928154857006112161128562114855544422368074316508870600192219593565649427544978773164779121432253631794038806540811255813327701451237828689832176990555566921873234752788019943785e-2 }), upper_bound: F(VarFloat { float: 4.50096019182270793871156294720615053060112718361364618170932397734514711080987392331940322755100771019674059655008057236752740928154857006112161128562114855544422368074316508870600192219593565649427544978773164779121432253631794038806540811255813327701451237828689832176990555566921873234752788019943844e-2 }) }"
3961        );
3962        let (_, production) = surface
3963            .solve_lu_cut(
3964                &momentum,
3965                &externals,
3966                &masses,
3967                lmb,
3968                &e_cm,
3969                &mut RadialRootDiagnostics::default(),
3970                &identity,
3971            )
3972            .unwrap();
3973        // The production LU caller includes the existing energy-sum forward-error
3974        // budget. Preserve the retained strict fixture below at its unit budget.
3975        let production_budget =
3976            one.from_i64((4 * ray.energies.len() + 1) as i64) * one.epsilon() * &e_cm;
3977        let (production_value, production_derivative) = ray.evaluate(&production.solution);
3978        assert_eq!(production.error_of_function, production_value);
3979        assert_eq!(production.derivative_at_solution, production_derivative);
3980        assert!(production.error_of_function.abs() <= production_budget);
3981        let certified = RadialRootDiagnostics::default()
3982            .solve(
3983                &identity,
3984                &zero,
3985                &guess,
3986                |r| ray.evaluate(r),
3987                &one,
3988                2000,
3989                64,
3990                &e_cm,
3991            )
3992            .unwrap();
3993        let SafeguardedNewtonError::DidNotConverge {
3994            result,
3995            lower_bound,
3996            upper_bound,
3997        } = strict
3998        else {
3999            panic!("retained physical source did not reproduce an exhausted bracket");
4000        };
4001        assert_eq!(result.solution, lower_bound);
4002        assert_eq!(result.num_iterations_used, 10);
4003        assert!(upper_bound > lower_bound);
4004        let midpoint = (&lower_bound + &upper_bound) / one.from_i64(2);
4005        assert!(midpoint == lower_bound || midpoint == upper_bound);
4006        let width = &upper_bound - &lower_bound;
4007        let budget = one.epsilon() * &e_cm;
4008        assert_eq!(certified.solution, upper_bound);
4009        assert_eq!(certified.num_iterations_used, result.num_iterations_used);
4010        let (upper_value, upper_derivative) = ray.evaluate(&upper_bound);
4011        assert_eq!(certified.error_of_function, upper_value);
4012        assert_eq!(certified.derivative_at_solution, upper_derivative);
4013        // Strict native convergence still fails. Only the already represented
4014        // upper endpoint satisfies the unchanged bracket-resolution allowance.
4015        assert!(
4016            result.error_of_function.abs() > &budget + result.derivative_at_solution.abs() * &width
4017        );
4018        assert!(certified.error_of_function.abs() > budget);
4019        assert!(
4020            certified.error_of_function.abs()
4021                <= &budget + certified.derivative_at_solution.abs() * &width
4022        );
4023        crate::debug_tags!(#integration, #cut, #solver;
4024            stage = "canonical_physical_lu_root_setup",
4025            cut_group = 3, physical_cut = 0,
4026            initial_guess = %guess, original_residual_budget = %budget,
4027            bracket_width = %width, file.ray = ?ray,
4028            file.original_momenta = ?momentum, file.original_result = ?result,
4029            "actual captured physical ray and unchanged LU solver policy"
4030        );
4031        for (endpoint, radius) in [("lower", &lower_bound), ("upper", &upper_bound)] {
4032            let (value, derivative) = ray.evaluate(radius);
4033            let enclosed = surface
4034                .evaluate_routed_enclosed(radius, &momentum, &externals, &masses, lmb)
4035                .unwrap();
4036            assert!(!derivative.is_nan() && !derivative.is_infinite() && derivative > zero);
4037            assert!(enclosed.iter().all(|value| value.is_finite()));
4038            let resolution_budget = &budget + derivative.abs() * &width;
4039            let allowed = budget.0.mpfr_enclosure(2048).0;
4040            let native_strict = value.abs() <= budget;
4041            let native_resolution = value.abs() <= resolution_budget;
4042            let original_strict = enclosed[0] >= -allowed.clone() && enclosed[1] <= allowed;
4043            assert!(
4044                original_strict,
4045                "{endpoint} violates the original equation budget"
4046            );
4047            crate::debug_tags!(#integration, #cut, #solver;
4048                stage = "canonical_physical_lu_root_endpoint", endpoint,
4049                radius = %radius, native_residual = %value, derivative = %derivative,
4050                original_residual_lower = %enclosed[0], original_residual_upper = %enclosed[1],
4051                original_residual_budget = %budget, resolution_budget = %resolution_budget,
4052                native_strict, native_resolution, original_strict,
4053                "both represented endpoints satisfy the unchanged original equation budget"
4054            );
4055        }
4056
4057        // This adversarial callback preserves the actual energy values but deliberately
4058        // supplies the wrong derivative only at the alternate endpoint. Its residual
4059        // fits the resolution allowance; its own four-probe slope check must reject it.
4060        let inconsistent_derivative = |radius: &F<ArbPrec>| {
4061            let (value, derivative) = ray.evaluate(radius);
4062            let derivative = if radius == &upper_bound {
4063                derivative * one.from_i64(8)
4064            } else {
4065                derivative
4066            };
4067            (value, derivative)
4068        };
4069        let invalid_strict = safeguarded_newton_iteration_and_derivative(
4070            &zero,
4071            &guess,
4072            inconsistent_derivative,
4073            &one,
4074            2000,
4075            64,
4076            &e_cm,
4077        )
4078        .unwrap_err();
4079        let SafeguardedNewtonError::DidNotConverge {
4080            result: invalid_result,
4081            lower_bound: invalid_lower,
4082            upper_bound: invalid_upper,
4083        } = &invalid_strict
4084        else {
4085            panic!("adversarial callback changed the structural failure");
4086        };
4087        assert!(invalid_result.num_iterations_used >= 4);
4088        assert_eq!(invalid_result.solution, lower_bound);
4089        assert_eq!(*invalid_lower, lower_bound);
4090        assert_eq!(*invalid_upper, upper_bound);
4091        let (alternate_value, wrong_derivative) = inconsistent_derivative(&upper_bound);
4092        assert!(alternate_value.abs() <= &budget + wrong_derivative * &width);
4093        let rejected = RadialRootDiagnostics::default()
4094            .solve(
4095                &RadialRootIdentity::new("adversarial alternate endpoint derivative".into()),
4096                &zero,
4097                &guess,
4098                inconsistent_derivative,
4099                &one,
4100                2000,
4101                64,
4102                &e_cm,
4103            )
4104            .unwrap_err();
4105        assert_eq!(format!("{rejected:?}"), format!("{invalid_strict:?}"));
4106    }
4107
4108    #[test]
4109    fn phase_space_cut_root_replays_canonical_foreign_inverse_cancellation() {
4110        test_initialise().unwrap();
4111        // GL638 Halton3363: the hosted joint's ordinary fallback generated this
4112        // canonical Arb point; its foreign full-frame Cut1 inverse failed before
4113        // any reference/physical body. Retain the native decimal roundtrip, not
4114        // a binary64 reconstruction or an independently regenerated joint point.
4115        let point_tokens = [
4116            "1367030.43662744423077657886622585118732404500993473554913895566241589500405947461690714412624268513505631015396234810943269596104532748643105542702039862197867565164412838526283892048404678602045626445614508293241427506110755435656591802035619036221554197413841701638040025597247140012517189434530153459",
4117            "-999526.399472714004924745558487742174280959297534606425446121987615879279562127703414975258631734220750778229983972859653308430370782154297373722895399005861599636821957013566636477314723339751660664618818230868728997962470940365780821107652219318608507461393737546471739313569778449847787262829689519758",
4118            "2635.11804627574968679150746698546684926537860473844219904054202664480773875967585945065405808941879393444480927960481154917330356034222219635456028440278614622109927632916893794632929631394563782912542725019630912377572312073034188155233244619198043408564989307377224208055269346704158128964908317509261",
4119            "1365298.80606570193439178248903075440797721030001172865681315316962534564202453943423169414748443288269134401947007088168439700475928251218359436660404679423063978012317674014693339414507122222142523195226338848798195882508017496231651900921236382771006795390348616128129934316532814543030209744364019054",
4120            "-998820.613196962571373332987243603087494541655288997511144379181267051699516068044611894994190494668286551419090745020640141977947029490154255331734395335596048929725132710856235917568105358581132488124086315318516657933430936244305844004808412923698398375511933676277573855820903335530154557900187401664",
4121            "4047.33414152831088829375438466435223275171308987396130932582985387723022229862922976703274469623350667935654058159331604678380195069378771816145293238142522165286453283051283913992888932063257542647725648351805459013522266494043094042838131736080143438861266226902012570363568976019577762021664023114866",
4122            "83.1126856152680405091850152707032759635065978000760419277583832932540620572340228677113831897018166702219588733109015730699449632190446808998629297952866898031107347012837750524595236018672538656817674901345397345906420467235996584856394111008677691361716531755180039690828818424181738021720937612332473",
4123            "-271.487004006624936032383971262627992814142127178531082780852027992007247020700756462748911221240546859750792472835768342005119164451772938283340118890996475035825441694527852209889256539059182981432179743946981544937115591798425012272630650241061657324339012716835440520492257904951141777212048569398409",
4124            "172.995634312193812329535193383815352801038985837137105437759642412099825918135903635674987213191903064253661504641496794322344612352796885741009454426914017751085087430094408938579737245424065728815449873693652886682643449058779221848518639106186622340605844416298128898323757003199986295961514068931809",
4125            "1553.02030744971213963777112883628816116222224922513300779830478460724114360528704175205365061869941782426355915003669694077799625540057885933310993881768684532880891858480897867129984665510634951301156097819370899150217888483360555670282615505920358106216430177949458381682435533431403640861028867643634",
4126            "175.729482877721774403936070331243420758342427008390700023593859226796553067648366117048302143782877238854817038614857585311307016571639203050561588272527571998881087153974255763006207376888422035958124493436136546263533358525466966789793340591585338882735253021052931213562265925007864963381639808647379",
4127            "-987.094530658251250328865816150283711625817471698266080780545993723465686910923865574545177650018084715406989468479547700982468885609732012850096019949134333598256299704715871688857759497730140969322324491488236509562871514705347225367092074540791495561129260238451255593578254459645239533939527551314165",
4128        ];
4129        let point = point_tokens
4130            .iter()
4131            .map(|token| {
4132                let value = token.parse::<ArbPrec>().unwrap();
4133                assert_eq!(value.to_string(), *token);
4134                F(value)
4135            })
4136            .collect_vec();
4137        let one = point[0].one();
4138        let zero = one.zero();
4139        let f = |value| one.from_i64(value);
4140        let model = crate::utils::load_generic_model("sm");
4141        let graph: Graph = include_str!("../../../../tests/resources/graphs/GL638.dot")
4142            .into_graph(&model)
4143            .unwrap();
4144        let lmb = &graph.loop_momentum_basis;
4145        assert_eq!(
4146            lmb.loop_edges.raw,
4147            vec![EdgeIndex(3), EdgeIndex(4), EdgeIndex(7), EdgeIndex(10)]
4148        );
4149        for (edge, coefficients) in [(2, [-1, 1, 0, 0]), (6, [-1, 1, 0, 1]), (10, [0, 0, 0, 1])] {
4150            assert_eq!(
4151                lmb.edge_signatures[EdgeIndex(edge)]
4152                    .internal
4153                    .to_momtrop_format(),
4154                coefficients
4155            );
4156        }
4157        let surface = Esurface {
4158            energies: vec![EdgeIndex(2), EdgeIndex(6), EdgeIndex(10)],
4159            external_shift: vec![(EdgeIndex(0), -1), (EdgeIndex(1), -1)],
4160            vertex_set: VertexSet::dummy(),
4161        };
4162        let masses = graph
4163            .underlying
4164            .new_edgevec_from_iter(lmb.edge_signatures.iter().map(|(edge, _)| {
4165                f(match edge.0 {
4166                    2 => 125,
4167                    6 | 10 => 173,
4168                    _ => 0,
4169                })
4170            }))
4171            .unwrap();
4172        let externals = ExternalFourMomenta::from_iter([1, -1].map(|sign| {
4173            FourMomentum::from_args(f(500), zero.clone(), zero.clone(), f(500 * sign))
4174        }));
4175        assert_eq!(externals.len(), lmb.ext_edges.len());
4176        let center = LoopMomenta::from_iter(
4177            (0..4).map(|_| ThreeMomentum::new(zero.clone(), zero.clone(), zero.clone())),
4178        );
4179        // Preserve the inverse owner's full12D sum/order and normalization.
4180        // Host-null p/s components must not be dropped before forming this ray.
4181        let radius = point
4182            .iter()
4183            .map(F::square)
4184            .fold(zero.clone(), |sum, x| sum + x)
4185            .sqrt();
4186        let direction = point.iter().map(|x| x / &radius).collect_vec();
4187        let velocity = LoopMomenta::from_iter(
4188            direction
4189                .as_chunks::<3>()
4190                .0
4191                .iter()
4192                .map(|v| ThreeMomentum::new(v[0].clone(), v[1].clone(), v[2].clone())),
4193        );
4194        let original = |r: &F<ArbPrec>| {
4195            surface.compute_self_and_r_derivative(r, &velocity, &center, &externals, &masses, lmb)
4196        };
4197        let ray = surface.routed_ray(&velocity, &center, &externals, &masses, lmb);
4198        let routed = |r: &F<ArbPrec>| ray.evaluate(r);
4199        let scale = original(&zero).0.abs().max(one.clone());
4200        assert_eq!(scale, f(529));
4201        let guess = f(300);
4202        let tolerance = f(64);
4203        let identity = RadialRootIdentity::new("canonical foreign phase-space Cut1".into());
4204        let old_strict = safeguarded_newton_iteration_and_derivative(
4205            &zero, &guess, original, &tolerance, 2048, 96, &scale,
4206        );
4207        let old_checked = RadialRootDiagnostics::default().solve(
4208            &identity, &zero, &guess, original, &tolerance, 2048, 96, &scale,
4209        );
4210        let ray_strict = safeguarded_newton_iteration_and_derivative(
4211            &zero, &guess, routed, &tolerance, 2048, 96, &scale,
4212        );
4213        let ray_checked = RadialRootDiagnostics::default().solve(
4214            &identity, &zero, &guess, routed, &tolerance, 2048, 96, &scale,
4215        );
4216        crate::debug_tags!(#integration, #sampling, #solver;
4217            stage = "canonical_foreign_cut_root_comparison",
4218            scale = %scale, tolerance = %tolerance,
4219            old_strict = ?old_strict, old_checked = ?old_checked,
4220            ray_strict = ?ray_strict, ray_checked = ?ray_checked,
4221            "same native direction and root budget; only radial routing association differs"
4222        );
4223        let Err(SafeguardedNewtonError::DidNotConverge {
4224            result,
4225            lower_bound,
4226            upper_bound,
4227        }) = old_strict
4228        else {
4229            panic!("retained point did not reproduce the original failed callback");
4230        };
4231        assert_eq!(result.solution, lower_bound);
4232        assert_eq!(result.solution, F("397339.292129129457670657255502631235707430728003236007665188851741059988603886168137387085943384940050545361933742066907875904266411120132268928043162217340042566193200203939814362605526391210977153331875750728687297380447984755419924107738341193852462036361671032388546985919083686130672457601933412077".parse::<ArbPrec>().unwrap()));
4233        assert_eq!(result.derivative_at_solution, F("1.89172234332619088523547124801376213069340504387632086738281927967636989017896303335256085095364995340008004517730539670922548200475769065127437640250637540730631026978180801048759551386539060201137497670459856172911183019727298113312180354967670498695170211446748295063372406403797004835125320002494423e-3".parse::<ArbPrec>().unwrap()));
4234        assert_eq!(result.error_of_function, F("-1.14679433441675535850302203255662653802548196930155482065516258074128414020703309961761627393787629857947032362638242933663599106623983500678126078018019011326084382655077394465668514937147617056084028309045057099969394177818124344214140305955891007956258616793631925999960450772417858669201530418435211e-296".parse::<ArbPrec>().unwrap()));
4235        assert_eq!(result.num_iterations_used, 17);
4236        assert!(upper_bound > lower_bound);
4237        assert!(result.error_of_function.abs() > one.epsilon() * &tolerance * &scale);
4238        // The existing wrapper is tested independently: a tiny bracket alone
4239        // must not silently enlarge this callback's residual allowance.
4240        assert!(old_checked.is_err());
4241        let prepared = ray_strict.expect("pre-routed same-budget comparison did not converge");
4242        let checked = ray_checked.unwrap();
4243        assert_eq!(prepared.solution, checked.solution);
4244        assert!(prepared.derivative_at_solution > zero);
4245        assert!(prepared.error_of_function.abs() <= one.epsilon() * &tolerance * &scale);
4246        let residual = surface
4247            .evaluate_routed_enclosed(&prepared.solution, &velocity, &externals, &masses, lmb)
4248            .unwrap();
4249        let allowed = (one.epsilon() * &tolerance * &scale)
4250            .0
4251            .mpfr_enclosure(2048)
4252            .0;
4253        assert!(residual[0] >= -allowed.clone() && residual[1] <= allowed);
4254        crate::debug_tags!(#integration, #sampling, #solver;
4255            stage = "canonical_foreign_cut_original_residual",
4256            native_root = %prepared.solution,
4257            original_residual_lower = %residual[0], original_residual_upper = %residual[1],
4258            "directed original routing at the pre-routed callback root"
4259        );
4260    }
4261
4262    #[test]
4263    fn implicit_sampling_map_uses_graph_esurface_root_and_roundtrips() {
4264        test_initialise().unwrap();
4265        let graph: Graph = dot!(digraph implicit_sampling_esurface {
4266            ext [style=invis]
4267            node [num=1]
4268            edge [num=1 mass=0]
4269            ext -> a:0 [id=0]
4270            a -> b [id=1 lmb_id=0]
4271            a -> b [id=2 lmb_id=1]
4272            a -> b [id=3]
4273            b:1 -> ext [id=4]
4274        })
4275        .unwrap();
4276        let lmb = graph.loop_momentum_basis.clone();
4277        let surface = Esurface {
4278            energies: vec![EdgeIndex(1), EdgeIndex(2), EdgeIndex(3)],
4279            external_shift: vec![(EdgeIndex(0), -1)],
4280            vertex_set: VertexSet::dummy(),
4281        };
4282        let masses = graph
4283            .underlying
4284            .new_edgevec_from_iter([F(0.0), F(173.0), F(173.0), F(125.0), F(0.0)])
4285            .unwrap();
4286        let externals = ExternalFourMomenta::from_iter(
4287            [FourMomentum::from_args(F(1000.0), F(0.0), F(0.0), F(0.0)); 2],
4288        );
4289        let map = surface
4290            .sampling_radial_map(&lmb, &masses, &externals, 400.0, 2.0)
4291            .unwrap();
4292        // Exercise both sides of the cut shell with a sizeable branch
4293        // split; the outer compactification carries its (1-split) factor.
4294        for radial_coordinate in [0.1, 0.9] {
4295            let coordinates = [radial_coordinate, 0.27, 0.61, 0.39, 0.72, 0.58];
4296            let forward = map.forward(&coordinates).unwrap();
4297            let step = 1.0e-5;
4298            let mut derivative_matrix = vec![vec![0.0; 6]; 6];
4299            for axis in 0..6 {
4300                let mut plus = coordinates;
4301                let mut minus = coordinates;
4302                plus[axis] += step;
4303                minus[axis] -= step;
4304                let plus = map.forward(&plus).unwrap();
4305                let minus = map.forward(&minus).unwrap();
4306                for (component, row) in derivative_matrix.iter_mut().enumerate().take(6) {
4307                    row[axis] = (plus.point[component] - minus.point[component]) / (2.0 * step);
4308                }
4309            }
4310            let numerical_jacobian = SamplingMapAffine::new(derivative_matrix, vec![0.0; 6])
4311                .unwrap()
4312                .determinant();
4313            assert!((numerical_jacobian / forward.jacobian - 1.0).abs() < 1.0e-4);
4314
4315            assert!(
4316                forward
4317                    .diagnostics
4318                    .iter()
4319                    .any(|diagnostic| diagnostic == "implicit_surface:regular_root")
4320            );
4321            assert!(forward.jacobian.is_finite() && forward.jacobian > 0.0);
4322            let inverse = map.inverse(&forward.point).unwrap();
4323            assert!(inverse.residual < 1.0e-9, "{}", inverse.residual);
4324            for (actual, expected) in inverse.coordinates.iter().zip(coordinates) {
4325                assert!((actual - expected).abs() < 1.0e-9);
4326            }
4327        }
4328
4329        // The graph factory must bind source data in the evaluation precision.
4330        // These two energies are indistinguishable in f64 but define different
4331        // physical shells and therefore different forward maps at Quad precision.
4332        let one = F::<crate::utils::f128>::from_f64(1.0);
4333        let energy = one.from_i64(1000);
4334        let displacement = one.from_i64(10).powi(-20);
4335        assert_eq!(energy.into_f64(), (energy + displacement).into_f64());
4336        let native_masses = graph
4337            .underlying
4338            .new_edgevec_from_iter(
4339                masses
4340                    .iter()
4341                    .map(|(_, mass)| F::<crate::utils::f128>::from_ff64(*mass)),
4342            )
4343            .unwrap();
4344        let coordinates = [0.19, 0.27, 0.61, 0.39, 0.72, 0.58]
4345            .map(|value| F::<crate::utils::f128>::from_f64(value).0);
4346        let mut points = Vec::new();
4347        for energy in [energy, energy + displacement] {
4348            let externals = ExternalFourMomenta::from_iter(
4349                [FourMomentum::from_args(energy, one.zero(), one.zero(), one.zero()); 2],
4350            );
4351            let map = surface
4352                .sampling_radial_map(&lmb, &native_masses, &externals, 400.0, 2.0)
4353                .unwrap();
4354            let forward = map.forward(&coordinates).unwrap();
4355            let inverse = map.inverse(&forward.point).unwrap();
4356            for (actual, expected) in inverse.coordinates.iter().zip(coordinates) {
4357                assert!((F(*actual) - F(expected)).abs() < one.from_i64(10).powi(-25));
4358            }
4359            points.push(forward.point);
4360        }
4361        assert_ne!(points[0], points[1]);
4362        assert_eq!(
4363            points[0]
4364                .iter()
4365                .map(|value| F(*value).into_f64())
4366                .collect::<Vec<_>>(),
4367            points[1]
4368                .iter()
4369                .map(|value| F(*value).into_f64())
4370                .collect::<Vec<_>>(),
4371        );
4372    }
4373
4374    #[test]
4375    fn classification_preserves_the_previous_existing_predicate() {
4376        let e_cm = F(10.0);
4377        let shift_tolerance = F(ESURFACE_SHIFT_THRESHOLD) * e_cm;
4378        let normalized_margin_tolerance = F(DEFAULT_ESURFACE_EXISTENCE_THRESHOLD);
4379        let invariant_tolerance = normalized_margin_tolerance * e_cm * e_cm;
4380
4381        for shift_factor in [-2, -1, 0, 1, 2] {
4382            let shift_part = shift_tolerance * shift_tolerance.from_i64(shift_factor);
4383            for margin_factor in [-2, -1, 0, 1, 2] {
4384                let invariant_margin =
4385                    invariant_tolerance * invariant_tolerance.from_i64(margin_factor);
4386                let was_existing =
4387                    shift_part < -&shift_tolerance && invariant_margin > invariant_tolerance;
4388                let classification = Esurface::classify_invariant_margin(
4389                    &shift_part,
4390                    invariant_margin,
4391                    &e_cm,
4392                    &normalized_margin_tolerance,
4393                );
4394
4395                assert_eq!(
4396                    classification.is_existing(),
4397                    was_existing,
4398                    "existence changed for shift factor {shift_factor} and margin factor {margin_factor}",
4399                );
4400            }
4401        }
4402    }
4403
4404    #[test]
4405    fn invalid_programmatic_tolerances_cannot_invert_classification() {
4406        let e_cm = F(10.0);
4407        let shift_part = F(-1.0);
4408        let invariant_margin = F(0.0);
4409
4410        for tolerance in [
4411            F(DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
4412            F(-DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
4413            F(f64::NAN),
4414            F(f64::INFINITY),
4415        ] {
4416            assert!(matches!(
4417                Esurface::classify_invariant_margin(
4418                    &shift_part,
4419                    invariant_margin,
4420                    &e_cm,
4421                    &tolerance,
4422                ),
4423                EsurfaceExistence::Pinched { .. }
4424            ));
4425        }
4426    }
4427
4428    #[test]
4429    fn positive_external_energy_filter_uses_energy_conservation() {
4430        let incoming_edges = (0..4).map(EdgeIndex::from).collect_vec();
4431        let outgoing_edges = (4..9).map(EdgeIndex::from).collect_vec();
4432        let shift_is_negative = |external_shift: &[(usize, i64)]| {
4433            Esurface {
4434                energies: vec![],
4435                external_shift: external_shift
4436                    .iter()
4437                    .map(|(edge, coefficient)| (EdgeIndex::from(*edge), *coefficient))
4438                    .collect(),
4439                vertex_set: VertexSet::dummy(),
4440            }
4441            .external_shift_is_strictly_negative_for_positive_energies(
4442                &incoming_edges,
4443                &outgoing_edges,
4444            )
4445        };
4446
4447        assert!(
4448            shift_is_negative(&[(0, -1), (1, -1)]),
4449            "a negative proper subset of incoming energies must be retained"
4450        );
4451        assert!(
4452            shift_is_negative(&[(4, -1)]),
4453            "a negative proper subset of outgoing energies must be retained"
4454        );
4455        assert!(
4456            shift_is_negative(&[(0, -1), (1, -1), (2, -1), (3, -1), (4, 1)]),
4457            "energy conservation turns minus all incoming plus one outgoing into minus the remaining outgoing energies"
4458        );
4459        assert!(
4460            !shift_is_negative(&[
4461                (0, -1),
4462                (1, -1),
4463                (2, -1),
4464                (3, -1),
4465                (4, 1),
4466                (5, 1),
4467                (6, 1),
4468                (7, 1),
4469                (8, 1),
4470            ]),
4471            "the energy-conservation identity is zero rather than strictly negative"
4472        );
4473        assert!(
4474            !shift_is_negative(&[(0, -1), (4, 1)]),
4475            "a sign-indefinite difference must not be accepted from positivity alone"
4476        );
4477        assert!(
4478            !shift_is_negative(&[(9, -1)]),
4479            "a shift outside the external-energy partition must be rejected conservatively"
4480        );
4481    }
4482
4483    #[test]
4484    fn massless_two_to_two_surface_is_existing_away_from_collinear_pinch() {
4485        let dummy_graph = dummy_hedge_graph(4);
4486        let lmb = LoopMomentumBasis {
4487            tree: SuBitGraph::empty(0),
4488            loop_edges: vec![EdgeIndex::from(2)].into(),
4489            ext_edges: vec![].into(),
4490            edge_signatures: dummy_graph
4491                .new_edgevec_from_iter(vec![
4492                    LoopExtSignature::from((vec![0], vec![1, 0])),
4493                    LoopExtSignature::from((vec![0], vec![0, 1])),
4494                    LoopExtSignature::from((vec![1], vec![0, 0])),
4495                    LoopExtSignature::from((vec![-1], vec![-1, -1])),
4496                ])
4497                .unwrap(),
4498        };
4499        let esurface = Esurface {
4500            energies: vec![EdgeIndex::from(2), EdgeIndex::from(3)],
4501            external_shift: vec![(EdgeIndex::from(0), -1), (EdgeIndex::from(1), -1)],
4502            vertex_set: VertexSet::dummy(),
4503        };
4504        let masses = dummy_graph.new_edgevec_from_iter(vec![F(0.0); 4]).unwrap();
4505        // Either side of the 2->2 sandwich determines the same total four-momentum by
4506        // momentum conservation, so the incoming pair is sufficient for this classification.
4507        let classify_pair = |second_spatial_momentum: (f64, f64), threshold: f64| {
4508            let external_momenta = ExternalFourMomenta::from_iter([
4509                FourMomentum::from_args(F(5.0), F(5.0), F(0.0), F(0.0)),
4510                FourMomentum::from_args(
4511                    F(5.0),
4512                    F(second_spatial_momentum.0),
4513                    F(second_spatial_momentum.1),
4514                    F(0.0),
4515                ),
4516            ]);
4517            esurface.classify_existence(&external_momenta, &lmb, &masses, &F(10.0), &F(threshold))
4518        };
4519
4520        assert!(matches!(
4521            classify_pair((0.0, 5.0), DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
4522            EsurfaceExistence::Existing { .. }
4523        ));
4524        assert!(matches!(
4525            classify_pair((5.0, 0.0), DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
4526            EsurfaceExistence::Pinched { .. }
4527        ));
4528        assert!(matches!(
4529            classify_pair((0.0, 5.0), 1.0),
4530            EsurfaceExistence::Pinched { .. }
4531        ));
4532    }
4533
4534    #[test]
4535    fn classifies_existing_pinched_and_non_existing_surfaces() {
4536        let dummy_graph = dummy_hedge_graph(5);
4537        let lmb = LoopMomentumBasis {
4538            tree: SuBitGraph::empty(0),
4539            loop_edges: vec![EdgeIndex::from(2), EdgeIndex::from(3)].into(),
4540            ext_edges: vec![].into(),
4541            edge_signatures: dummy_graph
4542                .new_edgevec_from_iter(vec![
4543                    LoopExtSignature::from((vec![0, 0], vec![1])),
4544                    LoopExtSignature::from((vec![0, 0], vec![-1])),
4545                    LoopExtSignature::from((vec![1, 0], vec![0])),
4546                    LoopExtSignature::from((vec![0, 1], vec![0])),
4547                    LoopExtSignature::from((vec![1, 1], vec![-1])),
4548                ])
4549                .unwrap(),
4550        };
4551        let esurface = Esurface {
4552            energies: vec![EdgeIndex::from(2), EdgeIndex::from(3), EdgeIndex::from(4)],
4553            external_shift: vec![(EdgeIndex::from(0), -1)],
4554            vertex_set: VertexSet::dummy(),
4555        };
4556        let masses = dummy_graph.new_edgevec_from_iter(vec![F(0.0); 5]).unwrap();
4557
4558        let classification = |energy| {
4559            let external_momenta = ExternalFourMomenta::from_iter([FourMomentum::from_args(
4560                F(energy),
4561                F(-10.0),
4562                F(0.0),
4563                F(0.0),
4564            )]);
4565            esurface.classify_existence(
4566                &external_momenta,
4567                &lmb,
4568                &masses,
4569                &F(10.0),
4570                &F(DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
4571            )
4572        };
4573
4574        assert!(matches!(
4575            classification(11.0),
4576            EsurfaceExistence::Existing { .. }
4577        ));
4578        assert!(matches!(
4579            classification(10.0),
4580            EsurfaceExistence::Pinched { .. }
4581        ));
4582        assert!(matches!(
4583            classification(10.0 + 1.0e-8),
4584            EsurfaceExistence::Pinched { .. }
4585        ));
4586        assert!(matches!(
4587            classification(9.0),
4588            EsurfaceExistence::NonExisting { .. }
4589        ));
4590    }
4591
4592    #[test]
4593    fn test_esurface() {
4594        let dummy_graph = dummy_hedge_graph(5);
4595
4596        let _energies_cache = dummy_graph
4597            .new_edgevec_from_iter([F(1.), F(2.), F(3.), F(4.), F(5.)])
4598            .unwrap();
4599
4600        let energies = vec![EdgeIndex::from(0), EdgeIndex::from(1), EdgeIndex::from(2)];
4601
4602        let external_shift = vec![(EdgeIndex::from(3), 1), (EdgeIndex::from(4), 1)];
4603
4604        let mut esurface = Esurface {
4605            energies,
4606            external_shift,
4607            vertex_set: VertexSet::dummy(),
4608            //subspace_graph: dummy_graph.full_graph(),
4609        };
4610
4611        let shift_rewrite = ShiftRewrite {
4612            dependent_momentum: EdgeIndex::from(4),
4613            dependent_momentum_expr: vec![
4614                (EdgeIndex::from(1), -1),
4615                (EdgeIndex::from(2), -1),
4616                (EdgeIndex::from(3), -1),
4617            ],
4618        };
4619
4620        esurface.canonicalize_shift(&shift_rewrite);
4621
4622        assert_eq!(
4623            esurface.external_shift,
4624            vec![(EdgeIndex::from(1), -1), (EdgeIndex::from(2), -1)]
4625        );
4626
4627        let energies = vec![EdgeIndex::from(0), EdgeIndex::from(2)];
4628
4629        let external_shift = vec![(EdgeIndex::from(1), -1)];
4630
4631        let _esurface = Esurface {
4632            energies,
4633            external_shift,
4634            vertex_set: VertexSet::dummy(),
4635            //subspace_graph: dummy_graph.full_graph(),
4636        };
4637    }
4638
4639    #[test]
4640    fn test_add_external_shifts() {
4641        let shift_1 = vec![
4642            (EdgeIndex::from(0), 1),
4643            (EdgeIndex::from(1), 1),
4644            (EdgeIndex::from(2), -1),
4645        ];
4646        let shift_2 = vec![(EdgeIndex::from(1), -1), (EdgeIndex::from(2), 1)];
4647
4648        let add = add_external_shifts(&shift_1, &shift_2);
4649
4650        assert_eq!(add, vec![(EdgeIndex::from(0), 1)]);
4651
4652        let shift_3 = vec![(EdgeIndex::from(3), 1), (EdgeIndex::from(4), -1)];
4653        let shift_4 = vec![
4654            (EdgeIndex::from(0), 1),
4655            (EdgeIndex::from(1), 1),
4656            (EdgeIndex::from(2), 1),
4657            (EdgeIndex::from(4), 1),
4658        ];
4659
4660        let add = add_external_shifts(&shift_3, &shift_4);
4661
4662        assert_eq!(
4663            add,
4664            vec![
4665                (EdgeIndex::from(0), 1),
4666                (EdgeIndex::from(1), 1),
4667                (EdgeIndex::from(2), 1),
4668                (EdgeIndex::from(3), 1)
4669            ]
4670        );
4671    }
4672
4673    #[test]
4674    fn test_esurface_equality() {
4675        let esurface_1 = Esurface {
4676            energies: vec![EdgeIndex::from(3), EdgeIndex::from(5)],
4677            external_shift: vec![(EdgeIndex::from(0), 1), (EdgeIndex::from(1), 1)],
4678            vertex_set: VertexSet::dummy(),
4679            //subspace_graph: unsafe { InternalSubGraph::new_unchecked(SuBitGraph::new()) },
4680        };
4681
4682        let esurface_2 = Esurface {
4683            energies: vec![EdgeIndex::from(3), EdgeIndex::from(5)],
4684            external_shift: vec![(EdgeIndex::from(0), 1), (EdgeIndex::from(1), 1)],
4685            vertex_set: VertexSet::dummy(),
4686            //subspace_graph: unsafe { InternalSubGraph::new_unchecked(SuBitGraph::new()) },
4687        };
4688
4689        assert_eq!(esurface_1, esurface_2);
4690    }
4691
4692    #[test]
4693    fn test_to_atom() {
4694        let external_shift = vec![(EdgeIndex::from(1), -1)];
4695
4696        let esurface = Esurface {
4697            energies: vec![EdgeIndex::from(2), EdgeIndex::from(3)],
4698            external_shift,
4699            vertex_set: VertexSet::dummy(),
4700            // subspace_graph: unsafe { InternalSubGraph::new_unchecked(SuBitGraph::new()) },
4701        };
4702
4703        let esurface_atom = esurface.to_atom(&[]);
4704        let expected_atom = parse!("OSE(2) + OSE(3) - Q(1, spenso::cind(0))");
4705
4706        let diff = esurface_atom - &expected_atom;
4707        let diff = diff.expand();
4708        assert_eq!(diff, Atom::new());
4709    }
4710
4711    mod failing {
4712        use super::*;
4713
4714        #[test]
4715        fn test_from_cut_left_dt() {
4716            let mut hedge_graph_builder = HedgeGraphBuilder::new();
4717            let nodes = (0..4)
4718                .map(|_| hedge_graph_builder.add_node(()))
4719                .collect_vec();
4720
4721            hedge_graph_builder.add_edge(nodes[0], nodes[1], (), Orientation::Undirected);
4722            hedge_graph_builder.add_edge(nodes[0], nodes[2], (), Orientation::Undirected);
4723            hedge_graph_builder.add_edge(nodes[1], nodes[2], (), Orientation::Undirected);
4724            hedge_graph_builder.add_edge(nodes[1], nodes[3], (), Orientation::Undirected);
4725            hedge_graph_builder.add_edge(nodes[2], nodes[3], (), Orientation::Undirected);
4726
4727            hedge_graph_builder.add_external_edge(
4728                nodes[0],
4729                (),
4730                Orientation::Undirected,
4731                Flow::Sink,
4732            );
4733            hedge_graph_builder.add_external_edge(
4734                nodes[3],
4735                (),
4736                Orientation::Undirected,
4737                Flow::Source,
4738            );
4739
4740            let double_triangle: HedgeGraph<(), (), ()> =
4741                hedge_graph_builder.build::<NodeStorageVec<()>>();
4742            let node_0 = double_triangle.iter_crown(nodes[0]).into();
4743            let node_3 = double_triangle.iter_crown(nodes[3]).into();
4744
4745            let cuts = double_triangle.all_cuts(node_0, node_3);
4746
4747            let cross_section_cuts = cuts
4748                .into_iter()
4749                .map(|(node_l, cut, node_r)| CrossSectionCut {
4750                    cut,
4751                    left: node_l,
4752                    right: node_r,
4753                })
4754                .map(|cut| Esurface::new_from_cut_left(&double_triangle, &cut, None))
4755                .collect_vec();
4756
4757            let expected_esurfaces = vec![
4758                Esurface {
4759                    energies: vec![EdgeIndex::from(0), EdgeIndex::from(1)],
4760                    external_shift: vec![(EdgeIndex::from(5), -1)],
4761                    vertex_set: VertexSet::dummy(),
4762                    //subspace_graph: double_triangle.full_graph(),
4763                },
4764                Esurface {
4765                    energies: vec![EdgeIndex::from(0), EdgeIndex::from(2), EdgeIndex::from(4)],
4766                    external_shift: vec![(EdgeIndex::from(5), -1)],
4767                    vertex_set: VertexSet::dummy(),
4768                    //subspace_graph: double_triangle.full_graph(),
4769                },
4770                Esurface {
4771                    energies: vec![EdgeIndex::from(3), EdgeIndex::from(4)],
4772                    external_shift: vec![(EdgeIndex::from(5), -1)],
4773                    vertex_set: VertexSet::dummy(),
4774                    //subspace_graph: double_triangle.full_graph(),
4775                },
4776                Esurface {
4777                    energies: vec![EdgeIndex::from(1), EdgeIndex::from(2), EdgeIndex::from(3)],
4778                    external_shift: vec![(EdgeIndex::from(5), -1)],
4779                    vertex_set: VertexSet::dummy(),
4780                    //subspace_graph: double_triangle.full_graph(),
4781                },
4782            ];
4783
4784            for expected_esurface in expected_esurfaces {
4785                assert!(cross_section_cuts.contains(&expected_esurface));
4786            }
4787        }
4788
4789        #[test]
4790        fn test_from_cut_left_box() {
4791            let mut hedge_graph_builder = HedgeGraphBuilder::new();
4792            let nodes = (0..4)
4793                .map(|_| hedge_graph_builder.add_node(()))
4794                .collect_vec();
4795
4796            hedge_graph_builder.add_edge(nodes[0], nodes[1], (), Orientation::Undirected);
4797            hedge_graph_builder.add_edge(nodes[1], nodes[2], (), Orientation::Undirected);
4798            hedge_graph_builder.add_edge(nodes[2], nodes[3], (), Orientation::Undirected);
4799            hedge_graph_builder.add_edge(nodes[3], nodes[0], (), Orientation::Undirected);
4800
4801            hedge_graph_builder.add_external_edge(
4802                nodes[0],
4803                (),
4804                Orientation::Undirected,
4805                Flow::Sink,
4806            );
4807            hedge_graph_builder.add_external_edge(
4808                nodes[1],
4809                (),
4810                Orientation::Undirected,
4811                Flow::Source,
4812            );
4813            hedge_graph_builder.add_external_edge(
4814                nodes[2],
4815                (),
4816                Orientation::Undirected,
4817                Flow::Source,
4818            );
4819            hedge_graph_builder.add_external_edge(
4820                nodes[3],
4821                (),
4822                Orientation::Undirected,
4823                Flow::Sink,
4824            );
4825
4826            let box_graph: HedgeGraph<(), (), ()> =
4827                hedge_graph_builder.build::<NodeStorageVec<()>>();
4828
4829            let node_0 = box_graph.iter_crown(nodes[0]).into();
4830            let node_2 = box_graph.iter_crown(nodes[2]).into();
4831
4832            let cuts = box_graph.all_cuts(node_0, node_2);
4833            assert_eq!(cuts.len(), 4);
4834
4835            let cross_section_cuts = cuts
4836                .into_iter()
4837                .map(|(node_l, cut, node_r)| CrossSectionCut {
4838                    cut,
4839                    left: node_l,
4840                    right: node_r,
4841                })
4842                .map(|cut| Esurface::new_from_cut_left(&box_graph, &cut, None))
4843                .collect_vec();
4844
4845            let expected_esurfaces = vec![
4846                Esurface {
4847                    energies: vec![EdgeIndex::from(0), EdgeIndex::from(3)],
4848                    external_shift: vec![(EdgeIndex::from(4), -1)],
4849                    vertex_set: VertexSet::dummy(),
4850                    //subspace_graph: box_graph.full_graph(),
4851                },
4852                Esurface {
4853                    energies: vec![EdgeIndex::from(0), EdgeIndex::from(2)],
4854                    external_shift: vec![(EdgeIndex::from(4), -1), (EdgeIndex::from(7), -1)],
4855                    vertex_set: VertexSet::dummy(),
4856                    //subspace_graph: box_graph.full_graph(),
4857                },
4858                Esurface {
4859                    energies: vec![EdgeIndex::from(1), EdgeIndex::from(3)],
4860                    external_shift: vec![(EdgeIndex::from(4), -1), (EdgeIndex::from(5), 1)],
4861                    vertex_set: VertexSet::dummy(),
4862                    //subspace_graph: box_graph.full_graph(),
4863                },
4864                Esurface {
4865                    energies: vec![EdgeIndex::from(1), EdgeIndex::from(2)],
4866                    external_shift: vec![
4867                        (EdgeIndex::from(4), -1),
4868                        (EdgeIndex::from(5), 1),
4869                        (EdgeIndex::from(7), -1),
4870                    ],
4871                    vertex_set: VertexSet::dummy(),
4872                    //subspace_graph: box_graph.full_graph(),
4873                },
4874            ];
4875
4876            for expected_esurface in expected_esurfaces {
4877                assert!(cross_section_cuts.contains(&expected_esurface));
4878            }
4879        }
4880    }
4881}