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