1use crate::GammaLoopContext;
2use crate::cff::esurface::EsurfaceCollection;
3use crate::cff::esurface::EsurfaceID;
4use crate::cff::esurface::ExistingEsurfaceId;
5use crate::cff::esurface::ExistingThresholds;
6use crate::cff::esurface::esurface_value_is_strictly_inside;
7use crate::graph::FeynmanGraph;
8use crate::graph::Graph;
9use crate::graph::LmbIndex;
10use crate::graph::LoopMomentumBasis;
11use crate::integrands::process::sampling::maps::SamplingEvaluationError;
12use crate::momentum::sample::ExternalFourMomenta;
13use crate::momentum::sample::LoopIndex;
14use crate::momentum::sample::LoopMomenta;
15use crate::momentum::sample::SubspaceData;
16use crate::momentum::signature::LoopExtSignature;
17use crate::momentum::{Rotation, ThreeMomentum};
18use crate::settings::RuntimeSettings;
19use crate::utils::F;
20use crate::utils::Length;
21use crate::utils::compute_shift_part_subspace;
22use ahash::HashMap;
23use ahash::HashMapExt;
24use ahash::HashSet;
25use bincode_trait_derive::Decode;
26use bincode_trait_derive::Encode;
27use clarabel::algebra::*;
28use clarabel::solver::*;
29use eyre::{Result, eyre};
30use itertools::Itertools;
31use linnet::half_edge::involution::EdgeVec;
32use spenso::algebra::algebraic_traits::IsZero;
33#[cfg(test)]
34use std::collections::BTreeMap;
35use std::fmt::Display;
36use typed_index_collections::TiVec;
37
38#[cfg(test)]
39#[path = "overlap_subspace_tests.rs"]
40mod regression_tests;
41
42#[derive(Debug, Clone, Encode, Decode)]
43#[trait_decode(trait = GammaLoopContext)]
44pub struct OverlapGroup {
45 pub existing_esurfaces: Vec<ExistingEsurfaceId>,
46 pub complement: Vec<ExistingEsurfaceId>,
47 pub center: LoopMomenta<F<f64>>,
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: ExistingThresholds,
59}
60
61#[derive(Debug, Clone)]
64pub(crate) struct OverlapKinematics {
65 pub loop_moms: LoopMomenta<F<f64>>,
66 pub external_momenta: ExternalFourMomenta<F<f64>>,
67 pub edge_masses: Option<EdgeVec<F<f64>>>,
69}
70
71impl Display for OverlapStructure {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 let existing_esurfaces: Vec<_> = self.existing_esurfaces.iter().map(|id| id.0).collect();
74 writeln!(f, "existing esurfaces: {:?}", existing_esurfaces)?;
75
76 for (i, group) in self.overlap_groups.iter().enumerate() {
77 writeln!(f, "Group {}:", i)?;
78 let existing_esurfaces_in_group: Vec<_> = group
79 .existing_esurfaces
80 .iter()
81 .map(|id| self.existing_esurfaces[*id].0)
82 .collect();
83
84 writeln!(f, "center:\n{}", group.center)?;
85 writeln!(
86 f,
87 "existing esurfaces in group: {:?}",
88 existing_esurfaces_in_group
89 )?;
90 }
91
92 Ok(())
93 }
94}
95
96impl OverlapStructure {
97 pub fn fill_in_complements(&mut self) {
98 for group in self.overlap_groups.iter_mut() {
99 group.complement = self
100 .existing_esurfaces
101 .iter_enumerated()
102 .map(|(existing_esurface_id, _)| existing_esurface_id)
103 .filter(|&existing_esurface_id| {
104 !group.existing_esurfaces.contains(&existing_esurface_id)
105 })
106 .collect();
107 }
108 }
109
110 pub fn new_empty() -> Self {
111 Self {
112 overlap_groups: vec![],
113 existing_esurfaces: ExistingThresholds::new(),
114 }
115 }
116}
117struct PropagatorConstraint<'a> {
119 mass_pointer: Option<usize>, signature: &'a LoopExtSignature,
121 subspace: &'a SubspaceData,
122 loop_moms: LoopMomenta<F<f64>>,
123 external_momenta: ExternalFourMomenta<F<f64>>,
124}
125
126impl PropagatorConstraint<'_> {
127 fn get_dimension(&self) -> usize {
128 let mass_value = if self.mass_pointer.is_some() { 1 } else { 0 };
129
130 3 + mass_value + 1
131 }
132}
133
134fn extract_center(
135 global_loop_num: usize,
136 subspace: &SubspaceData,
137 solution: &[f64],
138) -> LoopMomenta<F<f64>> {
139 let len = solution.len();
140 let num_loop_vars = 3 * subspace.loopcount();
141
142 let mut loop_chunks = solution[len - num_loop_vars..].chunks(3);
143
144 (0..global_loop_num)
145 .map(LoopIndex::from)
146 .map(|loop_index| {
147 if subspace.contains_loop_index(loop_index) {
148 let window = loop_chunks.next().expect("not enough loop momenta");
149 ThreeMomentum::new(
150 F::from_f64(window[0]),
151 F::from_f64(window[1]),
152 F::from_f64(window[2]),
153 )
154 } else {
155 ThreeMomentum::new(F(0.0), F(0.0), F(0.0))
156 }
157 })
158 .collect()
159}
160
161fn construct_solver(
162 overlap_input: &OverlapInput,
163 esurfaces_to_consider: &[ExistingEsurfaceId],
164 existing_esurfaces: &ExistingThresholds,
165 loop_moms: &LoopMomenta<F<f64>>,
166 external_momenta: &ExternalFourMomenta<F<f64>>,
167 verbose: bool,
168 variable_scale: f64,
169) -> Option<DefaultSolver> {
170 let num_loops = overlap_input.subspace.loopcount();
171
172 let num_loop_vars = 3 * num_loops;
173
174 let mut propagator_constraints: Vec<PropagatorConstraint> = Vec::with_capacity(20);
176
177 let mut inequivalent_masses: Vec<F<f64>> = vec![];
178
179 let mut esurface_constraints: Vec<Vec<usize>> = Vec::with_capacity(esurfaces_to_consider.len());
180
181 for existing_esurface_id in esurfaces_to_consider.iter() {
182 let surface_id = existing_esurfaces[*existing_esurface_id];
183
184 let esurface = &overlap_input.thresholds[surface_id];
185 let threshold_subspace = overlap_input.threshold_subspace(surface_id);
186 let (surface_loop_moms, surface_external_momenta, edge_masses) = overlap_input
187 .surface_kinematics
188 .and_then(|kinematics| kinematics.get(surface_id.0))
189 .map(|kinematics| {
190 (
191 &kinematics.loop_moms,
192 &kinematics.external_momenta,
193 kinematics
194 .edge_masses
195 .as_ref()
196 .unwrap_or(&overlap_input.edge_masses),
197 )
198 })
199 .unwrap_or((loop_moms, external_momenta, &overlap_input.edge_masses));
200 let lmb = threshold_subspace.get_lmb(overlap_input.lmbs);
201
202 let mut esurface_constraint_indices: Vec<usize> = Vec::with_capacity(6);
203
204 for edge_id in threshold_subspace.contains(&esurface.energies, overlap_input.graph) {
205 if let Some(edge_position) = propagator_constraints.iter().position(|constraint| {
206 overlap_input.surface_kinematics.is_none()
207 && *constraint.signature == lmb.edge_signatures[edge_id]
208 && constraint.subspace.solve_signature(overlap_input.lmbs)
209 == threshold_subspace.solve_signature(overlap_input.lmbs)
210 }) {
211 esurface_constraint_indices.push(edge_position);
212 } else {
213 let mass_pointer = if edge_masses[edge_id].is_zero() {
214 None
215 } else {
216 Some(
217 if let Some(mass_position) = inequivalent_masses
218 .iter()
219 .position(|&x| x == edge_masses[edge_id])
220 {
221 mass_position
222 } else {
223 inequivalent_masses.push(edge_masses[edge_id]);
224 inequivalent_masses.len() - 1
225 },
226 )
227 };
228
229 let signature = &lmb.edge_signatures[edge_id];
230
231 let propagator_constraint = PropagatorConstraint {
232 mass_pointer,
233 signature,
234 subspace: threshold_subspace,
235 loop_moms: surface_loop_moms.clone(),
236 external_momenta: surface_external_momenta.clone(),
237 };
238
239 propagator_constraints.push(propagator_constraint);
240 esurface_constraint_indices.push(propagator_constraints.len() - 1);
241 };
242 }
243
244 esurface_constraints.push(esurface_constraint_indices);
245 }
246
247 let propagator_index_offset = 1;
250 let loop_momentum_offset = propagator_index_offset + propagator_constraints.len();
251
252 let num_primal_variables = loop_momentum_offset + num_loop_vars;
253
254 let cone_dimension_sum = propagator_constraints
255 .iter()
256 .map(|constraint| constraint.get_dimension())
257 .sum::<usize>();
258
259 let num_constaints = 1 + esurface_constraints.len() + cone_dimension_sum;
260
261 let p_matrix: CscMatrix<f64> =
263 CscMatrix::spalloc((num_primal_variables, num_primal_variables), 0);
264
265 let mut q_vector = vec![0.0; num_primal_variables];
267 q_vector[0] = 1.0;
268
269 let mut cones: Vec<SupportedConeT<f64>> = Vec::with_capacity(1 + propagator_constraints.len());
271 cones.push(NonnegativeConeT(1));
272 cones.push(NonnegativeConeT(esurface_constraints.len()));
273
274 for prop_constraint in &propagator_constraints {
275 cones.push(SecondOrderConeT(prop_constraint.get_dimension()));
276 }
277
278 let mut a_matrix = vec![vec![0.0; num_primal_variables]; num_constaints];
282 let mut b_vector = vec![0.0; num_constaints];
283
284 a_matrix[0][0] = 1.0;
285 for (constraint_index, (existing_esurface_id, esurface_constraint)) in esurfaces_to_consider
287 .iter()
288 .zip(esurface_constraints.iter())
289 .enumerate()
290 {
291 for prop_index in esurface_constraint {
292 a_matrix[constraint_index + 1][*prop_index + propagator_index_offset] = 1.0;
293 }
294
295 let esurface_id = existing_esurfaces[*existing_esurface_id];
296 let esurface = &overlap_input.thresholds[esurface_id];
297 let threshold_subspace = overlap_input.threshold_subspace(esurface_id);
298 let (surface_loop_moms, surface_external_momenta, edge_masses) = overlap_input
299 .surface_kinematics
300 .and_then(|kinematics| kinematics.get(esurface_id.0))
301 .map(|kinematics| {
302 (
303 &kinematics.loop_moms,
304 &kinematics.external_momenta,
305 kinematics
306 .edge_masses
307 .as_ref()
308 .unwrap_or(&overlap_input.edge_masses),
309 )
310 })
311 .unwrap_or((loop_moms, external_momenta, &overlap_input.edge_masses));
312
313 let shift_part = esurface.compute_shift_part_from_momenta_in_subspace(
314 surface_loop_moms,
315 surface_external_momenta,
316 threshold_subspace,
317 overlap_input.lmbs,
318 overlap_input.graph,
319 edge_masses,
320 );
321 b_vector[constraint_index + 1] = -shift_part.0;
322 a_matrix[constraint_index + 1][0] = -1.0;
323 }
324
325 let mut vertical_offset = esurface_constraints.len() + 1;
327 for (cone_index, propagator_constraint) in propagator_constraints.iter().enumerate() {
328 a_matrix[vertical_offset][propagator_index_offset + cone_index] = -1.0;
329 vertical_offset += 1;
330
331 let spatial_shift = compute_shift_part_subspace(
332 &propagator_constraint.signature.internal,
333 &propagator_constraint.signature.external,
334 &propagator_constraint.loop_moms,
335 &propagator_constraint
336 .external_momenta
337 .iter()
338 .map(|p| p.spatial)
339 .collect(),
340 propagator_constraint.subspace,
341 );
342
343 b_vector[vertical_offset] = spatial_shift.px.0;
344 b_vector[vertical_offset + 1] = spatial_shift.py.0;
345 b_vector[vertical_offset + 2] = spatial_shift.pz.0;
346
347 for (subspace_loop_index, loop_index) in
348 overlap_input.subspace.iter_lmb_indices().enumerate()
349 {
350 if !propagator_constraint
351 .subspace
352 .contains_loop_index(loop_index)
353 {
354 continue;
355 }
356 let individual_loop_signature = propagator_constraint.signature.internal[loop_index];
357 if individual_loop_signature.is_sign() {
358 a_matrix[vertical_offset][loop_momentum_offset + 3 * subspace_loop_index] =
359 -(individual_loop_signature as i8) as f64;
360 a_matrix[vertical_offset + 1][loop_momentum_offset + 3 * subspace_loop_index + 1] =
361 -(individual_loop_signature as i8) as f64;
362 a_matrix[vertical_offset + 2][loop_momentum_offset + 3 * subspace_loop_index + 2] =
363 -(individual_loop_signature as i8) as f64;
364 }
365 }
366
367 vertical_offset += 3;
368
369 if let Some(mass_index) = propagator_constraint.mass_pointer {
370 b_vector[vertical_offset] = inequivalent_masses[mass_index].0;
371 vertical_offset += 1;
372 }
373 }
374
375 if variable_scale != 1.0
377 && b_vector
378 .iter()
379 .any(|value| (value / variable_scale) * variable_scale != *value)
380 {
381 return None;
382 }
383 for value in &mut b_vector {
387 *value /= variable_scale;
388 }
389
390 let a_matrix_sparse = CscMatrix::from(&a_matrix);
391
392 let settings = DefaultSettingsBuilder::default()
393 .verbose(verbose)
394 .build()
395 .unwrap();
396
397 crate::debug_tags!(#subtraction, #threshold, #overlap, #socp;
398 stage = "threshold_socp_input",
399 variable_scale,
400 graph = %overlap_input.graph.name,
401 surfaces = ?esurfaces_to_consider,
402 file.a = ?a_matrix,
403 file.b = ?b_vector,
404 file.q = ?q_vector,
405 file.cones = ?cones,
406 "constructed threshold overlap problem"
407 );
408
409 Some(
410 DefaultSolver::new(
411 &p_matrix,
412 &q_vector,
413 &a_matrix_sparse,
414 &b_vector,
415 &cones,
416 settings,
417 )
418 .unwrap(),
419 )
420}
421
422pub(crate) fn find_center(
423 overlap_input: &OverlapInput,
424 esurfaces_to_consider: &[ExistingEsurfaceId],
425 existing_esurfaces: &ExistingThresholds,
426 loop_moms: &LoopMomenta<F<f64>>,
427 external_momenta: &ExternalFourMomenta<F<f64>>,
428 verbose: bool,
429) -> Result<Option<LoopMomenta<F<f64>>>> {
430 let global_loop_number = overlap_input.graph.get_loop_number();
431 let esurfaces_to_check = esurfaces_to_consider
432 .iter()
433 .map(|existing_esurface_id| existing_esurfaces[*existing_esurface_id])
434 .collect();
435 OverlapInput::solve_with_rescaling(
436 &overlap_input.graph.name,
437 &esurfaces_to_check,
438 |variable_scale| {
439 construct_solver(
440 overlap_input,
441 esurfaces_to_consider,
442 existing_esurfaces,
443 loop_moms,
444 external_momenta,
445 verbose,
446 variable_scale,
447 )
448 },
449 |coordinates| {
450 let center = extract_center(global_loop_number, overlap_input.subspace, coordinates);
451 check_global_center(
452 overlap_input,
453 &esurfaces_to_check,
454 ¢er,
455 loop_moms,
456 external_momenta,
457 )
458 },
459 )
460 .map(|coordinates| {
461 coordinates.map(|coordinates| {
462 extract_center(global_loop_number, overlap_input.subspace, &coordinates)
463 })
464 })
465}
466
467pub(crate) struct OverlapInput<'a> {
468 pub graph: &'a Graph,
469 pub settings: &'a RuntimeSettings,
470 pub subspace: &'a SubspaceData,
472 pub threshold_subspaces: Option<&'a [SubspaceData]>,
474 pub lmbs: &'a TiVec<LmbIndex, LoopMomentumBasis>,
475 pub thresholds: &'a EsurfaceCollection,
476 pub edge_masses: EdgeVec<F<f64>>,
477 pub surface_kinematics: Option<&'a [OverlapKinematics]>,
480}
481
482impl OverlapInput<'_> {
483 fn solve_with_rescaling(
484 graph_name: &str,
485 esurfaces: &ExistingThresholds,
486 mut construct: impl FnMut(f64) -> Option<DefaultSolver>,
487 mut center_is_interior: impl FnMut(&[f64]) -> bool,
488 ) -> Result<Option<Vec<f64>>> {
489 let mut attempts = Vec::with_capacity(2);
490 for variable_scale in [1.0, 2.0] {
494 let Some(mut solver) = construct(variable_scale) else {
495 attempts.push(format!(
496 "scale={variable_scale}: change of energy units is not exactly representable"
497 ));
498 continue;
499 };
500 solver.solve();
501 crate::debug_tags!(#subtraction, #threshold, #overlap, #socp;
502 stage = "threshold_socp_result",
503 graph = graph_name,
504 surfaces = ?esurfaces,
505 variable_scale,
506 status = ?solver.solution.status,
507 iterations = solver.solution.iterations,
508 primal_objective = solver.solution.obj_val,
509 dual_objective = solver.solution.obj_val_dual,
510 primal_residual = solver.solution.r_prim,
511 dual_residual = solver.solution.r_dual,
512 file.solution = ?solver.solution,
513 "finished threshold overlap solve"
514 );
515
516 for coordinate in &mut solver.solution.x {
519 *coordinate *= variable_scale;
520 }
521 if center_is_interior(&solver.solution.x) {
522 return Ok(Some(solver.solution.x));
523 }
524 if solver.solution.status == SolverStatus::PrimalInfeasible {
525 return Ok(None);
526 }
527 attempts.push(format!(
528 "scale={variable_scale}: {:?} without a strictly interior center: primal objective={}, dual objective={}, primal residual={}, dual residual={}, iterations={}",
529 solver.solution.status,
530 solver.solution.obj_val,
531 solver.solution.obj_val_dual,
532 solver.solution.r_prim,
533 solver.solution.r_dual,
534 solver.solution.iterations,
535 ));
536 }
537 Err(SamplingEvaluationError::UncertifiedOverlap {
538 detail: format!(
539 "Threshold SOCP for graph '{graph_name}' and E-surfaces {esurfaces:?}: {}",
540 attempts.join("; retry "),
541 ),
542 }
543 .into())
544 }
545
546 fn threshold_subspace(&self, esurface_id: EsurfaceID) -> &SubspaceData {
547 self.threshold_subspaces
548 .map_or(self.subspace, |subspaces| &subspaces[esurface_id.0])
549 }
550
551 fn validate_subspaces(&self) -> Result<()> {
552 let Some(threshold_subspaces) = self.threshold_subspaces else {
553 return Ok(());
554 };
555 if threshold_subspaces.len() != self.thresholds.len() {
556 return Err(eyre!(
557 "Projected threshold overlap has {} subspaces for {} E-surfaces",
558 threshold_subspaces.len(),
559 self.thresholds.len(),
560 ));
561 }
562 let common_parent = self.subspace.parent_lmb_index();
563 for (esurface_index, subspace) in threshold_subspaces.iter().enumerate() {
564 if subspace.parent_lmb_index() != common_parent {
565 return Err(eyre!(
566 "Projected threshold E-surface instance {} uses parent LMB {}, while its common center uses parent LMB {}",
567 esurface_index,
568 usize::from(subspace.parent_lmb_index()),
569 usize::from(common_parent),
570 ));
571 }
572 if subspace
573 .iter_lmb_indices()
574 .any(|loop_index| !self.subspace.contains_loop_index(loop_index))
575 {
576 return Err(eyre!(
577 "Projected threshold E-surface instance {} contains coordinates outside the common-center subspace",
578 esurface_index,
579 ));
580 }
581 }
582 Ok(())
583 }
584}
585
586pub(crate) fn check_global_center(
587 overlap_input: &OverlapInput,
588 existing_esurfaces: &ExistingThresholds,
589 center: &LoopMomenta<F<f64>>,
590 loop_moms: &LoopMomenta<F<f64>>,
591 external_momenta: &ExternalFourMomenta<F<f64>>,
592) -> bool {
593 existing_esurfaces.iter().all(|esurface_id| {
594 let esurface = &overlap_input.thresholds[*esurface_id];
595 let threshold_subspace = overlap_input.threshold_subspace(*esurface_id);
596 let (surface_loop_moms, surface_external_momenta, edge_masses) = overlap_input
597 .surface_kinematics
598 .and_then(|kinematics| kinematics.get(esurface_id.0))
599 .map(|kinematics| {
600 (
601 &kinematics.loop_moms,
602 &kinematics.external_momenta,
603 kinematics
604 .edge_masses
605 .as_ref()
606 .unwrap_or(&overlap_input.edge_masses),
607 )
608 })
609 .unwrap_or((loop_moms, external_momenta, &overlap_input.edge_masses));
610 let mut center_with_fixed_complement = surface_loop_moms.clone();
611 for loop_index in threshold_subspace.iter_lmb_indices() {
612 center_with_fixed_complement[loop_index] = center[loop_index];
613 }
614
615 let lmb = threshold_subspace.get_lmb(overlap_input.lmbs);
616
617 let esurface_val = esurface.compute_from_momenta(
618 lmb,
619 edge_masses,
620 ¢er_with_fixed_complement,
621 surface_external_momenta,
622 );
623
624 esurface_value_is_strictly_inside(&esurface_val, &F(overlap_input.settings.kinematics.e_cm))
625 })
626}
627
628pub(crate) fn find_maximal_overlap(
635 overlap_input: &OverlapInput,
636 existing_esurfaces: &ExistingThresholds,
637 loop_moms: &LoopMomenta<F<f64>>,
638 external_momenta: &ExternalFourMomenta<F<f64>>,
639 probe_rotation: &Rotation,
640) -> Result<OverlapStructure> {
641 if let Some(surface_kinematics) = overlap_input.surface_kinematics
642 && surface_kinematics.len() != overlap_input.thresholds.len()
643 {
644 return Err(eyre!(
645 "per-surface overlap kinematics has {} entries for {} threshold surfaces",
646 surface_kinematics.len(),
647 overlap_input.thresholds.len()
648 ));
649 }
650 overlap_input.validate_subspaces()?;
651 if existing_esurfaces.is_empty()
654 && overlap_input
655 .settings
656 .subtraction
657 .overlap_settings
658 .force_global_center
659 .is_some()
660 {
661 return find_maximal_overlap_single(
662 overlap_input,
663 existing_esurfaces,
664 loop_moms,
665 external_momenta,
666 probe_rotation,
667 );
668 }
669 let mut partitions: Vec<(SubspaceData, ExistingThresholds)> = Vec::new();
670 for existing_id in existing_esurfaces.iter_enumerated().map(|(id, _)| id) {
671 let surface_id = existing_esurfaces[existing_id];
672 let threshold_subspace = overlap_input.threshold_subspace(surface_id);
673 if let Some((_, members)) = partitions.iter_mut().find(|(representative, _)| {
674 representative.solve_signature(overlap_input.lmbs)
675 == threshold_subspace.solve_signature(overlap_input.lmbs)
676 }) {
677 members.push(surface_id);
678 } else {
679 let mut members = ExistingThresholds::new();
680 members.push(surface_id);
681 partitions.push((threshold_subspace.clone(), members));
682 }
683 }
684
685 let mut combined = OverlapStructure {
686 overlap_groups: Vec::new(),
687 existing_esurfaces: existing_esurfaces.clone(),
688 };
689 let mut original_ids_by_partition = Vec::new();
690 for (_, members) in &partitions {
691 original_ids_by_partition.push(
692 members
693 .iter()
694 .map(|surface_id| {
695 existing_esurfaces
696 .iter_enumerated()
697 .find_map(|(id, candidate)| (candidate == surface_id).then_some(id))
698 .expect("partition surface must belong to original threshold set")
699 })
700 .collect::<Vec<_>>(),
701 );
702 }
703 for (partition_index, (representative, members)) in partitions.into_iter().enumerate() {
704 let partition_input = OverlapInput {
705 graph: overlap_input.graph,
706 settings: overlap_input.settings,
707 subspace: &representative,
708 threshold_subspaces: None,
709 lmbs: overlap_input.lmbs,
710 thresholds: overlap_input.thresholds,
711 edge_masses: overlap_input.edge_masses.clone(),
712 surface_kinematics: overlap_input.surface_kinematics,
713 };
714 let local = find_maximal_overlap_single(
715 &partition_input,
716 &members,
717 loop_moms,
718 external_momenta,
719 probe_rotation,
720 )?;
721 for mut group in local.overlap_groups {
722 for id in group
723 .existing_esurfaces
724 .iter_mut()
725 .chain(&mut group.complement)
726 {
727 *id = original_ids_by_partition[partition_index][usize::from(*id)];
728 }
729 combined.overlap_groups.push(group);
730 }
731 }
732 Ok(combined)
733}
734
735#[cfg(test)]
738pub(crate) fn find_maximal_overlap_with_group_ids(
739 overlap_input: &OverlapInput,
740 existing_esurfaces: &ExistingThresholds,
741 group_ids: &[Option<usize>],
742 loop_moms: &LoopMomenta<F<f64>>,
743 external_momenta: &ExternalFourMomenta<F<f64>>,
744 probe_rotation: &Rotation,
745) -> Result<OverlapStructure> {
746 if let Some(surface_kinematics) = overlap_input.surface_kinematics
747 && surface_kinematics.len() != overlap_input.thresholds.len()
748 {
749 return Err(eyre!(
750 "per-surface overlap kinematics has {} entries for {} threshold surfaces",
751 surface_kinematics.len(),
752 overlap_input.thresholds.len()
753 ));
754 }
755 if group_ids.len() < overlap_input.thresholds.len() {
756 return Err(eyre!(
757 "threshold solve-group metadata has {} entries for {} threshold surfaces",
758 group_ids.len(),
759 overlap_input.thresholds.len()
760 ));
761 }
762 overlap_input.validate_subspaces()?;
763 let mut labelled = BTreeMap::<Option<usize>, ExistingThresholds>::new();
764 for existing_id in existing_esurfaces.iter_enumerated().map(|(id, _)| id) {
765 let surface_id = existing_esurfaces[existing_id];
766 let label = group_ids[surface_id.0];
767 labelled.entry(label).or_default().push(surface_id);
768 }
769 for (label, members) in &labelled {
770 let Some(group_id) = label else {
771 continue;
772 };
773 let Some(first_surface_id) = members.first() else {
774 continue;
775 };
776 let first_subspace = overlap_input.threshold_subspace(*first_surface_id);
777 let first_signature = first_subspace.solve_signature(overlap_input.lmbs);
778 let incompatible = members.iter().skip(1).any(|surface_id| {
779 overlap_input
780 .threshold_subspace(*surface_id)
781 .solve_signature(overlap_input.lmbs)
782 != first_signature
783 });
784 if incompatible {
785 let members = members
786 .iter()
787 .map(|surface_id| {
788 let subspace = overlap_input.threshold_subspace(*surface_id);
789 format!(
790 "E-surface instance {}: parent {:?}, selected cycles {:?}",
791 surface_id.0,
792 subspace.get_lmb(overlap_input.lmbs).loop_edges,
793 subspace.solve_signature(overlap_input.lmbs)
794 )
795 })
796 .join("; ");
797 return Err(eyre!(
798 "explicit threshold group_id={} contains incompatible solve subspaces: {}",
799 group_id,
800 members,
801 ));
802 }
803 }
804 let mut combined = OverlapStructure {
805 overlap_groups: Vec::new(),
806 existing_esurfaces: existing_esurfaces.clone(),
807 };
808 for members in labelled.into_values() {
809 let local = find_maximal_overlap(
810 overlap_input,
811 &members,
812 loop_moms,
813 external_momenta,
814 probe_rotation,
815 )?;
816 for mut group in local.overlap_groups {
817 for id in group
818 .existing_esurfaces
819 .iter_mut()
820 .chain(&mut group.complement)
821 {
822 let surface_id = local.existing_esurfaces[*id];
823 *id = existing_esurfaces
824 .iter_enumerated()
825 .find_map(|(original_id, candidate)| {
826 (candidate == &surface_id).then_some(original_id)
827 })
828 .expect("labelled overlap surface must belong to original threshold set");
829 }
830 combined.overlap_groups.push(group);
831 }
832 }
833 Ok(combined)
834}
835
836fn find_maximal_overlap_single(
837 overlap_input: &OverlapInput,
838 existing_esurfaces: &ExistingThresholds,
839 loop_moms: &LoopMomenta<F<f64>>,
840 external_momenta: &ExternalFourMomenta<F<f64>>,
841 probe_rotation: &Rotation,
842) -> Result<OverlapStructure> {
843 overlap_input.validate_subspaces()?;
844 let mut res = OverlapStructure {
845 overlap_groups: vec![],
846 existing_esurfaces: existing_esurfaces.clone(),
847 };
848
849 let settings = overlap_input.settings;
850
851 let all_existing_esurfaces = existing_esurfaces
852 .iter_enumerated()
853 .map(|a| a.0)
854 .collect_vec();
855
856 if let Some(global_center) = &settings.subtraction.overlap_settings.force_global_center {
857 let global_center_identity: LoopMomenta<F<f64>> = global_center
858 .iter()
859 .map(|coordinates| ThreeMomentum {
860 px: F(coordinates[0]),
861 py: F(coordinates[1]),
862 pz: F(coordinates[2]),
863 })
864 .collect();
865 let center_externals = existing_esurfaces
866 .first()
867 .and_then(|id| {
868 overlap_input
869 .surface_kinematics
870 .map(|kinematics| &kinematics[id.0].external_momenta)
871 })
872 .unwrap_or(external_momenta);
873 let rotated_external_spatial = center_externals
874 .iter()
875 .map(|momentum| momentum.spatial)
876 .collect();
877 let mut global_center_probe = global_center_identity.rotate(probe_rotation).lmb_transform(
880 &overlap_input.graph.loop_momentum_basis,
881 overlap_input.subspace.get_lmb(overlap_input.lmbs),
882 &rotated_external_spatial,
883 );
884 if let Some(surface_kinematics) = overlap_input.surface_kinematics {
885 for surface_id in existing_esurfaces {
886 let surface_externals = &surface_kinematics[surface_id.0].external_momenta;
887 if surface_externals == center_externals {
888 continue;
889 }
890 let surface_center = global_center_identity.rotate(probe_rotation).lmb_transform(
891 &overlap_input.graph.loop_momentum_basis,
892 overlap_input.subspace.get_lmb(overlap_input.lmbs),
893 &surface_externals
894 .iter()
895 .map(|momentum| momentum.spatial)
896 .collect(),
897 );
898 if overlap_input
899 .subspace
900 .iter_lmb_indices()
901 .any(|index| surface_center[index] != global_center_probe[index])
902 {
903 return Err(eyre!(
904 "forced global threshold center gives incompatible active coordinates for E-surface instance {}: selected edges {:?}, center {}, expected {}",
905 surface_id.0,
906 overlap_input
907 .subspace
908 .iter_basis_edges(overlap_input.lmbs)
909 .collect_vec(),
910 surface_center,
911 global_center_probe,
912 ));
913 }
914 }
915 }
916 for loop_index in (0..global_center_probe.len()).map(LoopIndex::from) {
921 if !overlap_input.subspace.contains_loop_index(loop_index) {
922 global_center_probe[loop_index] = ThreeMomentum::new(F(0.0), F(0.0), F(0.0));
923 }
924 }
925
926 tracing::debug!(
927 graph = %overlap_input.graph.name,
928 rotation_id = %probe_rotation.method,
929 center_provenance = "forced_identity_frame_rotated_lmb_transformed_and_projected_once",
930 identity_center = %global_center_identity,
931 probe_cut_lmb_center = %global_center_probe,
932 "prepared forced LU overlap center"
933 );
934
935 if !settings.subtraction.overlap_settings.check_global_center {
936 tracing::warn!(
937 graph = %overlap_input.graph.name,
938 "overlap_settings.check_global_center=false is deprecated; forced centers are always validated"
939 );
940 }
941
942 let is_valid = check_global_center(
943 overlap_input,
944 existing_esurfaces,
945 &global_center_probe,
946 loop_moms,
947 external_momenta,
948 );
949
950 if !is_valid {
951 return Err(eyre!(
952 "Forced identity-frame center is not finite and strictly inside all existing esurfaces after applying probe rotation {} and the cut-side LMB transform",
953 probe_rotation.method,
954 ));
955 }
956
957 let single_group = OverlapGroup {
958 existing_esurfaces: all_existing_esurfaces,
959 center: global_center_probe,
960 complement: vec![],
961 };
962 res.overlap_groups.push(single_group);
963
964 res.fill_in_complements();
965 return Ok(res);
966 }
967
968 if settings.subtraction.overlap_settings.try_origin {
969 let global_loop_count = overlap_input.graph.get_loop_number();
970 let origin = LoopMomenta::from_iter(
971 (0..global_loop_count).map(|_| ThreeMomentum::new(F(0.0), F(0.0), F(0.0))),
972 );
973
974 let is_valid = check_global_center(
975 overlap_input,
976 existing_esurfaces,
977 &origin,
978 loop_moms,
979 external_momenta,
980 );
981
982 if is_valid {
983 let single_group = OverlapGroup {
984 existing_esurfaces: all_existing_esurfaces,
985 center: origin,
986 complement: vec![],
987 };
988 res.overlap_groups.push(single_group);
989 res.fill_in_complements();
990 return Ok(res);
991 }
992 }
993
994 if settings.subtraction.overlap_settings.try_origin_all_lmbs {
995 todo!("Not all heuristics implemented")
996 }
997
998 let option_center = find_center(
1001 overlap_input,
1002 &all_existing_esurfaces,
1003 existing_esurfaces,
1004 loop_moms,
1005 external_momenta,
1006 false,
1007 )?;
1008
1009 if let Some(center) = option_center {
1010 let single_group = OverlapGroup {
1011 existing_esurfaces: all_existing_esurfaces,
1012 center,
1013 complement: vec![],
1014 };
1015 res.overlap_groups.push(single_group);
1016 res.fill_in_complements();
1017 return Ok(res);
1018 }
1019
1020 let esurface_pairs = EsurfacePairs::new(
1022 overlap_input,
1023 existing_esurfaces,
1024 loop_moms,
1025 external_momenta,
1026 )?;
1027
1028 let mut num_disconnected_surfaces = 0;
1033
1034 for (existing_esurface_id, &esurface_id) in existing_esurfaces.iter_enumerated() {
1035 if esurface_pairs.has_no_overlap(existing_esurface_id) {
1037 let center = find_center(
1038 overlap_input,
1039 &[existing_esurface_id],
1040 existing_esurfaces,
1041 loop_moms,
1042 external_momenta,
1043 false,
1044 )?
1045 .ok_or_else(|| {
1046 let esurface = &overlap_input.thresholds[esurface_id];
1047
1048 let mut error_message = String::new();
1049
1050 error_message.push_str(&format!(
1051 "Could not find center of esuface {:?}\n",
1052 esurface_id
1053 ));
1054 error_message.push_str(&format!("edges: {:?}\n", esurface.energies));
1055
1056 error_message.push_str(&format!("external shift: {:?}\n", esurface.external_shift));
1057
1058 error_message.push_str(&format!("External momenta: {:#?}\n", external_momenta));
1059
1060 eyre!("{}", error_message)
1061 })?;
1062
1063 res.overlap_groups.push(OverlapGroup {
1064 existing_esurfaces: vec![existing_esurface_id],
1065 center,
1066 complement: vec![],
1067 });
1068 num_disconnected_surfaces += 1;
1069 }
1070 }
1071
1072 if num_disconnected_surfaces == existing_esurfaces.len() {
1077 res.fill_in_complements();
1078 return Ok(res);
1079 }
1080
1081 let mut subset_size =
1082 if let Some(size) = esurface_pairs.has_pair_with.iter().map(Vec::len).max() {
1083 size + 1
1084 } else {
1085 1
1086 };
1087
1088 while subset_size > 1 {
1089 let possible_subsets =
1090 esurface_pairs.construct_possible_subsets_of_len(existing_esurfaces, subset_size, &res);
1091
1092 for subset in possible_subsets.iter() {
1093 let option_center = find_center(
1094 overlap_input,
1095 subset,
1096 existing_esurfaces,
1097 loop_moms,
1098 external_momenta,
1099 false,
1100 )?;
1101
1102 if let Some(center) = option_center {
1103 res.overlap_groups.push(OverlapGroup {
1104 existing_esurfaces: subset.clone(),
1105 center,
1106 complement: vec![],
1107 });
1108 }
1109 }
1110
1111 subset_size -= 1;
1119 }
1120
1121 res.fill_in_complements();
1122 Ok(res)
1123}
1124
1125fn is_subset_of_result(subset: &[ExistingEsurfaceId], result: &OverlapStructure) -> bool {
1126 result.overlap_groups.iter().any(|group| {
1127 subset
1128 .iter()
1129 .all(|&x| group.existing_esurfaces.contains(&x))
1130 })
1131}
1132
1133#[derive(Debug)]
1134struct EsurfacePairs {
1135 data: HashMap<(ExistingEsurfaceId, ExistingEsurfaceId), LoopMomenta<F<f64>>>,
1136 has_pair_with: Vec<Vec<ExistingEsurfaceId>>,
1137}
1138
1139impl EsurfacePairs {
1140 fn insert(
1141 &mut self,
1142 pair: (ExistingEsurfaceId, ExistingEsurfaceId),
1143 center: LoopMomenta<F<f64>>,
1144 ) {
1145 if pair.0 > pair.1 {
1146 self.data.insert((pair.1, pair.0), center);
1147 } else {
1148 self.data.insert(pair, center);
1149 }
1150 }
1151
1152 fn pair_exists(&self, pair: (ExistingEsurfaceId, ExistingEsurfaceId)) -> bool {
1153 if pair.0 > pair.1 {
1154 self.data.contains_key(&(pair.1, pair.0))
1155 } else {
1156 self.data.contains_key(&pair)
1157 }
1158 }
1159
1160 fn new_empty(num_existing_esurfaces: usize) -> Self {
1161 let capacity = match num_existing_esurfaces {
1162 0 => 0,
1163 1 => 0,
1164 _ => num_existing_esurfaces * (num_existing_esurfaces - 1) / 2,
1165 };
1166
1167 Self {
1168 data: HashMap::with_capacity(capacity),
1169 has_pair_with: vec![Vec::with_capacity(num_existing_esurfaces); num_existing_esurfaces],
1170 }
1171 }
1172
1173 fn new(
1174 overlap_input: &OverlapInput,
1175 existing_esurfaces: &ExistingThresholds,
1176 loop_moms: &LoopMomenta<F<f64>>,
1177 external_momenta: &ExternalFourMomenta<F<f64>>,
1178 ) -> Result<Self> {
1179 let mut res = Self::new_empty(existing_esurfaces.len());
1180
1181 let all_existing_esurfaces = existing_esurfaces
1182 .iter_enumerated()
1183 .map(|a| a.0)
1184 .collect_vec();
1185
1186 for (i, &esurface_id_1) in all_existing_esurfaces.iter().enumerate() {
1187 for &esurface_id_2 in all_existing_esurfaces.iter().skip(i + 1) {
1188 let center = find_center(
1189 overlap_input,
1190 &[esurface_id_1, esurface_id_2],
1191 existing_esurfaces,
1192 loop_moms,
1193 external_momenta,
1194 false,
1195 )?;
1196
1197 if let Some(center) = center {
1198 res.insert((esurface_id_1, esurface_id_2), center);
1199 res.has_pair_with[Into::<usize>::into(esurface_id_1)].push(esurface_id_2);
1200 res.has_pair_with[Into::<usize>::into(esurface_id_2)].push(esurface_id_1);
1201 }
1202 }
1203 }
1204
1205 Ok(res)
1206 }
1207
1208 fn has_no_overlap(&self, esurface_id: ExistingEsurfaceId) -> bool {
1209 self.has_pair_with[Into::<usize>::into(esurface_id)].is_empty()
1210 }
1211
1212 fn construct_possible_subsets_of_len(
1213 &self,
1214 existing_esurfaces: &ExistingThresholds,
1215 subset_len: usize,
1216 result: &OverlapStructure,
1217 ) -> HashSet<Vec<ExistingEsurfaceId>> {
1218 let mut res = HashSet::default();
1219 for (first, _) in existing_esurfaces.iter_enumerated() {
1222 for remaining in self.has_pair_with[usize::from(first)]
1223 .iter()
1224 .copied()
1225 .filter(|&id| id > first)
1226 .combinations(subset_len - 1)
1227 {
1228 if !remaining
1229 .iter()
1230 .tuple_combinations()
1231 .all(|(&left, &right)| self.pair_exists((left, right)))
1232 {
1233 continue;
1234 }
1235 let mut candidate = vec![first];
1236 candidate.extend(remaining);
1237 candidate.sort_unstable();
1238 if !is_subset_of_result(&candidate, result) {
1239 res.insert(candidate);
1240 }
1241 }
1242 }
1243 res
1244 }
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249 use super::*;
1250 use crate::{
1251 cff::{
1252 VertexSet,
1253 esurface::{Esurface, EsurfaceExistence, EsurfaceID},
1254 },
1255 dot,
1256 graph::{LMBext, parse::from_dot::IntoGraph},
1257 initialisation::test_initialise,
1258 momentum::{FourMomentum, Rotatable, Rotation, RotationMethod},
1259 };
1260 use linnet::half_edge::involution::EdgeIndex;
1261 use linnet::half_edge::subgraph::{SuBitGraph, SubSetOps};
1262 use typed_index_collections::ti_vec;
1263
1264 #[test]
1265 fn gl297_recorded_uncertified_socp_problems_are_rescued_without_dropping_overlaps() {
1266 let fixtures: Vec<serde_json::Value> = serde_json::from_str(include_str!(
1267 "../../../../tests/resources/graphs/ir_safe_thresholds/GL297_socp_failures.json"
1268 ))
1269 .unwrap();
1270 for fixture in fixtures {
1271 let a: Vec<Vec<f64>> = serde_json::from_value(fixture["a"].clone()).unwrap();
1272 let b: Vec<f64> = serde_json::from_value(fixture["b"].clone()).unwrap();
1273 let q: Vec<f64> = serde_json::from_value(fixture["q"].clone()).unwrap();
1274 let dimensions: Vec<(String, usize)> =
1275 serde_json::from_value(fixture["cones"].clone()).unwrap();
1276 let cones = dimensions
1277 .iter()
1278 .map(|(kind, size)| match kind.as_str() {
1279 "NonnegativeConeT" => NonnegativeConeT(*size),
1280 "SecondOrderConeT" => SecondOrderConeT(*size),
1281 _ => panic!("unexpected fixture cone {kind}"),
1282 })
1283 .collect::<Vec<_>>();
1284 let surfaces: ExistingThresholds = (0..dimensions[1].1).map(EsurfaceID).collect();
1285 let mut attempts = Vec::new();
1286 let result = OverlapInput::solve_with_rescaling(
1287 "GL297",
1288 &surfaces,
1289 |scale| {
1290 attempts.push(scale);
1291 Some(
1292 DefaultSolver::new(
1293 &CscMatrix::spalloc((q.len(), q.len()), 0),
1294 &q,
1295 &CscMatrix::from(&a),
1296 &b.iter().map(|value| value / scale).collect::<Vec<_>>(),
1297 &cones,
1298 DefaultSettingsBuilder::default()
1299 .verbose(false)
1300 .build()
1301 .unwrap(),
1302 )
1303 .unwrap(),
1304 )
1305 },
1306 |coordinates| {
1307 let active_offset = q.len() - 3;
1310 let mut offset = 1 + surfaces.len();
1311 let energies = dimensions[2..]
1312 .iter()
1313 .map(|(_, size)| {
1314 let energy = (offset + 1..offset + size)
1315 .map(|row| {
1316 let shift = b[row]
1317 - (active_offset..q.len())
1318 .map(|column| a[row][column] * coordinates[column])
1319 .sum::<f64>();
1320 shift * shift
1321 })
1322 .sum::<f64>()
1323 .sqrt();
1324 offset += size;
1325 energy
1326 })
1327 .collect::<Vec<_>>();
1328 (1..=surfaces.len()).all(|row| {
1329 energies
1330 .iter()
1331 .enumerate()
1332 .map(|(index, energy)| a[row][index + 1] * energy)
1333 .sum::<f64>()
1334 < b[row]
1335 })
1336 },
1337 )
1338 .unwrap();
1339 assert!(result.is_none(), "{}", fixture["strategy"]);
1343 assert!(attempts == [1.0] || attempts == [1.0, 2.0]);
1344 }
1345 }
1346
1347 #[test]
1348 fn maximal_overlap_candidates_include_cliques_whose_pairs_are_already_covered() {
1349 let existing: ExistingThresholds = (0..9).map(EsurfaceID::from).collect();
1350 let center = LoopMomenta::from_iter([ThreeMomentum::new(F(0.0), F(0.0), F(0.0))]);
1351 let larger = [[0, 1, 3, 4], [0, 2, 5, 6], [1, 2, 7, 8]];
1354 let result = OverlapStructure {
1355 existing_esurfaces: existing.clone(),
1356 overlap_groups: larger
1357 .map(|members| OverlapGroup {
1358 existing_esurfaces: members.into_iter().map(ExistingEsurfaceId::from).collect(),
1359 complement: vec![],
1360 center: center.clone(),
1361 })
1362 .to_vec(),
1363 };
1364 let mut pairs = EsurfacePairs::new_empty(existing.len());
1365 for members in larger {
1366 for (left, right) in members
1367 .into_iter()
1368 .map(ExistingEsurfaceId::from)
1369 .tuple_combinations()
1370 {
1371 if !pairs.pair_exists((left, right)) {
1372 pairs.insert((left, right), center.clone());
1373 pairs.has_pair_with[usize::from(left)].push(right);
1374 pairs.has_pair_with[usize::from(right)].push(left);
1375 }
1376 }
1377 }
1378 let central = (0..3).map(ExistingEsurfaceId::from).collect_vec();
1379 assert!(
1380 central
1381 .iter()
1382 .tuple_combinations()
1383 .all(|(&left, &right)| { is_subset_of_result(&[left, right], &result) })
1384 );
1385 assert_eq!(
1386 pairs.construct_possible_subsets_of_len(&existing, 3, &result),
1387 HashSet::from_iter([central]),
1388 );
1389 }
1390
1391 #[test]
1392 fn overlap_partitions_remap_complements_and_keep_instance_kinematics() {
1393 test_initialise().unwrap();
1394 let graph: Graph = dot!(digraph partitioned_centers {
1395 ext [style=invis]
1396 edge [num=1 mass=0]
1397 node [num=1]
1398 ext->a:0 [id=0]
1399 a->b [id=1]
1400 b->a [id=2]
1401 a->b [id=3]
1402 b->a [id=4]
1403 ext->b:1 [id=5]
1404 })
1405 .unwrap();
1406 let lmbs = ti_vec![graph.loop_momentum_basis.clone()];
1407 let subspaces = [LoopIndex(0), LoopIndex(1)].map(|index| {
1408 SubspaceData::new_from_parent_basis_edges(
1409 &[graph.loop_momentum_basis.loop_edges[index]],
1410 &graph.full_filter(),
1411 LmbIndex::from(0),
1412 &graph,
1413 &lmbs,
1414 )
1415 .unwrap()
1416 });
1417 let common = SubspaceData::union_in_common_parent(subspaces.iter(), &graph, &lmbs).unwrap();
1418 let support = graph
1419 .iter_loop_edges()
1420 .map(|(_, edge, _)| edge)
1421 .find(|edge| !graph.loop_momentum_basis.loop_edges.contains(edge))
1422 .unwrap();
1423 let external =
1424 graph.loop_momentum_basis.ext_edges[crate::momentum::sample::ExternalIndex(0)];
1425 let thresholds: EsurfaceCollection = (0..4)
1426 .map(|_| Esurface {
1427 energies: vec![support],
1428 external_shift: vec![(external, -1)],
1429 vertex_set: VertexSet::dummy(),
1430 })
1431 .collect::<Vec<_>>()
1432 .into();
1433 let threshold_subspaces = [0, 1, 0, 1].map(|index| subspaces[index].clone());
1434 let masses = graph.underlying.new_edgevec(|_, _, _| F(0.0));
1435 let externals: ExternalFourMomenta<_> = [
1436 FourMomentum::from_args(F(1.0), F(0.0), F(0.0), F(0.0)),
1437 FourMomentum::from_args(F(-1.0), F(0.0), F(0.0), F(0.0)),
1438 ]
1439 .into_iter()
1440 .collect();
1441 let mut kinematics = (0..4)
1442 .map(|index| {
1443 let mut loop_moms = LoopMomenta::from_iter(
1444 (0..3).map(|_| ThreeMomentum::new(F(0.0), F(0.0), F(0.0))),
1445 );
1446 loop_moms[LoopIndex(2)].px = F(10.0 * (index + 1) as f64);
1447 OverlapKinematics {
1448 loop_moms,
1449 external_momenta: externals.clone(),
1450 edge_masses: None,
1451 }
1452 })
1453 .collect_vec();
1454 let mut member_masses = masses.clone();
1455 member_masses[support] = F(0.5);
1456 kinematics[0].edge_masses = Some(member_masses);
1457 let settings = RuntimeSettings::default();
1458 let input = OverlapInput {
1459 graph: &graph,
1460 settings: &settings,
1461 subspace: &common,
1462 threshold_subspaces: Some(&threshold_subspaces),
1463 lmbs: &lmbs,
1464 thresholds: &thresholds,
1465 edge_masses: masses.clone(),
1466 surface_kinematics: Some(&kinematics),
1467 };
1468 let existing: ExistingThresholds = thresholds.keys().collect();
1469 let rotation = Rotation::new(RotationMethod::Identity);
1470 let automatic = find_maximal_overlap(
1471 &input,
1472 &existing,
1473 &kinematics[0].loop_moms,
1474 &externals,
1475 &rotation,
1476 )
1477 .unwrap();
1478 let labelled = find_maximal_overlap_with_group_ids(
1479 &input,
1480 &existing,
1481 &[Some(0), Some(1), Some(0), Some(1)],
1482 &kinematics[0].loop_moms,
1483 &externals,
1484 &rotation,
1485 )
1486 .unwrap();
1487 let conflict = find_maximal_overlap_with_group_ids(
1488 &input,
1489 &existing,
1490 &[Some(0); 4],
1491 &kinematics[0].loop_moms,
1492 &externals,
1493 &rotation,
1494 )
1495 .unwrap_err()
1496 .to_string();
1497 assert!(conflict.contains("group_id=0"));
1498 for member in 0..4 {
1499 assert!(conflict.contains(&format!("E-surface instance {member}:")));
1500 }
1501 for overlap in [&automatic, &labelled] {
1502 assert_eq!(overlap.overlap_groups.len(), 4);
1503 for group in &overlap.overlap_groups {
1504 assert_eq!(group.existing_esurfaces.len(), 1);
1505 let member = usize::from(group.existing_esurfaces[0]);
1506 assert_eq!(
1507 group.complement,
1508 vec![ExistingEsurfaceId::from((member + 2) % 4)]
1509 );
1510 assert!(check_global_center(
1511 &input,
1512 &ti_vec![EsurfaceID(member)],
1513 &group.center,
1514 &kinematics[member].loop_moms,
1515 &externals
1516 ));
1517 assert!(group.center.hyper_radius_squared(None) > F(1.0));
1518 }
1519 }
1520 let first = automatic
1521 .overlap_groups
1522 .iter()
1523 .find(|group| group.existing_esurfaces == vec![ExistingEsurfaceId::from(0)])
1524 .unwrap();
1525 let mut outside_massive_surface = first.center.clone();
1526 outside_massive_surface[LoopIndex(0)].px += F(0.95);
1527 assert!(!check_global_center(
1528 &input,
1529 &ti_vec![EsurfaceID(0)],
1530 &outside_massive_surface,
1531 &kinematics[0].loop_moms,
1532 &externals
1533 ));
1534
1535 let mut forced_settings = settings.clone();
1536 forced_settings
1537 .subtraction
1538 .overlap_settings
1539 .force_global_center = Some(
1540 first
1541 .center
1542 .iter()
1543 .map(|momentum| [momentum.px.0, momentum.py.0, momentum.pz.0])
1544 .collect(),
1545 );
1546 kinematics[1].external_momenta[crate::momentum::sample::ExternalIndex(0)]
1549 .temporal
1550 .value = F(2.0);
1551 let forced_input = OverlapInput {
1552 graph: &graph,
1553 settings: &forced_settings,
1554 subspace: &common,
1555 threshold_subspaces: Some(&threshold_subspaces),
1556 lmbs: &lmbs,
1557 thresholds: &thresholds,
1558 edge_masses: masses,
1559 surface_kinematics: Some(&kinematics),
1560 };
1561 assert!(
1562 find_maximal_overlap(
1563 &forced_input,
1564 &ti_vec![EsurfaceID(0)],
1565 &kinematics[0].loop_moms,
1566 &externals,
1567 &rotation
1568 )
1569 .is_ok()
1570 );
1571 }
1572
1573 #[test]
1574 fn global_center_check_preserves_fixed_complement_for_multidimensional_subspace() {
1575 test_initialise().unwrap();
1576 let graph: Graph = dot!(digraph subspace_center {
1577 ext [style=invis]
1578 edge [num=1 mass=0]
1579 node [num=1]
1580 ext->v1:0 [id=0]
1581 v1->v2 [id=1]
1582 v2->v1 [id=2]
1583 v1->v2 [id=3]
1584 v2->v1 [id=4]
1585 ext->v2:1 [id=5]
1586 })
1587 .unwrap();
1588
1589 assert_eq!(graph.loop_momentum_basis.loop_edges.len(), 3);
1590 let all_lmbs = ti_vec![graph.loop_momentum_basis.clone()];
1591
1592 let mut parallel_edges_only: SuBitGraph = graph.empty_subgraph();
1593 for active_loop_index in [LoopIndex(0), LoopIndex(1)] {
1594 let active_loop_edge = graph.loop_momentum_basis.loop_edges[active_loop_index];
1595 parallel_edges_only.union_with(&graph.get_edge_subgraph(active_loop_edge));
1596 }
1597 let parallel_edge_subspace = SubspaceData::new_with_user_selected_lmb(
1598 parallel_edges_only,
1599 LmbIndex::from(0),
1600 &graph,
1601 &all_lmbs,
1602 )
1603 .unwrap();
1604 assert_eq!(
1605 parallel_edge_subspace.loopcount(),
1606 1,
1607 "two graph-parallel defining edges without their spanning support contain only one independent loop"
1608 );
1609
1610 let raised_graph: Graph = dot!(digraph raised_signature_subspace {
1611 ext [style=invis]
1612 edge [num=1 mass=0]
1613 node [num=1]
1614 ext->a [id=0]
1615 a->b [id=1]
1616 b->c [id=2]
1617 c->a [id=3]
1618 ext->c [id=4]
1619 })
1620 .unwrap();
1621 let raised_group = raised_graph
1622 .get_raised_edge_groups()
1623 .into_iter()
1624 .find(|group| group.len() >= 2)
1625 .expect("test graph must contain a raised equal-signature edge group");
1626 assert!(raised_group.iter().tuple_windows().all(|(left, right)| {
1627 raised_graph.loop_momentum_basis.edge_signatures[*left]
1628 .equality_up_to_sign(&raised_graph.loop_momentum_basis.edge_signatures[*right])
1629 }));
1630 let mut raised_subgraph: SuBitGraph = raised_graph.empty_subgraph();
1631 for &edge in &raised_group {
1632 raised_subgraph.union_with(&raised_graph.get_edge_subgraph(edge));
1633 }
1634 let raised_lmbs = ti_vec![raised_graph.loop_momentum_basis.clone()];
1635 let raised_edges_only = SubspaceData::new_with_user_selected_lmb(
1636 raised_subgraph.clone(),
1637 LmbIndex::from(0),
1638 &raised_graph,
1639 &raised_lmbs,
1640 )
1641 .unwrap();
1642 assert_eq!(
1643 raised_edges_only.loopcount(),
1644 0,
1645 "a chain of raised equal-signature edges is not multiple independent loops"
1646 );
1647
1648 let support_edge = raised_graph
1649 .iter_loop_edges()
1650 .map(|(_, edge, _)| edge)
1651 .find(|edge| !raised_group.contains(edge))
1652 .expect("test graph must contain the support edge closing the loop");
1653 raised_subgraph.union_with(&raised_graph.get_edge_subgraph(support_edge));
1654 let raised_cycle = SubspaceData::new_with_user_selected_lmb(
1655 raised_subgraph,
1656 LmbIndex::from(0),
1657 &raised_graph,
1658 &raised_lmbs,
1659 )
1660 .unwrap();
1661 assert_eq!(
1662 raised_cycle.loopcount(),
1663 1,
1664 "raised equal-signature edges must be counted by topology, not once per edge"
1665 );
1666
1667 let mut subgraph = graph.loop_momentum_basis.tree.clone();
1670 for active_loop_index in [LoopIndex(0), LoopIndex(1)] {
1671 let active_loop_edge = graph.loop_momentum_basis.loop_edges[active_loop_index];
1672 subgraph.union_with(&graph.get_edge_subgraph(active_loop_edge));
1673 }
1674 let subspace = SubspaceData::new_with_user_selected_lmb(
1675 subgraph.clone(),
1676 LmbIndex::from(0),
1677 &graph,
1678 &all_lmbs,
1679 )
1680 .unwrap();
1681 assert_eq!(subspace.loopcount(), 2);
1682
1683 let radial_surface = Esurface {
1684 energies: vec![graph.loop_momentum_basis.loop_edges[LoopIndex(0)]],
1685 external_shift: vec![(EdgeIndex::from(0), -1)],
1686 vertex_set: VertexSet::dummy(),
1687 };
1688 assert!(radial_surface.has_radial_dependence_in_subspace(&subspace, &all_lmbs, &graph,));
1689 let zero_loop_momenta = LoopMomenta::from_iter([
1690 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1691 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1692 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1693 ]);
1694 let classify_radial_surface = |energy| {
1695 let external_momenta = ExternalFourMomenta::from_iter([
1696 FourMomentum::from_args(F(energy), F(10.0), F(0.0), F(0.0)),
1697 FourMomentum::from_args(F(-energy), F(-10.0), F(0.0), F(0.0)),
1698 ]);
1699 radial_surface.classify_existence_subspace(
1700 &zero_loop_momenta,
1701 &external_momenta,
1702 &subspace,
1703 &all_lmbs,
1704 &graph,
1705 &graph.underlying.new_edgevec(|_, _, _| F(0.0)),
1706 &[],
1707 &F(10.0),
1708 &F(crate::utils::DEFAULT_ESURFACE_EXISTENCE_THRESHOLD),
1709 )
1710 };
1711 assert!(matches!(
1712 classify_radial_surface(11.0),
1713 EsurfaceExistence::Existing { .. }
1714 ));
1715 assert!(matches!(
1716 classify_radial_surface(10.0),
1717 EsurfaceExistence::Pinched { .. }
1718 ));
1719 assert!(matches!(
1720 classify_radial_surface(9.0),
1721 EsurfaceExistence::NonExisting { .. }
1722 ));
1723
1724 let complement_edge = graph.loop_momentum_basis.loop_edges[LoopIndex(2)];
1725 let thresholds: crate::cff::esurface::EsurfaceCollection = vec![Esurface {
1726 energies: vec![complement_edge],
1727 external_shift: vec![(EdgeIndex::from(0), -1)],
1728 vertex_set: VertexSet::dummy(),
1729 }]
1730 .into();
1731 assert!(
1732 !thresholds[EsurfaceID::from(0)]
1733 .has_radial_dependence_in_subspace(&subspace, &all_lmbs, &graph,)
1734 );
1735 let masses = graph.underlying.new_edgevec(|_, _, _| F(0.0));
1736 let settings = RuntimeSettings::default();
1737 let overlap_input = OverlapInput {
1738 graph: &graph,
1739 settings: &settings,
1740 subspace: &subspace,
1741 threshold_subspaces: None,
1742 lmbs: &all_lmbs,
1743 thresholds: &thresholds,
1744 edge_masses: masses,
1745 surface_kinematics: None,
1746 };
1747
1748 let external_momenta = ExternalFourMomenta::from_iter([
1749 FourMomentum::from_args(F(1.0), F(0.0), F(0.0), F(0.0)),
1750 FourMomentum::from_args(F(-1.0), F(0.0), F(0.0), F(0.0)),
1751 ]);
1752 let center = LoopMomenta::from_iter([
1753 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1754 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1755 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1756 ]);
1757 let sampled_momenta = LoopMomenta::from_iter([
1758 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1759 ThreeMomentum::new(F(0.0), F(0.0), F(0.0)),
1760 ThreeMomentum::new(F(2.0), F(0.0), F(0.0)),
1761 ]);
1762
1763 let origin_value = overlap_input.thresholds[EsurfaceID::from(0)].compute_from_momenta(
1764 subspace.get_lmb(&all_lmbs),
1765 &overlap_input.edge_masses,
1766 ¢er,
1767 &external_momenta,
1768 );
1769 assert!(origin_value < F(0.0));
1770 assert!(!check_global_center(
1771 &overlap_input,
1772 &ti_vec![EsurfaceID::from(0)],
1773 ¢er,
1774 &sampled_momenta,
1775 &external_momenta,
1776 ));
1777 assert!(check_global_center(
1778 &overlap_input,
1779 &ti_vec![EsurfaceID::from(0)],
1780 ¢er,
1781 ¢er,
1782 &external_momenta,
1783 ));
1784
1785 let full_parent_subspace = SubspaceData::new_with_user_selected_lmb(
1786 graph.full_filter(),
1787 LmbIndex::from(0),
1788 &graph,
1789 &all_lmbs,
1790 )
1791 .unwrap();
1792 let common_subspace = SubspaceData::union_in_common_parent(
1793 [&subspace, &full_parent_subspace],
1794 &graph,
1795 &all_lmbs,
1796 )
1797 .unwrap();
1798 let projected_thresholds: crate::cff::esurface::EsurfaceCollection = vec![
1801 thresholds[EsurfaceID::from(0)].clone(),
1802 thresholds[EsurfaceID::from(0)].clone(),
1803 ]
1804 .into();
1805 let projected_subspaces: TiVec<EsurfaceID, _> =
1806 ti_vec![subspace.clone(), full_parent_subspace];
1807 let projected_input = OverlapInput {
1808 graph: &graph,
1809 settings: &settings,
1810 subspace: &common_subspace,
1811 threshold_subspaces: Some(&projected_subspaces.raw),
1812 lmbs: &all_lmbs,
1813 thresholds: &projected_thresholds,
1814 edge_masses: overlap_input.edge_masses.clone(),
1815 surface_kinematics: overlap_input.surface_kinematics,
1816 };
1817 assert!(!check_global_center(
1818 &projected_input,
1819 &ti_vec![EsurfaceID::from(0)],
1820 ¢er,
1821 &sampled_momenta,
1822 &external_momenta,
1823 ));
1824 assert!(check_global_center(
1825 &projected_input,
1826 &ti_vec![EsurfaceID::from(1)],
1827 ¢er,
1828 &sampled_momenta,
1829 &external_momenta,
1830 ));
1831
1832 let forced_center_coordinates = vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
1833 let identity_frame_center =
1834 LoopMomenta::from_iter(forced_center_coordinates.iter().map(|coordinates| {
1835 ThreeMomentum::new(F(coordinates[0]), F(coordinates[1]), F(coordinates[2]))
1836 }));
1837 let probe_rotation = Rotation::new(RotationMethod::Pi2Z);
1838 let expected_probe_center = identity_frame_center.rotate(&probe_rotation);
1839 let mut expected_subspace_probe_center = expected_probe_center.clone();
1840 for loop_index in (0..expected_subspace_probe_center.len()).map(LoopIndex::from) {
1841 if !subspace.contains_loop_index(loop_index) {
1842 expected_subspace_probe_center[loop_index] =
1843 ThreeMomentum::new(F(0.0), F(0.0), F(0.0));
1844 }
1845 }
1846 let mut forced_settings = RuntimeSettings::default();
1847 forced_settings
1848 .subtraction
1849 .overlap_settings
1850 .force_global_center = Some(forced_center_coordinates.clone());
1851 let forced_overlap_input = OverlapInput {
1852 graph: &graph,
1853 settings: &forced_settings,
1854 subspace: &subspace,
1855 threshold_subspaces: None,
1856 lmbs: &all_lmbs,
1857 thresholds: &thresholds,
1858 edge_masses: overlap_input.edge_masses.clone(),
1859 surface_kinematics: overlap_input.surface_kinematics,
1860 };
1861
1862 let forced_overlap = find_maximal_overlap(
1863 &forced_overlap_input,
1864 &ti_vec![EsurfaceID::from(0)],
1865 ¢er,
1866 &external_momenta,
1867 &probe_rotation,
1868 )
1869 .unwrap();
1870 assert_eq!(forced_overlap.overlap_groups.len(), 1);
1871 assert_eq!(
1872 forced_overlap.overlap_groups[0].center, expected_subspace_probe_center,
1873 "an identity-frame forced center must be rotated exactly once and projected onto the active subspace"
1874 );
1875
1876 let alternate_lmb = graph
1877 .generate_loop_momentum_bases()
1878 .into_iter()
1879 .find(|lmb| lmb.loop_edges != graph.loop_momentum_basis.loop_edges)
1880 .expect("test graph must admit a non-default parent LMB");
1881 let alternate_lmbs = ti_vec![graph.loop_momentum_basis.clone(), alternate_lmb];
1882 let full_subspace = SubspaceData::new_with_user_selected_lmb(
1883 graph.full_filter(),
1884 LmbIndex::from(1),
1885 &graph,
1886 &alternate_lmbs,
1887 )
1888 .unwrap();
1889 let empty_thresholds: EsurfaceCollection = Vec::new().into();
1890 let alternate_overlap_input = OverlapInput {
1891 graph: &graph,
1892 settings: &forced_settings,
1893 subspace: &full_subspace,
1894 threshold_subspaces: None,
1895 lmbs: &alternate_lmbs,
1896 thresholds: &empty_thresholds,
1897 edge_masses: overlap_input.edge_masses.clone(),
1898 surface_kinematics: overlap_input.surface_kinematics,
1899 };
1900 let rotated_external_spatial = external_momenta
1901 .iter()
1902 .map(|momentum| momentum.spatial)
1903 .collect();
1904 let expected_alternate_lmb_center = expected_probe_center.lmb_transform(
1905 &graph.loop_momentum_basis,
1906 full_subspace.get_lmb(&alternate_lmbs),
1907 &rotated_external_spatial,
1908 );
1909 let alternate_overlap = find_maximal_overlap(
1910 &alternate_overlap_input,
1911 &ti_vec![],
1912 ¢er,
1913 &external_momenta,
1914 &probe_rotation,
1915 )
1916 .unwrap();
1917 assert_eq!(
1918 alternate_overlap.overlap_groups[0].center, expected_alternate_lmb_center,
1919 "a forced center must be transformed from the graph LMB into the selected parent LMB after its single probe rotation"
1920 );
1921
1922 let proper_alternate_subspace = SubspaceData::new_with_user_selected_lmb(
1923 subgraph,
1924 LmbIndex::from(1),
1925 &graph,
1926 &alternate_lmbs,
1927 )
1928 .expect("the non-default parent LMB must support the same proper two-loop subspace");
1929 assert_eq!(proper_alternate_subspace.loopcount(), 2);
1930 let affine_external_momenta = ExternalFourMomenta::from_iter([
1931 FourMomentum::from_args(F(5.0), F(1.0), F(2.0), F(3.0)),
1932 FourMomentum::from_args(F(-5.0), F(-1.0), F(-2.0), F(-3.0)),
1933 ]);
1934 let affine_external_spatial = affine_external_momenta
1935 .iter()
1936 .map(|momentum| momentum.spatial)
1937 .collect();
1938 let unprojected_affine_center = identity_frame_center.lmb_transform(
1939 &graph.loop_momentum_basis,
1940 proper_alternate_subspace.get_lmb(&alternate_lmbs),
1941 &affine_external_spatial,
1942 );
1943 assert!(
1944 unprojected_affine_center
1945 .iter_enumerated()
1946 .any(|(loop_index, momentum)| {
1947 !proper_alternate_subspace.contains_loop_index(loop_index)
1948 && momentum.norm_squared() > F(0.0)
1949 }),
1950 "the fixture must exercise an affine LMB transform with a nonzero inactive component"
1951 );
1952 let mut expected_projected_affine_center = unprojected_affine_center.clone();
1953 for loop_index in (0..expected_projected_affine_center.len()).map(LoopIndex::from) {
1954 if !proper_alternate_subspace.contains_loop_index(loop_index) {
1955 expected_projected_affine_center[loop_index] =
1956 ThreeMomentum::new(F(0.0), F(0.0), F(0.0));
1957 }
1958 }
1959 let mut affine_forced_settings = RuntimeSettings::default();
1960 affine_forced_settings
1961 .subtraction
1962 .overlap_settings
1963 .force_global_center = Some(forced_center_coordinates);
1964 let affine_overlap_input = OverlapInput {
1965 graph: &graph,
1966 settings: &affine_forced_settings,
1967 subspace: &proper_alternate_subspace,
1968 threshold_subspaces: None,
1969 lmbs: &alternate_lmbs,
1970 thresholds: &empty_thresholds,
1971 edge_masses: overlap_input.edge_masses.clone(),
1972 surface_kinematics: overlap_input.surface_kinematics,
1973 };
1974 let affine_overlap = find_maximal_overlap(
1975 &affine_overlap_input,
1976 &ti_vec![],
1977 &sampled_momenta,
1978 &affine_external_momenta,
1979 &Rotation::new(RotationMethod::Identity),
1980 )
1981 .unwrap();
1982 assert_eq!(
1983 affine_overlap.overlap_groups[0].center, expected_projected_affine_center,
1984 "an affine parent-LMB transform must not displace the fixed complement of a forced subspace center"
1985 );
1986
1987 let support_edge = [1, 2, 3, 4]
1988 .into_iter()
1989 .map(EdgeIndex::from)
1990 .find(|edge| !graph.loop_momentum_basis.loop_edges.contains(edge))
1991 .unwrap();
1992 let covariant_thresholds = vec![Esurface {
1993 energies: vec![
1994 graph.loop_momentum_basis.loop_edges[LoopIndex(0)],
1995 support_edge,
1996 ],
1997 external_shift: vec![(EdgeIndex::from(0), -1)],
1998 vertex_set: VertexSet::dummy(),
1999 }]
2000 .into();
2001 let covariant_overlap_input = OverlapInput {
2002 graph: &graph,
2003 settings: &settings,
2004 subspace: &subspace,
2005 threshold_subspaces: None,
2006 lmbs: &all_lmbs,
2007 thresholds: &covariant_thresholds,
2008 edge_masses: overlap_input.edge_masses.clone(),
2009 surface_kinematics: overlap_input.surface_kinematics,
2010 };
2011 let covariant_loop_momenta = LoopMomenta::from_iter([
2012 ThreeMomentum::new(F(0.3), F(-0.4), F(0.5)),
2013 ThreeMomentum::new(F(-0.2), F(0.7), F(0.1)),
2014 ThreeMomentum::new(F(0.6), F(-0.8), F(0.9)),
2015 ]);
2016 let covariant_externals = ExternalFourMomenta::from_iter([
2017 FourMomentum::from_args(F(20.0), F(6.0), F(8.0), F(0.0)),
2018 FourMomentum::from_args(F(-20.0), F(-6.0), F(-8.0), F(0.0)),
2019 ]);
2020 let existing = ti_vec![EsurfaceID::from(0)];
2021 let identity_center = find_center(
2022 &covariant_overlap_input,
2023 &[ExistingEsurfaceId::from(0)],
2024 &existing,
2025 &covariant_loop_momenta,
2026 &covariant_externals,
2027 false,
2028 )
2029 .unwrap()
2030 .unwrap();
2031 assert!(check_global_center(
2032 &covariant_overlap_input,
2033 &existing,
2034 &identity_center,
2035 &covariant_loop_momenta,
2036 &covariant_externals,
2037 ));
2038
2039 for rotation_method in [
2040 RotationMethod::Identity,
2041 RotationMethod::Pi2X,
2042 RotationMethod::Pi2Y,
2043 RotationMethod::Pi2Z,
2044 RotationMethod::EulerAngles(0.1, 0.2, 0.3),
2045 ] {
2046 let stability_rotation = Rotation::new(rotation_method);
2047 let rotated_loop_momenta = covariant_loop_momenta.rotate(&stability_rotation);
2048 let rotated_externals = covariant_externals
2049 .iter()
2050 .map(|momentum| FourMomentum {
2051 temporal: momentum.temporal,
2052 spatial: momentum.spatial.rotate(&stability_rotation),
2053 })
2054 .collect();
2055 let rotated_center = find_center(
2056 &covariant_overlap_input,
2057 &[ExistingEsurfaceId::from(0)],
2058 &existing,
2059 &rotated_loop_momenta,
2060 &rotated_externals,
2061 false,
2062 )
2063 .unwrap()
2064 .unwrap();
2065 let expected_rotated_center = identity_center.rotate(&stability_rotation);
2066
2067 for (actual, expected) in rotated_center.iter().zip(expected_rotated_center.iter()) {
2068 for (actual_component, expected_component) in actual.into_iter().zip(expected) {
2069 assert!(
2070 (actual_component.0 - expected_component.0).abs() < 1.0e-8,
2071 "solver-derived center is not covariant under stability rotation {rotation_method}: actual={actual_component}, expected={expected_component}"
2072 );
2073 }
2074 }
2075 assert!(
2076 check_global_center(
2077 &covariant_overlap_input,
2078 &existing,
2079 &rotated_center,
2080 &rotated_loop_momenta,
2081 &rotated_externals,
2082 ),
2083 "solver-derived center is not strictly interior after stability rotation {rotation_method}",
2084 );
2085 }
2086 }
2087}