Skip to main content

gammalooprs/subtraction/
overlap.rs

1use crate::GammaLoopContext;
2use crate::cff::esurface::EsurfaceCollection;
3use crate::cff::esurface::EsurfaceID;
4use crate::cff::esurface::ExistingEsurfaceId;
5use crate::cff::esurface::ExistingEsurfaces;
6use crate::cff::esurface::GroupEsurfaceId;
7use crate::cff::esurface::RaisedEsurfaceData;
8use crate::cff::esurface::RaisedEsurfaceId;
9use crate::cff::esurface::{esurface_value_is_strictly_inside, get_representative};
10use crate::graph::GraphGroupPosition;
11use crate::graph::LoopMomentumBasis;
12use crate::integrands::process::GenericEvaluator;
13use crate::momentum::ThreeMomentum;
14use crate::momentum::sample::ExternalFourMomenta;
15use crate::momentum::sample::LoopMomenta;
16use crate::momentum::signature::LoopExtSignature;
17use crate::processes::EvaluatorSettings;
18use crate::settings::RuntimeSettings;
19use crate::utils::F;
20use crate::utils::GS;
21use crate::utils::compute_shift_part;
22use crate::utils::hyperdual_utils::simple_n_deriv_shape;
23use ahash::HashMap;
24use ahash::HashMapExt;
25use ahash::HashSet;
26use bincode_trait_derive::Decode;
27use bincode_trait_derive::Encode;
28use clarabel::algebra::*;
29use clarabel::solver::*;
30use eyre::{Result, eyre};
31use itertools::Itertools;
32use linnet::half_edge::involution::EdgeVec;
33use spenso::algebra::algebraic_traits::IsZero;
34use std::cell::RefCell;
35use std::fmt::Display;
36use symbolica::atom::Atom;
37use symbolica::atom::AtomCore;
38use symbolica::evaluate::FunctionMap;
39use symbolica::evaluate::OptimizationSettings;
40use symbolica::function;
41use typed_index_collections::TiVec;
42
43#[derive(Debug, Clone, Encode, Decode)]
44#[trait_decode(trait = GammaLoopContext)]
45pub struct OverlapGroup {
46    pub existing_esurfaces: Vec<ExistingEsurfaceId>,
47    pub complement: Vec<ExistingEsurfaceId>,
48    /// Amplitude overlap centers are stored in the identity-probe frame.
49    /// Counterterm evaluation rotates them exactly once into the current probe frame.
50    pub center: LoopMomenta<F<f64>>,
51    pub prefactor_evaluator: Option<Vec<RefCell<GenericEvaluator>>>,
52}
53
54#[derive(Debug, Clone, Encode, Decode)]
55#[trait_decode(trait = GammaLoopContext)]
56pub struct OverlapStructure {
57    pub overlap_groups: Vec<OverlapGroup>,
58    pub existing_esurfaces: ExistingEsurfaces,
59}
60
61impl Display for OverlapStructure {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        let existing_esurfaces: Vec<_> = self.existing_esurfaces.iter().map(|id| id.0).collect();
64        writeln!(f, "existing esurfaces: {:?}", existing_esurfaces)?;
65
66        for (i, group) in self.overlap_groups.iter().enumerate() {
67            writeln!(f, "Group {}:", i)?;
68            let existing_esurfaces_in_group: Vec<_> = group
69                .existing_esurfaces
70                .iter()
71                .map(|id| self.existing_esurfaces[*id].0)
72                .collect();
73
74            writeln!(f, "center:\n {}", group.center)?;
75            writeln!(
76                f,
77                "existing esurfaces in group: {:?}",
78                existing_esurfaces_in_group
79            )?;
80        }
81
82        Ok(())
83    }
84}
85
86impl OverlapStructure {
87    pub fn fill_in_complements(&mut self) {
88        for group in self.overlap_groups.iter_mut() {
89            group.complement = self
90                .existing_esurfaces
91                .iter_enumerated()
92                .map(|(existing_esurface_id, _)| existing_esurface_id)
93                .filter(|&existing_esurface_id| {
94                    !group.existing_esurfaces.contains(&existing_esurface_id)
95                })
96                .collect();
97        }
98    }
99
100    pub fn build_evaluators(
101        &mut self,
102        atoms: &TiVec<GroupEsurfaceId, Atom>,
103        optimization_settings: &OptimizationSettings,
104        num_loops: usize,
105        num_externals: usize,
106        model_params: Vec<Atom>,
107        power: i32,
108    ) -> Result<()> {
109        let group_square_atoms = self
110            .overlap_groups
111            .iter()
112            .map(|group| {
113                group
114                    .complement
115                    .iter()
116                    .map(|&existing_esurface_id| {
117                        let esurface = self.existing_esurfaces[existing_esurface_id];
118                        let atom = &atoms[esurface];
119                        atom.pow(power)
120                    })
121                    .reduce(|prod, atom| prod * atom)
122                    .unwrap_or_else(|| Atom::num(1))
123            })
124            .collect_vec();
125
126        let denominator = group_square_atoms
127            .iter()
128            .fold(Atom::new(), |sum, atom| sum + atom);
129
130        let params = (0..num_loops)
131            .flat_map(|loop_index| {
132                (1..=3).map(move |spatial_index| function!(GS.loop_mom, loop_index, spatial_index))
133            })
134            .chain((0..num_externals).flat_map(|external_index| {
135                (0..=3).map(move |spatial_index| {
136                    function!(GS.external_mom, external_index, spatial_index)
137                })
138            }))
139            .chain(model_params)
140            .collect_vec();
141
142        for (group, square_atom) in self.overlap_groups.iter_mut().zip(group_square_atoms) {
143            let atom = square_atom / &denominator;
144            let num_orders = power.saturating_sub(1).max(1) as usize;
145
146            let evaluators = (0..num_orders)
147                .map(|order_index| {
148                    GenericEvaluator::new_from_raw_params(
149                        [atom.clone()],
150                        &params,
151                        &FunctionMap::new(),
152                        vec![],
153                        optimization_settings.clone(),
154                        (order_index > 0).then(|| simple_n_deriv_shape(order_index)),
155                        &EvaluatorSettings::default(),
156                    )
157                    .map(RefCell::new)
158                })
159                .collect::<Result<Vec<_>>>()?;
160
161            group.prefactor_evaluator = Some(evaluators);
162        }
163
164        Ok(())
165    }
166
167    pub fn new_empty() -> Self {
168        Self {
169            overlap_groups: vec![],
170            existing_esurfaces: ExistingEsurfaces::new(),
171        }
172    }
173
174    pub(crate) fn localized_to_existing_surfaces(
175        &self,
176        local_esurface_exists: &TiVec<GroupEsurfaceId, bool>,
177    ) -> Self {
178        let mut remapped_existing_esurfaces = ExistingEsurfaces::new();
179        let mut existing_esurface_map: TiVec<ExistingEsurfaceId, Option<ExistingEsurfaceId>> =
180            TiVec::with_capacity(self.existing_esurfaces.len());
181
182        for &group_esurface_id in self.existing_esurfaces.iter() {
183            if local_esurface_exists[group_esurface_id] {
184                let remapped_existing_esurface_id =
185                    ExistingEsurfaceId::from(remapped_existing_esurfaces.len());
186                remapped_existing_esurfaces.push(group_esurface_id);
187                existing_esurface_map.push(Some(remapped_existing_esurface_id));
188            } else {
189                existing_esurface_map.push(None);
190            }
191        }
192
193        let mut localized_overlap_groups = Vec::with_capacity(self.overlap_groups.len());
194        for overlap_group in &self.overlap_groups {
195            let mut localized_existing_esurfaces = overlap_group
196                .existing_esurfaces
197                .iter()
198                .filter_map(|&existing_esurface_id| existing_esurface_map[existing_esurface_id])
199                .collect_vec();
200            localized_existing_esurfaces.sort_unstable();
201            localized_existing_esurfaces.dedup();
202
203            if localized_existing_esurfaces.is_empty()
204                || localized_overlap_groups.iter().any(|group: &OverlapGroup| {
205                    group.existing_esurfaces == localized_existing_esurfaces
206                })
207            {
208                continue;
209            }
210
211            localized_overlap_groups.push(OverlapGroup {
212                existing_esurfaces: localized_existing_esurfaces,
213                complement: vec![],
214                center: overlap_group.center.clone(),
215                prefactor_evaluator: None,
216            });
217        }
218
219        let mut localized = Self {
220            overlap_groups: localized_overlap_groups,
221            existing_esurfaces: remapped_existing_esurfaces,
222        };
223        localized.fill_in_complements();
224        localized
225    }
226}
227/// Helper struct to construct the socp problem
228struct PropagatorConstraint<'a> {
229    mass_pointer: Option<usize>, // pointer to value of unique mass
230    signature: &'a LoopExtSignature,
231}
232
233impl PropagatorConstraint<'_> {
234    fn get_dimension(&self) -> usize {
235        let mass_value = if self.mass_pointer.is_some() { 1 } else { 0 };
236
237        3 + mass_value + 1
238    }
239}
240
241fn extract_center(num_loops: usize, solution: &[f64]) -> LoopMomenta<F<f64>> {
242    let len = solution.len();
243    let num_loop_vars = 3 * num_loops;
244
245    solution[len - num_loop_vars..]
246        .chunks(3)
247        .map(|window| {
248            ThreeMomentum::new(
249                F::from_f64(window[0]),
250                F::from_f64(window[1]),
251                F::from_f64(window[2]),
252            )
253        })
254        .collect()
255}
256
257fn construct_solver(
258    overlap_input: &OverlapInput,
259    esurfaces_to_consider: &[ExistingEsurfaceId],
260    existing_esurfaces: &ExistingEsurfaces,
261    external_momenta: &ExternalFourMomenta<F<f64>>,
262    verbose: bool,
263) -> DefaultSolver {
264    let num_loops = overlap_input
265        .graph_data
266        .first()
267        .expect("no graphs passed to overlap")
268        .lmb
269        .loop_edges
270        .len();
271
272    let num_loop_vars = 3 * num_loops;
273
274    // first we study the structure of the problem
275    let mut propagator_constraints: Vec<PropagatorConstraint> = Vec::with_capacity(20);
276
277    let mut inequivalent_masses: Vec<F<f64>> = vec![];
278
279    let mut esurface_constraints: Vec<Vec<usize>> = Vec::with_capacity(esurfaces_to_consider.len());
280    let local_esurfaces_to_consider = esurfaces_to_consider
281        .iter()
282        .flat_map(|existing_esurface_id| {
283            let group_esurface_id = existing_esurfaces[*existing_esurface_id];
284            overlap_input.group_esurface_map[group_esurface_id]
285                .iter_enumerated()
286                .filter_map(move |(graph_group_pos, option_raised_esurface_id)| {
287                    option_raised_esurface_id.and_then(|raised_esurface_id| {
288                        overlap_input.local_esurface_exists[graph_group_pos][group_esurface_id]
289                            .then_some((*existing_esurface_id, graph_group_pos, raised_esurface_id))
290                    })
291                })
292        })
293        .collect_vec();
294
295    for (_, graph_group_pos, raised_esurface_id) in local_esurfaces_to_consider.iter().copied() {
296        let esurface_id = representative_local_esurface_id(
297            &overlap_input.graph_data[graph_group_pos],
298            raised_esurface_id,
299        );
300
301        let esurface = &overlap_input.graph_data[graph_group_pos].esurfaces[esurface_id];
302        let lmb = overlap_input.graph_data[graph_group_pos].lmb;
303        let edge_masses = &overlap_input.graph_data[graph_group_pos].edge_masses;
304
305        let mut esurface_constraint_indices: Vec<usize> = Vec::with_capacity(6);
306
307        for &edge_id in &esurface.energies {
308            if let Some(edge_position) = propagator_constraints
309                .iter()
310                .position(|constraint| *constraint.signature == lmb.edge_signatures[edge_id])
311            {
312                esurface_constraint_indices.push(edge_position);
313            } else {
314                let mass_pointer = if edge_masses[edge_id].is_zero() {
315                    None
316                } else {
317                    Some(
318                        if let Some(mass_position) = inequivalent_masses
319                            .iter()
320                            .position(|&x| x == edge_masses[edge_id])
321                        {
322                            mass_position
323                        } else {
324                            inequivalent_masses.push(edge_masses[edge_id]);
325                            inequivalent_masses.len() - 1
326                        },
327                    )
328                };
329
330                let signature = &lmb.edge_signatures[edge_id];
331
332                let propagator_constraint = PropagatorConstraint {
333                    mass_pointer,
334                    signature,
335                };
336
337                propagator_constraints.push(propagator_constraint);
338                esurface_constraint_indices.push(propagator_constraints.len() - 1);
339            };
340        }
341
342        esurface_constraints.push(esurface_constraint_indices);
343    }
344
345    // now we know the structure, so we can put it in matrix form.
346    // variables are stored as [r , x_p_0, ... x_p_m, k1_x, k1_y, ... k_n_x, k_n_y, k_n_z]
347    let propagator_index_offset = 1;
348    let loop_momentum_offset = propagator_index_offset + propagator_constraints.len();
349
350    let num_primal_variables = loop_momentum_offset + num_loop_vars;
351
352    let cone_dimension_sum = propagator_constraints
353        .iter()
354        .map(|constraint| constraint.get_dimension())
355        .sum::<usize>();
356
357    let num_constaints = 1 + esurface_constraints.len() + cone_dimension_sum;
358
359    // quadratic part of the objective function, we don't need this for now
360    let p_matrix: CscMatrix<f64> =
361        CscMatrix::spalloc((num_primal_variables, num_primal_variables), 0);
362
363    // objective function
364    let mut q_vector = vec![0.0; num_primal_variables];
365    q_vector[0] = 1.0;
366
367    // construct the cones
368    let mut cones: Vec<SupportedConeT<f64>> = Vec::with_capacity(1 + propagator_constraints.len());
369    cones.push(NonnegativeConeT(1));
370    cones.push(NonnegativeConeT(esurface_constraints.len()));
371
372    for prop_constraint in &propagator_constraints {
373        cones.push(SecondOrderConeT(prop_constraint.get_dimension()));
374    }
375
376    // write the constaint equations
377
378    // perhaps it is faster to build the sparse matrix directly, but it is also extremely unreadable
379    let mut a_matrix = vec![vec![0.0; num_primal_variables]; num_constaints];
380    let mut b_vector = vec![0.0; num_constaints];
381
382    a_matrix[0][0] = 1.0;
383    // esurface constraints
384    for (constraint_index, ((_, graph_group_pos, raised_esurface_id), esurface_constraint)) in
385        local_esurfaces_to_consider
386            .iter()
387            .zip(esurface_constraints.iter())
388            .enumerate()
389    {
390        for prop_index in esurface_constraint {
391            a_matrix[constraint_index + 1][*prop_index + propagator_index_offset] = 1.0;
392        }
393        let esurface_id = representative_local_esurface_id(
394            &overlap_input.graph_data[*graph_group_pos],
395            *raised_esurface_id,
396        );
397        let lmb = overlap_input.graph_data[*graph_group_pos].lmb;
398        let esurface = &overlap_input.graph_data[*graph_group_pos].esurfaces[esurface_id];
399
400        let shift_part = esurface.compute_shift_part_from_momenta(external_momenta, lmb);
401        b_vector[constraint_index + 1] = -shift_part.0;
402        a_matrix[constraint_index + 1][0] = -1.0;
403    }
404
405    // propagator constraints
406    let mut vertical_offset = esurface_constraints.len() + 1;
407    for (cone_index, propagator_constraint) in propagator_constraints.iter().enumerate() {
408        a_matrix[vertical_offset][propagator_index_offset + cone_index] = -1.0;
409        vertical_offset += 1;
410
411        let spatial_shift =
412            compute_shift_part(&propagator_constraint.signature.external, external_momenta);
413
414        b_vector[vertical_offset] = spatial_shift.spatial.px.0;
415        b_vector[vertical_offset + 1] = spatial_shift.spatial.py.0;
416        b_vector[vertical_offset + 2] = spatial_shift.spatial.pz.0;
417
418        for (loop_index, individual_loop_signature) in
419            propagator_constraint.signature.internal.iter().enumerate()
420        {
421            if individual_loop_signature.is_sign() {
422                a_matrix[vertical_offset][loop_momentum_offset + 3 * loop_index] =
423                    -(*individual_loop_signature as i8) as f64;
424                a_matrix[vertical_offset + 1][loop_momentum_offset + 3 * loop_index + 1] =
425                    -(*individual_loop_signature as i8) as f64;
426                a_matrix[vertical_offset + 2][loop_momentum_offset + 3 * loop_index + 2] =
427                    -(*individual_loop_signature as i8) as f64;
428            }
429        }
430
431        vertical_offset += 3;
432
433        if let Some(mass_index) = propagator_constraint.mass_pointer {
434            b_vector[vertical_offset] = inequivalent_masses[mass_index].0;
435            vertical_offset += 1;
436        }
437    }
438
439    let a_matrix_sparse = CscMatrix::from(&a_matrix);
440
441    let settings = DefaultSettingsBuilder::default()
442        .verbose(verbose)
443        .build()
444        .unwrap();
445
446    DefaultSolver::new(
447        &p_matrix,
448        &q_vector,
449        &a_matrix_sparse,
450        &b_vector,
451        &cones,
452        settings,
453    )
454    .unwrap()
455}
456
457pub(crate) fn find_center(
458    overlap_input: &OverlapInput,
459    esurfaces_to_consider: &[ExistingEsurfaceId],
460    existing_esurfaces: &ExistingEsurfaces,
461    external_momenta: &ExternalFourMomenta<F<f64>>,
462    verbose: bool,
463) -> Option<LoopMomenta<F<f64>>> {
464    let mut solver = construct_solver(
465        overlap_input,
466        esurfaces_to_consider,
467        existing_esurfaces,
468        external_momenta,
469        verbose,
470    );
471
472    solver.solve();
473
474    let loop_number = overlap_input
475        .graph_data
476        .first()
477        .expect("no graphs passed to overlap")
478        .lmb
479        .loop_edges
480        .len();
481
482    let group_esurfaces_to_check = esurfaces_to_consider
483        .iter()
484        .map(|&existing_esurface_id| existing_esurfaces[existing_esurface_id])
485        .collect_vec();
486
487    if solver.solution.status == SolverStatus::Solved {
488        let center = extract_center(loop_number, &solver.solution.x);
489        check_center_for_group_esurfaces(
490            overlap_input,
491            &group_esurfaces_to_check,
492            &center,
493            external_momenta,
494        )
495        .then_some(center)
496    } else if solver.solution.status == SolverStatus::AlmostSolved
497        || solver.solution.status == SolverStatus::InsufficientProgress
498    {
499        // if the solver did not converge, we check if the solution is still valid
500        let center = extract_center(loop_number, &solver.solution.x);
501
502        let is_valid = check_center_for_group_esurfaces(
503            overlap_input,
504            &group_esurfaces_to_check,
505            &center,
506            external_momenta,
507        );
508
509        if is_valid { Some(center) } else { None }
510    } else {
511        if verbose {
512            println!("{:?}", solver.solution.x);
513        }
514
515        None
516    }
517}
518
519pub struct SingleGraphOverlapData<'a> {
520    pub lmb: &'a LoopMomentumBasis,
521    pub esurfaces: &'a EsurfaceCollection,
522    pub raised_data: &'a RaisedEsurfaceData,
523    pub edge_masses: EdgeVec<F<f64>>,
524}
525
526pub struct OverlapInput<'a> {
527    pub graph_data: TiVec<GraphGroupPosition, SingleGraphOverlapData<'a>>,
528    pub settings: &'a RuntimeSettings,
529    pub group_esurface_map:
530        TiVec<GroupEsurfaceId, TiVec<GraphGroupPosition, Option<RaisedEsurfaceId>>>,
531    pub local_esurface_exists: TiVec<GraphGroupPosition, TiVec<GroupEsurfaceId, bool>>,
532}
533
534fn representative_local_esurface_id(
535    graph_data: &SingleGraphOverlapData,
536    raised_esurface_id: RaisedEsurfaceId,
537) -> EsurfaceID {
538    graph_data.raised_data.raised_groups[raised_esurface_id].esurface_ids[0]
539}
540
541fn check_center_for_group_esurfaces(
542    overlap_input: &OverlapInput,
543    group_esurfaces: &[GroupEsurfaceId],
544    center: &LoopMomenta<F<f64>>,
545    external_momenta: &ExternalFourMomenta<F<f64>>,
546) -> bool {
547    group_esurfaces.iter().all(|&group_esurface_id| {
548        let mut has_local_esurface = false;
549
550        let all_local_valid = overlap_input.group_esurface_map[group_esurface_id]
551            .iter_enumerated()
552            .filter_map(|(graph_group_pos, option_raised_esurface_id)| {
553                option_raised_esurface_id.and_then(|raised_esurface_id| {
554                    overlap_input.local_esurface_exists[graph_group_pos][group_esurface_id]
555                        .then_some((graph_group_pos, raised_esurface_id))
556                })
557            })
558            .all(|(graph_group_position, raised_esurface_id)| {
559                has_local_esurface = true;
560                let esurface_id = representative_local_esurface_id(
561                    &overlap_input.graph_data[graph_group_position],
562                    raised_esurface_id,
563                );
564                let esurface =
565                    &overlap_input.graph_data[graph_group_position].esurfaces[esurface_id];
566
567                let lmb = overlap_input.graph_data[graph_group_position].lmb;
568                let edge_masses = &overlap_input.graph_data[graph_group_position].edge_masses;
569
570                let esurface_val =
571                    esurface.compute_from_momenta(lmb, edge_masses, center, external_momenta);
572
573                esurface_value_is_strictly_inside(
574                    &esurface_val,
575                    &F(overlap_input.settings.kinematics.e_cm),
576                )
577            });
578
579        has_local_esurface && all_local_valid
580    })
581}
582
583pub(crate) fn check_global_center(
584    overlap_input: &OverlapInput,
585    existing_esurfaces: &ExistingEsurfaces,
586    center: &LoopMomenta<F<f64>>,
587    external_momenta: &ExternalFourMomenta<F<f64>>,
588) -> bool {
589    let group_esurfaces = existing_esurfaces.iter().copied().collect_vec();
590    check_center_for_group_esurfaces(overlap_input, &group_esurfaces, center, external_momenta)
591}
592
593/// Runtime overlap failures are returned so the stability machinery can retry at higher precision.
594/// Structural generation invariants are still asserted where malformed generated data is unrecoverable.
595pub(crate) fn find_maximal_overlap(
596    overlap_input: &OverlapInput,
597    existing_esurfaces: &ExistingEsurfaces,
598    external_momenta: &ExternalFourMomenta<F<f64>>,
599) -> Result<OverlapStructure> {
600    let mut res = OverlapStructure {
601        overlap_groups: vec![],
602        existing_esurfaces: existing_esurfaces.clone(),
603    };
604
605    let settings = overlap_input.settings;
606
607    let all_existing_esurfaces = existing_esurfaces
608        .iter_enumerated()
609        .map(|a| a.0)
610        .collect_vec();
611
612    if let Some(global_center) = &settings.subtraction.overlap_settings.force_global_center {
613        let global_center_f = global_center
614            .iter()
615            .map(|coordinates| ThreeMomentum {
616                px: F(coordinates[0]),
617                py: F(coordinates[1]),
618                pz: F(coordinates[2]),
619            })
620            .collect();
621
622        if !settings.subtraction.overlap_settings.check_global_center {
623            tracing::warn!(
624                "overlap_settings.check_global_center=false is deprecated; forced centers are always validated"
625            );
626        }
627
628        let is_valid = check_global_center(
629            overlap_input,
630            existing_esurfaces,
631            &global_center_f,
632            external_momenta,
633        );
634
635        if !is_valid {
636            return Err(eyre!(
637                "Center provided is not finite and strictly inside all existing esurfaces"
638            ));
639        }
640
641        let single_group = OverlapGroup {
642            existing_esurfaces: all_existing_esurfaces,
643            center: global_center_f,
644            complement: vec![],
645            prefactor_evaluator: None,
646        };
647        res.overlap_groups.push(single_group);
648
649        res.fill_in_complements();
650        return Ok(res);
651    }
652
653    if settings.subtraction.overlap_settings.try_origin {
654        let global_loop_count = overlap_input
655            .graph_data
656            .first()
657            .unwrap()
658            .lmb
659            .loop_edges
660            .len();
661        let origin = LoopMomenta::from_iter(
662            (0..global_loop_count).map(|_| ThreeMomentum::new(F(0.0), F(0.0), F(0.0))),
663        );
664
665        let is_valid =
666            check_global_center(overlap_input, existing_esurfaces, &origin, external_momenta);
667
668        if is_valid {
669            let single_group = OverlapGroup {
670                existing_esurfaces: all_existing_esurfaces,
671                center: origin,
672                complement: vec![],
673                prefactor_evaluator: None,
674            };
675            res.overlap_groups.push(single_group);
676            res.fill_in_complements();
677            return Ok(res);
678        }
679    }
680
681    if settings.subtraction.overlap_settings.try_origin_all_lmbs {
682        todo!("Not all heuristics implemented")
683    }
684
685    // first try if all esurfaces have a single center, we explitely seach a center instead of trying the
686    // origin. This is because the origin might not be optimal.
687    let option_center = find_center(
688        overlap_input,
689        &all_existing_esurfaces,
690        existing_esurfaces,
691        external_momenta,
692        false,
693    );
694
695    if let Some(center) = option_center {
696        let single_group = OverlapGroup {
697            existing_esurfaces: all_existing_esurfaces,
698            center,
699            complement: vec![],
700            prefactor_evaluator: None,
701        };
702        res.overlap_groups.push(single_group);
703        res.fill_in_complements();
704        return Ok(res);
705    }
706
707    // if the center is not valid, create a table of all pairs
708    let esurface_pairs = EsurfacePairs::new(overlap_input, existing_esurfaces, external_momenta);
709
710    // if settings.general.debug > 3 {
711    //     DEBUG_LOGGER.write("overlap_pairs", &esurface_pairs);
712    // }
713
714    let mut num_disconnected_surfaces = 0;
715
716    for (existing_esurface_id, &esurface_id) in existing_esurfaces.iter_enumerated() {
717        // if an esurface overlaps with no other esurface, it is part of the maximal overlap structure
718        if esurface_pairs.has_no_overlap(existing_esurface_id) {
719            let center = find_center(
720                overlap_input,
721                &[existing_esurface_id],
722                existing_esurfaces,
723                external_momenta,
724                false,
725            )
726            .ok_or_else(|| {
727                let (graph_group_pos, raised_esurface_id) =
728                    get_representative(&overlap_input.group_esurface_map[esurface_id])
729                        .expect("overlap corrupted");
730                let esurface_id = representative_local_esurface_id(
731                    &overlap_input.graph_data[graph_group_pos],
732                    raised_esurface_id,
733                );
734
735                let esurface = &overlap_input.graph_data[graph_group_pos].esurfaces[esurface_id];
736
737                let mut error_message = String::new();
738
739                error_message.push_str(&format!(
740                    "Could not find center of esuface {:?}\n",
741                    esurface_id
742                ));
743                error_message.push_str(&format!("edges: {:?}\n", esurface.energies));
744
745                error_message.push_str(&format!("external shift: {:?}\n", esurface.external_shift));
746
747                error_message.push_str(&format!("External momenta: {:#?}\n", external_momenta));
748
749                eyre!("{}", error_message)
750            })?;
751
752            res.overlap_groups.push(OverlapGroup {
753                existing_esurfaces: vec![existing_esurface_id],
754                center,
755                complement: vec![],
756                prefactor_evaluator: None,
757            });
758            num_disconnected_surfaces += 1;
759        }
760    }
761
762    // if settings.general.debug > 3 {
763    //     DEBUG_LOGGER.write("num_disconnected_surfaces", &num_disconnected_surfaces);
764    // }
765
766    if num_disconnected_surfaces == existing_esurfaces.len() {
767        res.fill_in_complements();
768        return Ok(res);
769    }
770
771    let mut subset_size =
772        if let Some(size) = esurface_pairs.has_pair_with.iter().map(Vec::len).max() {
773            size + 1
774        } else {
775            1
776        };
777
778    while subset_size > 1 {
779        let possible_subsets =
780            esurface_pairs.construct_possible_subsets_of_len(existing_esurfaces, subset_size, &res);
781
782        for subset in possible_subsets.iter() {
783            let option_center = find_center(
784                overlap_input,
785                subset,
786                existing_esurfaces,
787                external_momenta,
788                false,
789            );
790
791            if let Some(center) = option_center {
792                res.overlap_groups.push(OverlapGroup {
793                    existing_esurfaces: subset.clone(),
794                    center,
795                    complement: vec![],
796                    prefactor_evaluator: None,
797                });
798            }
799        }
800
801        // if settings.general.debug > 3 {
802        //     DEBUG_LOGGER.write(
803        //         "subset_size_and_num_possible_subsets_and_res",
804        //         &(subset_size, possible_subsets.len(), &res),
805        //     );
806        // }
807
808        subset_size -= 1;
809    }
810
811    res.fill_in_complements();
812    Ok(res)
813}
814
815fn is_subset_of_result(subset: &[ExistingEsurfaceId], result: &OverlapStructure) -> bool {
816    result.overlap_groups.iter().any(|group| {
817        subset
818            .iter()
819            .all(|&x| group.existing_esurfaces.contains(&x))
820    })
821}
822
823#[derive(Debug)]
824struct EsurfacePairs {
825    data: HashMap<(ExistingEsurfaceId, ExistingEsurfaceId), LoopMomenta<F<f64>>>,
826    has_pair_with: Vec<Vec<ExistingEsurfaceId>>,
827}
828
829impl EsurfacePairs {
830    fn insert(
831        &mut self,
832        pair: (ExistingEsurfaceId, ExistingEsurfaceId),
833        center: LoopMomenta<F<f64>>,
834    ) {
835        if pair.0 > pair.1 {
836            self.data.insert((pair.1, pair.0), center);
837        } else {
838            self.data.insert(pair, center);
839        }
840    }
841
842    fn pair_exists(&self, pair: (ExistingEsurfaceId, ExistingEsurfaceId)) -> bool {
843        if pair.0 > pair.1 {
844            self.data.contains_key(&(pair.1, pair.0))
845        } else {
846            self.data.contains_key(&pair)
847        }
848    }
849
850    fn new_empty(num_existing_esurfaces: usize) -> Self {
851        let capacity = match num_existing_esurfaces {
852            0 => 0,
853            1 => 0,
854            _ => num_existing_esurfaces * (num_existing_esurfaces - 1) / 2,
855        };
856
857        Self {
858            data: HashMap::with_capacity(capacity),
859            has_pair_with: vec![Vec::with_capacity(num_existing_esurfaces); num_existing_esurfaces],
860        }
861    }
862
863    fn new(
864        overlap_input: &OverlapInput,
865        existing_esurfaces: &ExistingEsurfaces,
866        external_momenta: &ExternalFourMomenta<F<f64>>,
867    ) -> Self {
868        let mut res = Self::new_empty(existing_esurfaces.len());
869
870        let all_existing_esurfaces = existing_esurfaces
871            .iter_enumerated()
872            .map(|a| a.0)
873            .collect_vec();
874
875        for (i, &esurface_id_1) in all_existing_esurfaces.iter().enumerate() {
876            for &esurface_id_2 in all_existing_esurfaces.iter().skip(i + 1) {
877                let center = find_center(
878                    overlap_input,
879                    &[esurface_id_1, esurface_id_2],
880                    existing_esurfaces,
881                    external_momenta,
882                    false,
883                );
884
885                if let Some(center) = center {
886                    res.insert((esurface_id_1, esurface_id_2), center);
887                    res.has_pair_with[Into::<usize>::into(esurface_id_1)].push(esurface_id_2);
888                    res.has_pair_with[Into::<usize>::into(esurface_id_2)].push(esurface_id_1);
889                }
890            }
891        }
892
893        res
894    }
895
896    fn has_no_overlap(&self, esurface_id: ExistingEsurfaceId) -> bool {
897        self.has_pair_with[Into::<usize>::into(esurface_id)].is_empty()
898    }
899
900    fn construct_possible_subsets_of_len(
901        &self,
902        existing_esurfaces: &ExistingEsurfaces,
903        subset_len: usize,
904        result: &OverlapStructure,
905    ) -> HashSet<Vec<ExistingEsurfaceId>> {
906        let mut res = HashSet::default();
907        let existing_esurfaces_not_in_overlap = existing_esurfaces
908            .iter_enumerated()
909            .map(|a| a.0)
910            .filter(|&existing_esurface_id| {
911                !result
912                    .overlap_groups
913                    .iter()
914                    .any(|group| group.existing_esurfaces.contains(&existing_esurface_id))
915            });
916
917        let mut possible_options_from_esurfaces_not_in_overlap = HashSet::default();
918
919        for esurface in existing_esurfaces_not_in_overlap.filter(|existing_esurface_id| {
920            self.has_pair_with[Into::<usize>::into(*existing_esurface_id)].len() >= subset_len - 1
921        }) {
922            for possible_combination in self.has_pair_with[Into::<usize>::into(esurface)]
923                .iter()
924                .combinations(subset_len - 1)
925            {
926                let mut option = vec![esurface];
927                option.extend(possible_combination.iter().copied());
928                let mut is_valid = true;
929
930                'pair_loop: for i in 0..subset_len - 1 {
931                    for j in i + 1..subset_len - 1 {
932                        let pair = (
933                            Into::<ExistingEsurfaceId>::into(*possible_combination[i]),
934                            Into::<ExistingEsurfaceId>::into(*possible_combination[j]),
935                        );
936
937                        if !self.pair_exists(pair) {
938                            is_valid = false;
939                            break 'pair_loop;
940                        }
941                    }
942                }
943
944                if is_valid {
945                    option.sort_unstable();
946                    possible_options_from_esurfaces_not_in_overlap.insert(option);
947                }
948            }
949        }
950
951        let existing_pairs_not_in_overlap = self.data.keys().filter(|(left, right)| {
952            !is_subset_of_result(&[*left, *right], result)
953                && is_subset_of_result(&[*left], result)
954                && is_subset_of_result(&[*right], result)
955                && self.has_pair_with[Into::<usize>::into(*left)].len() >= subset_len - 1
956                && self.has_pair_with[Into::<usize>::into(*right)].len() >= subset_len - 1
957        });
958
959        let mut possible_options_from_pairs_not_in_overlap = HashSet::default();
960
961        for pair in existing_pairs_not_in_overlap {
962            let possible_additions = existing_esurfaces
963                .iter_enumerated()
964                .map(|a| a.0)
965                .filter(|&id| {
966                    id != pair.0
967                        && id != pair.1
968                        && self.has_pair_with[Into::<usize>::into(pair.0)].contains(&id)
969                        && self.has_pair_with[Into::<usize>::into(pair.1)].contains(&id)
970                })
971                .combinations(subset_len - 2);
972
973            for possible_addition in possible_additions {
974                let mut option = vec![pair.0, pair.1];
975                option.extend(possible_addition.iter().copied());
976                option.sort_unstable();
977                possible_options_from_pairs_not_in_overlap.insert(option);
978            }
979        }
980
981        res.extend(possible_options_from_esurfaces_not_in_overlap);
982        res.extend(possible_options_from_pairs_not_in_overlap);
983
984        res
985    }
986}
987
988#[cfg(test)]
989#[allow(dead_code, unused_variables)]
990mod tests {
991    use super::*;
992    use itertools::Itertools;
993    use linnet::half_edge::{
994        involution::EdgeIndex,
995        subgraph::{SuBitGraph, SubSetLike},
996    };
997    use typed_index_collections::ti_vec;
998
999    use crate::{
1000        cff::{
1001            cff_graph::VertexSet,
1002            esurface::{
1003                Esurface, EsurfaceExistence, EsurfaceID, RaisedEsurfaceData, RaisedEsurfaceGroup,
1004                RaisedEsurfaceId,
1005            },
1006        },
1007        graph::LoopMomentumBasis,
1008        momentum::FourMomentum,
1009        momentum::signature::LoopExtSignature,
1010        settings::RuntimeSettings,
1011        utils::test_utils::dummy_hedge_graph,
1012    };
1013
1014    #[test]
1015    fn overlap_structure_localizes_to_graph_existing_surfaces() {
1016        let center = LoopMomenta::from_iter([ThreeMomentum::new(F(0.0), F(0.0), F(0.0))]);
1017        let overlap = OverlapStructure {
1018            existing_esurfaces: ti_vec![
1019                GroupEsurfaceId::from(0),
1020                GroupEsurfaceId::from(1),
1021                GroupEsurfaceId::from(2),
1022            ],
1023            overlap_groups: vec![
1024                OverlapGroup {
1025                    existing_esurfaces: vec![
1026                        ExistingEsurfaceId::from(0),
1027                        ExistingEsurfaceId::from(1),
1028                    ],
1029                    complement: vec![],
1030                    center: center.clone(),
1031                    prefactor_evaluator: None,
1032                },
1033                OverlapGroup {
1034                    existing_esurfaces: vec![
1035                        ExistingEsurfaceId::from(1),
1036                        ExistingEsurfaceId::from(2),
1037                    ],
1038                    complement: vec![],
1039                    center: center.clone(),
1040                    prefactor_evaluator: None,
1041                },
1042            ],
1043        };
1044
1045        let localized = overlap.localized_to_existing_surfaces(&ti_vec![true, false, true]);
1046
1047        assert_eq!(
1048            localized
1049                .existing_esurfaces
1050                .iter()
1051                .map(|group_esurface_id| group_esurface_id.0)
1052                .collect_vec(),
1053            vec![0, 2]
1054        );
1055        assert_eq!(localized.overlap_groups.len(), 2);
1056        assert_eq!(
1057            localized.overlap_groups[0].existing_esurfaces,
1058            vec![ExistingEsurfaceId::from(0)]
1059        );
1060        assert_eq!(
1061            localized.overlap_groups[0].complement,
1062            vec![ExistingEsurfaceId::from(1)]
1063        );
1064        assert_eq!(
1065            localized.overlap_groups[1].existing_esurfaces,
1066            vec![ExistingEsurfaceId::from(1)]
1067        );
1068        assert_eq!(
1069            localized.overlap_groups[1].complement,
1070            vec![ExistingEsurfaceId::from(0)]
1071        );
1072    }
1073
1074    struct HelperBoxStructure {
1075        external_momenta: ExternalFourMomenta<F<f64>>,
1076        lmb: LoopMomentumBasis,
1077        esurfaces: EsurfaceCollection,
1078        raised_data: RaisedEsurfaceData,
1079        existing_esurfaces: ExistingEsurfaces,
1080        edge_masses: EdgeVec<F<f64>>,
1081    }
1082
1083    struct HelperBananaStructure {
1084        external_momenta: ExternalFourMomenta<F<f64>>,
1085        lmb: LoopMomentumBasis,
1086        esurfaces: EsurfaceCollection,
1087        raised_data: RaisedEsurfaceData,
1088        existing_esurfaces: ExistingEsurfaces,
1089        edge_masses: EdgeVec<F<f64>>,
1090    }
1091
1092    fn trivial_raised_data(num_esurfaces: usize) -> RaisedEsurfaceData {
1093        RaisedEsurfaceData {
1094            raised_groups: (0..num_esurfaces)
1095                .map(|index| RaisedEsurfaceGroup {
1096                    esurface_ids: vec![EsurfaceID::from(index)],
1097                    max_occurence: 1,
1098                })
1099                .collect(),
1100            pass_two_evaluator: None,
1101        }
1102    }
1103
1104    impl HelperBoxStructure {
1105        fn new(masses: Option<[F<f64>; 4]>) -> Self {
1106            let external_momenta = ExternalFourMomenta::from_iter([
1107                FourMomentum::from_args(F(14.0), F(-6.6), F(-40.0), F(0.0)),
1108                FourMomentum::from_args(F(-43.0), F(15.2), F(33.0), F(0.0)),
1109                FourMomentum::from_args(F(-17.9), F(-50.0), F(11.8), F(0.0)),
1110            ]);
1111
1112            let dummy_hedge_graph = dummy_hedge_graph(8);
1113
1114            let box_basis = ti_vec![EdgeIndex::from(4)];
1115            let box_signatures = dummy_hedge_graph
1116                .new_edgevec_from_iter(vec![
1117                    (vec![0], vec![1, 0, 0]).into(),
1118                    (vec![0], vec![0, 1, 0]).into(),
1119                    (vec![0], vec![0, 0, 1]).into(),
1120                    (vec![0], vec![-1, -1, -1]).into(),
1121                    (vec![1], vec![0, 0, 0]).into(),
1122                    (vec![1], vec![1, 0, 0]).into(),
1123                    (vec![1], vec![1, 1, 0]).into(),
1124                    (vec![1], vec![1, 1, 1]).into(),
1125                ])
1126                .unwrap();
1127
1128            let box_lmb = LoopMomentumBasis {
1129                tree: SuBitGraph::empty(0),
1130                ext_edges: vec![].into(),
1131                loop_edges: box_basis,
1132                edge_signatures: box_signatures,
1133            };
1134
1135            let esurfaces_array = [
1136                Esurface {
1137                    energies: vec![EdgeIndex::from(5), EdgeIndex::from(6)],
1138                    external_shift: vec![(EdgeIndex::from(1), 1)],
1139                    vertex_set: VertexSet::dummy(),
1140                    // subspace_graph: dummy_hedge_graph.full_graph(),
1141                },
1142                Esurface {
1143                    energies: vec![EdgeIndex::from(5), EdgeIndex::from(7)],
1144                    external_shift: vec![(EdgeIndex::from(1), 1), (EdgeIndex::from(2), 1)],
1145                    vertex_set: VertexSet::dummy(),
1146                    //subspace_graph: dummy_hedge_graph.full_graph(),
1147                },
1148                Esurface {
1149                    energies: vec![EdgeIndex::from(4), EdgeIndex::from(6)],
1150                    external_shift: vec![(EdgeIndex::from(0), 1), (EdgeIndex::from(1), 1)],
1151                    vertex_set: VertexSet::dummy(),
1152                    //subspace_graph: dummy_hedge_graph.full_graph(),
1153                },
1154                Esurface {
1155                    energies: vec![EdgeIndex::from(4), EdgeIndex::from(7)],
1156                    external_shift: vec![
1157                        (EdgeIndex::from(0), 1),
1158                        (EdgeIndex::from(1), 1),
1159                        (EdgeIndex::from(2), 1),
1160                    ],
1161                    vertex_set: VertexSet::dummy(),
1162                    //subspace_graph: dummy_hedge_graph.full_graph(),
1163                },
1164            ];
1165
1166            let esurfaces = esurfaces_array.to_vec().into();
1167
1168            let edge_masses = match masses {
1169                Some(masses) => {
1170                    let mut edge_masses = vec![F(0.0); 4];
1171                    let mut real_masses = masses.iter().copied().collect_vec();
1172
1173                    edge_masses.append(&mut real_masses);
1174                    edge_masses
1175                }
1176                None => vec![F(0.0); 8],
1177            };
1178
1179            let existing_esurfaces = (0..4).map(Into::<GroupEsurfaceId>::into).collect();
1180
1181            Self {
1182                external_momenta,
1183                lmb: box_lmb,
1184                existing_esurfaces,
1185                esurfaces,
1186                raised_data: trivial_raised_data(4),
1187                edge_masses: dummy_hedge_graph
1188                    .new_edgevec_from_iter(edge_masses)
1189                    .unwrap(),
1190            }
1191        }
1192    }
1193
1194    impl HelperBananaStructure {
1195        fn new() -> Self {
1196            let external_momenta = ExternalFourMomenta::from_iter([FourMomentum::from_args(
1197                F(10.0),
1198                F(-10.00000000),
1199                F(0.0),
1200                F(0.0),
1201            )]);
1202            let banana_basis = ti_vec![EdgeIndex::from(2), EdgeIndex::from(3)];
1203
1204            let dummy_hedge_graph = dummy_hedge_graph(5);
1205
1206            let banana_edge_sigs = dummy_hedge_graph
1207                .new_edgevec_from_iter(vec![
1208                    LoopExtSignature {
1209                        internal: vec![0, 0].into(),
1210                        external: vec![1].into(),
1211                    },
1212                    LoopExtSignature {
1213                        internal: vec![0, 0].into(),
1214                        external: vec![-1].into(),
1215                    },
1216                    LoopExtSignature {
1217                        internal: vec![1, 0].into(),
1218                        external: vec![0].into(),
1219                    },
1220                    LoopExtSignature {
1221                        internal: vec![0, 1].into(),
1222                        external: vec![0].into(),
1223                    },
1224                    LoopExtSignature {
1225                        internal: vec![1, 1].into(),
1226                        external: vec![-1].into(),
1227                    },
1228                ])
1229                .unwrap();
1230
1231            let banana_lmb = LoopMomentumBasis {
1232                tree: SuBitGraph::empty(0),
1233                loop_edges: banana_basis,
1234                ext_edges: vec![].into(),
1235                edge_signatures: banana_edge_sigs,
1236            };
1237
1238            let only_esurface = Esurface {
1239                energies: vec![EdgeIndex::from(2), EdgeIndex::from(3), EdgeIndex::from(4)],
1240                external_shift: vec![(EdgeIndex::from(0), -1)],
1241                vertex_set: VertexSet::dummy(),
1242                //subspace_graph: dummy_hedge_graph.full_graph(),
1243            };
1244
1245            let esurfaces = vec![only_esurface].into();
1246
1247            let existing_esurfaces = vec![Into::<GroupEsurfaceId>::into(0)].into();
1248            let edge_masses = dummy_hedge_graph
1249                .new_edgevec_from_iter(vec![F(0.0); 5])
1250                .unwrap();
1251
1252            Self {
1253                external_momenta,
1254                lmb: banana_lmb,
1255                esurfaces,
1256                raised_data: trivial_raised_data(1),
1257                existing_esurfaces,
1258                edge_masses,
1259            }
1260        }
1261    }
1262
1263    #[test]
1264    fn test_is_subset_of_result() {
1265        let fake_res = vec![
1266            (
1267                vec![
1268                    Into::<ExistingEsurfaceId>::into(1),
1269                    Into::<ExistingEsurfaceId>::into(2),
1270                    Into::<ExistingEsurfaceId>::into(3),
1271                ],
1272                LoopMomenta::from(vec![]),
1273            ),
1274            (
1275                vec![
1276                    Into::<ExistingEsurfaceId>::into(1),
1277                    Into::<ExistingEsurfaceId>::into(2),
1278                    Into::<ExistingEsurfaceId>::into(4),
1279                ],
1280                LoopMomenta::from(vec![]),
1281            ),
1282            (
1283                vec![
1284                    Into::<ExistingEsurfaceId>::into(2),
1285                    Into::<ExistingEsurfaceId>::into(3),
1286                    Into::<ExistingEsurfaceId>::into(4),
1287                ],
1288                LoopMomenta::from(vec![]),
1289            ),
1290        ];
1291
1292        let fake_res = OverlapStructure {
1293            overlap_groups: fake_res
1294                .into_iter()
1295                .map(|(group, center)| OverlapGroup {
1296                    existing_esurfaces: group,
1297                    center,
1298                    complement: vec![],
1299                    prefactor_evaluator: None,
1300                })
1301                .collect_vec(),
1302            existing_esurfaces: ti_vec![],
1303        };
1304
1305        let fake_subset = vec![
1306            Into::<ExistingEsurfaceId>::into(1),
1307            Into::<ExistingEsurfaceId>::into(2),
1308        ];
1309
1310        assert!(is_subset_of_result(&fake_subset, &fake_res));
1311
1312        let fake_subset_2 = vec![
1313            Into::<ExistingEsurfaceId>::into(0),
1314            Into::<ExistingEsurfaceId>::into(4),
1315        ];
1316
1317        assert!(!is_subset_of_result(&fake_subset_2, &fake_res));
1318    }
1319
1320    #[test]
1321    fn test_pair_creator() {
1322        let box4e = HelperBoxStructure::new(None);
1323
1324        let massless_overlap_input = OverlapInput {
1325            graph_data: ti_vec![SingleGraphOverlapData {
1326                lmb: &box4e.lmb,
1327                esurfaces: &box4e.esurfaces,
1328                raised_data: &box4e.raised_data,
1329                edge_masses: box4e.edge_masses.clone(),
1330            }],
1331            settings: &RuntimeSettings::default(),
1332            group_esurface_map: (0..4)
1333                .map(|i| ti_vec![Some(Into::<RaisedEsurfaceId>::into(i))])
1334                .collect(),
1335            local_esurface_exists: ti_vec![ti_vec![true; 4]],
1336        };
1337
1338        let esurface_pairs = EsurfacePairs::new(
1339            &massless_overlap_input,
1340            &box4e.existing_esurfaces,
1341            &box4e.external_momenta,
1342        );
1343
1344        assert_eq!(esurface_pairs.data.len(), 4);
1345
1346        let box4e_massive = HelperBoxStructure::new(Some([F(10.5); 4]));
1347
1348        let massive_overlap_input = OverlapInput {
1349            graph_data: ti_vec![SingleGraphOverlapData {
1350                lmb: &box4e_massive.lmb,
1351                esurfaces: &box4e_massive.esurfaces,
1352                raised_data: &box4e_massive.raised_data,
1353                edge_masses: box4e_massive.edge_masses.clone(),
1354            }],
1355            settings: &RuntimeSettings::default(),
1356            group_esurface_map: (0..4)
1357                .map(|i| ti_vec![Some(Into::<RaisedEsurfaceId>::into(i))])
1358                .collect(),
1359            local_esurface_exists: ti_vec![ti_vec![true; 4]],
1360        };
1361
1362        let esurface_pairs_massive = EsurfacePairs::new(
1363            &massive_overlap_input,
1364            &box4e_massive.existing_esurfaces,
1365            &box4e_massive.external_momenta,
1366        );
1367
1368        assert_eq!(esurface_pairs_massive.data.len(), 0);
1369    }
1370
1371    #[test]
1372    fn test_subset_generator() {
1373        let box4e = HelperBoxStructure::new(None);
1374
1375        let overlap_input = OverlapInput {
1376            graph_data: ti_vec![SingleGraphOverlapData {
1377                lmb: &box4e.lmb,
1378                esurfaces: &box4e.esurfaces,
1379                raised_data: &box4e.raised_data,
1380                edge_masses: box4e.edge_masses.clone(),
1381            }],
1382            settings: &RuntimeSettings::default(),
1383            group_esurface_map: (0..4)
1384                .map(|i| ti_vec![Some(Into::<RaisedEsurfaceId>::into(i))])
1385                .collect(),
1386            local_esurface_exists: ti_vec![ti_vec![true; 4]],
1387        };
1388
1389        let esurface_pairs = EsurfacePairs::new(
1390            &overlap_input,
1391            &box4e.existing_esurfaces,
1392            &box4e.external_momenta,
1393        );
1394
1395        let res = OverlapStructure {
1396            overlap_groups: vec![],
1397            existing_esurfaces: box4e.existing_esurfaces.clone(),
1398        };
1399        let subsets_3 =
1400            esurface_pairs.construct_possible_subsets_of_len(&box4e.existing_esurfaces, 3, &res);
1401
1402        assert_eq!(subsets_3.len(), 0);
1403
1404        let subsets_2 =
1405            esurface_pairs.construct_possible_subsets_of_len(&box4e.existing_esurfaces, 2, &res);
1406        assert_eq!(subsets_2.len(), 4);
1407    }
1408
1409    #[test]
1410    fn test_box_4e() {
1411        // massless variant
1412        let box4e = HelperBoxStructure::new(None);
1413
1414        let massless_overlap_input = OverlapInput {
1415            graph_data: ti_vec![SingleGraphOverlapData {
1416                lmb: &box4e.lmb,
1417                esurfaces: &box4e.esurfaces,
1418                raised_data: &box4e.raised_data,
1419                edge_masses: box4e.edge_masses.clone(),
1420            }],
1421            settings: &RuntimeSettings::default(),
1422            group_esurface_map: (0..4)
1423                .map(|i| ti_vec![Some(Into::<RaisedEsurfaceId>::into(i))])
1424                .collect(),
1425            local_esurface_exists: ti_vec![ti_vec![true; 4]],
1426        };
1427
1428        let maximal_overlap = find_maximal_overlap(
1429            &massless_overlap_input,
1430            &box4e.existing_esurfaces,
1431            &box4e.external_momenta,
1432        )
1433        .unwrap();
1434
1435        assert_eq!(maximal_overlap.overlap_groups.len(), 4);
1436
1437        for overlap_group in maximal_overlap.overlap_groups.iter() {
1438            let esurfaces = &overlap_group.existing_esurfaces;
1439            let center = &overlap_group.center;
1440
1441            assert_eq!(esurfaces.len(), 2);
1442            assert_eq!(overlap_group.complement.len(), 2);
1443
1444            for esurface in esurfaces.iter() {
1445                let raised_esurface_id = massless_overlap_input.group_esurface_map
1446                    [box4e.existing_esurfaces[*esurface]][GraphGroupPosition::from(0)]
1447                .unwrap();
1448                let esurface_id =
1449                    box4e.raised_data.raised_groups[raised_esurface_id].esurface_ids[0];
1450                let esurfaec_val = box4e.esurfaces[esurface_id].compute_from_momenta(
1451                    &box4e.lmb,
1452                    &box4e.edge_masses,
1453                    center,
1454                    &box4e.external_momenta,
1455                );
1456
1457                assert!(esurfaec_val.0 < 0.0);
1458            }
1459        }
1460    }
1461
1462    /// This test deforms the threshold structure into 4 pieces with no overlap
1463    #[test]
1464    fn test_disconnected_box_4e() {
1465        let box4e = HelperBoxStructure::new(Some([F(10.5); 4]));
1466
1467        let overlap_input = OverlapInput {
1468            graph_data: ti_vec![SingleGraphOverlapData {
1469                lmb: &box4e.lmb,
1470                esurfaces: &box4e.esurfaces,
1471                raised_data: &box4e.raised_data,
1472                edge_masses: box4e.edge_masses.clone(),
1473            }],
1474            settings: &RuntimeSettings::default(),
1475            group_esurface_map: (0..4)
1476                .map(|i| ti_vec![Some(Into::<RaisedEsurfaceId>::into(i))])
1477                .collect(),
1478            local_esurface_exists: ti_vec![ti_vec![true; 4]],
1479        };
1480
1481        let maximal_overlap = find_maximal_overlap(
1482            &overlap_input,
1483            &box4e.existing_esurfaces,
1484            &box4e.external_momenta,
1485        )
1486        .unwrap();
1487
1488        assert_eq!(maximal_overlap.overlap_groups.len(), 4);
1489
1490        for overlap_group in maximal_overlap.overlap_groups.iter() {
1491            let esurfaces = &overlap_group.existing_esurfaces;
1492            let center = &overlap_group.center;
1493
1494            assert_eq!(esurfaces.len(), 1);
1495
1496            for esurface in esurfaces.iter() {
1497                let raised_esurface_id = overlap_input.group_esurface_map
1498                    [box4e.existing_esurfaces[*esurface]][GraphGroupPosition::from(0)]
1499                .unwrap();
1500                let esurface_id =
1501                    box4e.raised_data.raised_groups[raised_esurface_id].esurface_ids[0];
1502                let esurfaec_val = box4e.esurfaces[esurface_id].compute_from_momenta(
1503                    &box4e.lmb,
1504                    &box4e.edge_masses,
1505                    center,
1506                    &box4e.external_momenta,
1507                );
1508
1509                assert!(esurfaec_val < F(0.0));
1510            }
1511
1512            assert_eq!(overlap_group.complement.len(), 3);
1513        }
1514    }
1515
1516    #[test]
1517    fn test_banana() {
1518        let banana = HelperBananaStructure::new();
1519
1520        let classification = banana.esurfaces[EsurfaceID::from(0)].classify_existence(
1521            &banana.external_momenta,
1522            &banana.lmb,
1523            &banana.edge_masses,
1524            &F(10.0),
1525            &F(crate::utils::DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
1526        );
1527        assert!(matches!(classification, EsurfaceExistence::Pinched { .. }));
1528
1529        let overlap_input = OverlapInput {
1530            graph_data: ti_vec![SingleGraphOverlapData {
1531                lmb: &banana.lmb,
1532                esurfaces: &banana.esurfaces,
1533                raised_data: &banana.raised_data,
1534                edge_masses: banana.edge_masses.clone(),
1535            }],
1536            settings: &RuntimeSettings::default(),
1537            group_esurface_map: ti_vec![ti_vec![Some(Into::<RaisedEsurfaceId>::into(0)),]],
1538            local_esurface_exists: ti_vec![ti_vec![true]],
1539        };
1540
1541        let result = find_maximal_overlap(
1542            &overlap_input,
1543            &banana.existing_esurfaces,
1544            &banana.external_momenta,
1545        );
1546
1547        assert!(
1548            result.is_err(),
1549            "a caller that manually marks a pinched surface as existing must be rejected"
1550        );
1551
1552        let mut forced_settings = RuntimeSettings::default();
1553        forced_settings
1554            .subtraction
1555            .overlap_settings
1556            .force_global_center = Some(vec![[0.0, 0.0, 0.0]; 2]);
1557        forced_settings
1558            .subtraction
1559            .overlap_settings
1560            .check_global_center = false;
1561        let forced_overlap_input = OverlapInput {
1562            graph_data: ti_vec![SingleGraphOverlapData {
1563                lmb: &banana.lmb,
1564                esurfaces: &banana.esurfaces,
1565                raised_data: &banana.raised_data,
1566                edge_masses: banana.edge_masses.clone(),
1567            }],
1568            settings: &forced_settings,
1569            group_esurface_map: ti_vec![ti_vec![Some(Into::<RaisedEsurfaceId>::into(0)),]],
1570            local_esurface_exists: ti_vec![ti_vec![true]],
1571        };
1572        assert!(
1573            find_maximal_overlap(
1574                &forced_overlap_input,
1575                &banana.existing_esurfaces,
1576                &banana.external_momenta,
1577            )
1578            .is_err(),
1579            "check_global_center=false must not allow a forced center to bypass the invariant"
1580        );
1581    }
1582}