1#![allow(dead_code)]
2
3use crate::momentum::sample::{
4 ExternalFourMomenta, ExternalIndex, ExternalThreeMomenta, LoopIndex, LoopMomenta,
5};
6use crate::momentum::{FourMomentum, SignOrZero, ThreeMomentum};
7use crate::utils::{F, FloatLike, Length};
8use bincode::{BorrowDecode, Decode, Encode};
9use serde::{Deserialize, Serialize};
10use spenso::algebra::algebraic_traits::RefZero;
11use std::fmt::Display;
12use std::ops::{Add, AddAssign, Index, IndexMut, Neg, SubAssign};
13use symbolica::atom::{Atom, AtomOrView, FunctionBuilder, Symbol};
14use typed_index_collections::TiVec;
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash)]
17pub struct SignatureLike<T: From<usize>>(TiVec<T, SignOrZero>);
18pub type LoopSignature = SignatureLike<LoopIndex>;
19pub type ExternalSignature = SignatureLike<ExternalIndex>;
20
21impl<T: Encode + From<usize>> Encode for SignatureLike<T> {
23 fn encode<E: bincode::enc::Encoder>(
24 &self,
25 encoder: &mut E,
26 ) -> Result<(), bincode::error::EncodeError> {
27 self.0.raw.encode(encoder)
28 }
29}
30
31impl<'de, Context, T: Decode<Context> + From<usize>> BorrowDecode<'de, Context>
32 for SignatureLike<T>
33{
34 fn borrow_decode<D: bincode::de::Decoder>(
35 decoder: &mut D,
36 ) -> Result<Self, bincode::error::DecodeError> {
37 Ok(SignatureLike(Vec::decode(decoder)?.into()))
38 }
39}
40
41impl<Context, T: Decode<Context> + From<usize>> Decode<Context> for SignatureLike<T> {
42 fn decode<D: bincode::de::Decoder>(
43 decoder: &mut D,
44 ) -> Result<Self, bincode::error::DecodeError> {
45 Ok(SignatureLike(Vec::decode(decoder)?.into()))
46 }
47}
48
49#[derive(
50 Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash, Encode, Decode,
51)]
52pub struct LoopExtSignature {
53 pub internal: LoopSignature,
54 pub external: ExternalSignature,
55}
56
57impl LoopExtSignature {
58 pub(crate) fn swap_loops(&mut self, i: LoopIndex, j: LoopIndex) {
59 if !self.internal.is_empty() {
61 self.internal.0.swap(i, j);
62 }
63 }
64
65 pub(crate) fn put_loop_to_ext(&mut self, l: LoopIndex) {
66 let a = self.internal.0.remove(l);
67 self.external.0.push(a);
68 }
69 pub(crate) fn swap_external(&mut self, i: ExternalIndex, j: ExternalIndex) {
70 self.external.0.swap(i, j);
71 }
72
73 pub(crate) fn loop_atom<'a, I>(
74 &self,
75 mom_symbol: Symbol,
76 additional_args: &'a [I],
77 id_map: impl Fn(LoopIndex) -> Atom,
78 ) -> Atom
79 where
80 &'a I: Into<AtomOrView<'a>>,
81 {
82 self.internal.atom(mom_symbol, additional_args, id_map)
83 }
84
85 pub(crate) fn ext_atom<'a, I>(
86 &self,
87 mom_symbol: Symbol,
88 additional_args: &'a [I],
89 id_map: impl Fn(ExternalIndex) -> Atom,
90 ) -> Atom
91 where
92 &'a I: Into<AtomOrView<'a>>,
93 {
94 self.external.atom(mom_symbol, additional_args, id_map)
95 }
96
97 pub(crate) fn equality_up_to_sign(&self, other: &Self) -> bool {
98 self == other
99 || (self.internal.len() == other.internal.len()
100 && self.external.len() == other.external.len()
101 && self
102 .internal
103 .iter()
104 .zip(other.internal.iter())
105 .all(|(a, b)| *a == -*b)
106 && self
107 .external
108 .iter()
109 .zip(other.external.iter())
110 .all(|(a, b)| *a == -*b))
111 }
112
113 pub(crate) fn spatial_equality_up_to_sign<T: FloatLike>(
116 &self,
117 other: &Self,
118 externals: &ExternalThreeMomenta<F<T>>,
119 ) -> eyre::Result<bool> {
120 use crate::integrands::process::sampling::maps::SamplingEvaluationError;
121 use rug::{Float, float::Round};
122 const PRECISION: u32 = 2048;
123 if self.internal.len() != other.internal.len()
124 || self.external.len() != other.external.len()
125 || self.external.len() != externals.len()
126 {
127 return Err(eyre::eyre!(
128 "spatial route comparison requires equal routing dimensions and the complete external frame"
129 ));
130 }
131 if externals
132 .iter()
133 .flat_map(|p| [&p.px, &p.py, &p.pz])
134 .any(|value| !value.0.is_finite())
135 {
136 return Err(SamplingEvaluationError::Unrepresentable {
137 operation: "spatial route comparison",
138 detail: "nonfinite original external component".into(),
139 }
140 .into());
141 }
142 if self.equality_up_to_sign(other) {
143 return Ok(true);
144 }
145 let mut uncertain = false;
146 for sign in [1, -1] {
147 if !self
148 .internal
149 .iter()
150 .zip(other.internal.iter())
151 .all(|(a, b)| *a == if sign == 1 { *b } else { -*b })
152 {
153 continue;
154 }
155 let coefficients = self
157 .external
158 .to_momtrop_format()
159 .into_iter()
160 .zip(other.external.to_momtrop_format())
161 .map(|(a, b)| a - sign * b)
162 .collect::<Vec<_>>();
163 let mut sums: [[Float; 2]; 3] = std::array::from_fn(|_| {
164 [Float::with_val(PRECISION, 0), Float::with_val(PRECISION, 0)]
165 });
166 for (&coefficient, momentum) in coefficients.iter().zip(externals) {
167 if coefficient == 0 {
168 continue;
169 }
170 for (sum, component) in
171 sums.iter_mut()
172 .zip([&momentum.px, &momentum.py, &momentum.pz])
173 {
174 let (mut lo, mut hi) = component.0.mpfr_enclosure(PRECISION);
175 if coefficient < 0 {
176 std::mem::swap(&mut lo, &mut hi);
177 }
178 let lo = Float::with_val_round(PRECISION, &lo * coefficient, Round::Down).0;
179 let hi = Float::with_val_round(PRECISION, &hi * coefficient, Round::Up).0;
180 sum[0] = Float::with_val_round(PRECISION, &sum[0] + &lo, Round::Down).0;
181 sum[1] = Float::with_val_round(PRECISION, &sum[1] + &hi, Round::Up).0;
182 }
183 }
184 if sums.iter().flatten().any(|value| !value.is_finite()) {
185 return Err(SamplingEvaluationError::Unrepresentable {
186 operation: "spatial route comparison",
187 detail: "nonfinite directed external sum".into(),
188 }
189 .into());
190 }
191 if sums.iter().any(|[lo, hi]| lo > &0 || hi < &0) {
192 continue;
193 }
194 if sums.iter().all(|[lo, hi]| lo == &0 && hi == &0) {
195 return Ok(true);
196 }
197 uncertain = true;
198 }
199 if uncertain {
200 return Err(SamplingEvaluationError::UncertainGeometry {
201 detail: format!("spatial routing equality is unresolved at {PRECISION}-bit directed precision: {self:?} versus {other:?}"),
202 }
203 .into());
204 }
205 Ok(false)
206 }
207}
208
209impl From<(Vec<isize>, Vec<isize>)> for LoopExtSignature {
210 fn from(value: (Vec<isize>, Vec<isize>)) -> Self {
211 Self {
212 internal: LoopSignature::from_iter(value.0),
213 external: ExternalSignature::from_iter(value.1),
214 }
215 }
216}
217
218impl<T> Index<T> for SignatureLike<T>
219where
220 usize: From<T>,
221 T: From<usize>,
222{
223 type Output = SignOrZero;
224
225 fn index(&self, index: T) -> &Self::Output {
226 &self.0[index]
227 }
228}
229
230impl<T> IndexMut<T> for SignatureLike<T>
231where
232 usize: From<T>,
233 T: From<usize>,
234{
235 fn index_mut(&mut self, index: T) -> &mut Self::Output {
236 &mut self.0[index]
237 }
238}
239
240impl<T> Display for SignatureLike<T>
241where
242 T: From<usize>,
243{
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 for sign in &self.0 {
246 write!(f, "{}", sign)?;
247 }
248 Ok(())
249 }
250}
251
252impl<T: From<usize>> Default for SignatureLike<T> {
253 fn default() -> Self {
254 SignatureLike(TiVec::new())
255 }
256}
257
258impl<T> FromIterator<SignOrZero> for SignatureLike<T>
259where
260 T: From<usize>,
261{
262 fn from_iter<I: IntoIterator<Item = SignOrZero>>(iter: I) -> Self {
263 SignatureLike(iter.into_iter().collect())
264 }
265}
266
267impl<T> FromIterator<i8> for SignatureLike<T>
268where
269 T: From<usize>,
270{
271 fn from_iter<I: IntoIterator<Item = i8>>(iter: I) -> Self {
272 SignatureLike(
273 iter.into_iter()
274 .map(|x| match x {
275 0 => SignOrZero::Zero,
276 1 => SignOrZero::Plus,
277 -1 => SignOrZero::Minus,
278 _ => panic!("Invalid value for Signature"),
279 })
280 .collect(),
281 )
282 }
283}
284
285impl<T> FromIterator<isize> for SignatureLike<T>
286where
287 T: From<usize>,
288{
289 fn from_iter<I: IntoIterator<Item = isize>>(iter: I) -> Self {
290 SignatureLike(
291 iter.into_iter()
292 .map(|x| match x {
293 0 => SignOrZero::Zero,
294 1 => SignOrZero::Plus,
295 -1 => SignOrZero::Minus,
296 _ => panic!("Invalid value for Signature"),
297 })
298 .collect(),
299 )
300 }
301}
302
303impl<T> From<Vec<i8>> for SignatureLike<T>
304where
305 T: From<usize>,
306{
307 fn from(value: Vec<i8>) -> Self {
308 SignatureLike::from_iter(value)
309 }
310}
311
312impl<T> IntoIterator for SignatureLike<T>
313where
314 T: From<usize>,
315{
316 type Item = SignOrZero;
317 type IntoIter = std::vec::IntoIter<Self::Item>;
318
319 fn into_iter(self) -> Self::IntoIter {
320 self.0.into_iter()
321 }
322}
323
324impl<'a, T> IntoIterator for &'a SignatureLike<T>
325where
326 T: From<usize>,
327{
328 type Item = SignOrZero;
329 type IntoIter = std::iter::Copied<std::slice::Iter<'a, Self::Item>>;
330
331 fn into_iter(self) -> Self::IntoIter {
332 self.0.iter().copied()
333 }
334}
335
336impl<T> AddAssign<()> for SignatureLike<T>
337where
338 T: From<usize> + Copy,
339 usize: From<T>,
340{
341 fn add_assign(&mut self, _rhs: ()) {
342 self.0.push(SignOrZero::Plus);
343 }
344}
345
346impl<T> SignatureLike<T>
347where
348 T: From<usize> + Copy,
349 usize: From<T>,
350{
351 pub(crate) fn atom<'a, I>(
352 &self,
353 mom_symbol: Symbol,
354 additional_args: &'a [I],
355 id_map: impl Fn(T) -> Atom,
356 ) -> Atom
357 where
358 &'a I: Into<AtomOrView<'a>>,
359 {
360 let mut rep = Atom::Zero;
361 for (l, s) in self.iter_enumerated() {
362 let mom = FunctionBuilder::new(mom_symbol)
363 .add_arg(id_map(l))
364 .add_args(additional_args)
365 .finish();
366 rep += *s * mom;
369 }
370
371 rep
374 }
375
376 pub(crate) fn iter_enumerated(&self) -> impl Iterator<Item = (T, &SignOrZero)> {
377 self.0.iter_enumerated()
378 }
379 pub(crate) fn validate_basis<B>(&self, basis: &[B]) -> bool {
380 self.len() == basis.len()
381 }
382
383 pub(crate) fn sum(&mut self, other: &Self) {
384 for (i, sign) in other.iter_enumerated() {
385 match (self[i], sign) {
386 (SignOrZero::Zero, SignOrZero::Zero) => self.0[i] = SignOrZero::Zero,
387 (SignOrZero::Zero, SignOrZero::Plus) => self.0[i] = SignOrZero::Plus,
388 (SignOrZero::Zero, SignOrZero::Minus) => self.0[i] = SignOrZero::Minus,
389 (SignOrZero::Plus, SignOrZero::Zero) => self.0[i] = SignOrZero::Plus,
390 (SignOrZero::Plus, SignOrZero::Plus) => panic!("cannot add two positive signs"),
391 (SignOrZero::Plus, SignOrZero::Minus) => self.0[i] = SignOrZero::Zero,
392 (SignOrZero::Minus, SignOrZero::Zero) => self.0[i] = SignOrZero::Minus,
393 (SignOrZero::Minus, SignOrZero::Plus) => self.0[i] = SignOrZero::Zero,
394 (SignOrZero::Minus, SignOrZero::Minus) => panic!("cannot add two negative signs"),
395 }
396 }
397 }
398
399 pub(crate) fn panic_validate_basis<B>(&self, basis: &[B]) {
400 if !self.validate_basis(basis) {
401 panic!(
402 "Invalid basis for Signature, expected length {}, got length {}",
403 self.len(),
404 basis.len()
405 );
406 }
407 }
408
409 pub(crate) fn to_momtrop_format(&self) -> Vec<isize> {
410 self.0
411 .iter()
412 .map(|x| match x {
413 SignOrZero::Zero => 0,
414 SignOrZero::Plus => 1,
415 SignOrZero::Minus => -1,
416 })
417 .collect()
418 }
419 pub(crate) fn len(&self) -> usize {
420 self.0.len()
421 }
422
423 pub fn iter(&'_ self) -> std::slice::Iter<'_, SignOrZero> {
424 self.0.iter()
425 }
426
427 pub(crate) fn is_empty(&self) -> bool {
428 self.0.is_empty()
429 }
430
431 pub(crate) fn apply<B>(&self, basis: &[B]) -> B
432 where
433 B: RefZero + Clone + Neg<Output = B> + AddAssign<B>,
434 {
435 let mut result = basis[0].ref_zero();
437 for (&sign, t) in self.0.iter().zip(basis.iter().cloned()) {
438 result += sign * t;
439 }
440 result
441 }
442
443 pub(crate) fn try_apply<B>(&self, basis: &[B]) -> Option<B>
444 where
445 B: Clone + Neg<Output = B> + Add<B, Output = B>,
446 {
447 self.0
448 .iter()
449 .zip(basis.iter().cloned())
450 .filter_map(|(sign, t)| match sign {
451 SignOrZero::Zero => None,
452 SignOrZero::Plus => Some(t),
453 SignOrZero::Minus => Some(-t),
454 })
455 .reduce(|sum, t| sum + t)
456 }
457
458 pub(crate) fn apply_typed<O, I, V>(&self, basis: &V) -> O
459 where
460 V: Index<I, Output = O>,
461 O: RefZero + Neg<Output = O> + AddAssign<O> + Clone,
462 I: From<usize>,
463 usize: From<I>,
464 {
465 let mut result = basis[I::from(0)].ref_zero();
466 for (&sign, i) in self.0.iter().zip(0..) {
467 result += sign * basis[I::from(i)].clone();
468 }
469
470 result
471 }
472
473 pub(crate) fn apply_iter<I, O>(&self, basis: I) -> Option<O>
474 where
475 I: IntoIterator,
476 I::Item: RefZero<O>,
477 O: Clone + SubAssign<I::Item> + AddAssign<I::Item>,
478 {
479 let mut basis_iter = basis.into_iter();
480 let mut signature_iter = self.into_iter();
481
482 while let (Some(sign), Some(item)) = (signature_iter.next(), basis_iter.next()) {
483 if sign.is_sign() {
484 let mut result = item.ref_zero();
486 match sign {
487 SignOrZero::Zero => {
488 panic!("unreachable");
489 }
491 SignOrZero::Plus => {
492 result += item;
493 }
494 SignOrZero::Minus => {
495 result -= item;
496 }
497 }
498
499 while let (Some(sign), Some(item)) = (signature_iter.next(), basis_iter.next()) {
501 match sign {
502 SignOrZero::Zero => {}
503 SignOrZero::Plus => {
504 result += item;
505 }
506 SignOrZero::Minus => {
507 result -= item;
508 }
509 }
510 }
511
512 return Some(result);
513 }
514 }
515
516 None
518 }
519
520 pub(crate) fn label_with(&self, label: &str) -> String {
521 let mut result = String::new();
522 let mut first = true;
523 for (i, sign) in self.0.iter().enumerate() {
524 if !first {
525 result.push_str(&sign.to_string());
526 } else {
527 first = false;
528 }
529 if sign.is_sign() {
530 result.push_str(&format!("{}_{}", label, i));
531 }
532 }
533 result
534 }
535
536 pub(crate) fn first_abs(&self) -> Self {
540 let sign = self.iter().find(|x| x.is_sign());
541
542 if let Some(sign) = sign {
543 if sign.is_positive() {
544 self.clone()
545 } else {
546 self.iter().map(|x| -*x).collect()
547 }
548 } else {
549 self.clone()
550 }
551 }
552
553 pub(crate) fn pop(&mut self) -> Option<SignOrZero> {
554 self.0.pop()
555 }
556}
557
558#[test]
559fn test_signature() {
560 use crate::momentum::FourMomentum;
561 let sig = LoopSignature::from_iter(vec![SignOrZero::Plus, SignOrZero::Minus]);
562 let basis: Vec<i32> = vec![1, 2];
563 assert_eq!(sig.apply(&basis), 1 - 2);
564 assert_eq!(sig.apply_iter(basis.iter()), Some(-1));
565
566 let basis: [FourMomentum<i32>; 4] = [
567 FourMomentum::from_args(1, 1, 0, 0),
568 FourMomentum::from_args(1, 0, 1, 0),
569 FourMomentum::from_args(1, 0, 0, 1),
570 FourMomentum::from_args(1, 1, 1, 1),
571 ];
572
573 let sig = LoopSignature::from_iter(vec![
574 SignOrZero::Plus,
575 SignOrZero::Minus,
576 SignOrZero::Zero,
577 SignOrZero::Plus,
578 ]);
579
580 assert_eq!(sig.apply(&basis), FourMomentum::from_args(1, 2, 0, 1));
581 let sig = ExternalSignature::from_iter(vec![
582 SignOrZero::Zero,
583 SignOrZero::Zero,
584 SignOrZero::Zero,
585 SignOrZero::Zero,
586 ]);
587 assert_eq!(sig.apply_iter(basis.iter()), None);
588 let sig = ExternalSignature::from_iter(vec![
589 SignOrZero::Zero,
590 SignOrZero::Zero,
591 SignOrZero::Zero,
592 SignOrZero::Minus,
593 ]);
594 assert_eq!(sig.apply_iter(basis.iter()), Some(-basis[3]));
595}
596
597impl LoopExtSignature {
598 pub(crate) fn compute_momentum_untyped<'a, 'b: 'a, T>(
599 &self,
600 loop_moms: &'a [T],
601 external_moms: &'b [T],
602 ) -> T
603 where
604 T: RefZero + Clone + Neg<Output = T> + AddAssign<T>,
605 {
606 if loop_moms.is_empty() {
607 return self.external.apply(external_moms);
608 }
609 if external_moms.is_empty() {
610 return self.internal.apply(loop_moms);
611 }
612 let mut res = self.internal.apply(loop_moms);
613 res += self.external.apply(external_moms);
614 res
615 }
616
617 pub(crate) fn try_compute_momentum<'a, 'b: 'a, T>(
618 &self,
619 loop_moms: &'a [T],
620 external_moms: &'b [T],
621 ) -> Option<T>
622 where
623 T: Clone + Neg<Output = T> + Add<T, Output = T>,
624 {
625 let loop_part = self.internal.try_apply(loop_moms);
626 let external_part = self.external.try_apply(external_moms);
627
628 match (loop_part, external_part) {
629 (Some(l), Some(e)) => Some(l + e),
630 (Some(l), None) => Some(l),
631 (None, Some(e)) => Some(e),
632 (None, None) => None,
633 }
634 }
635
636 pub(crate) fn compute_momentum<L, E, M>(&self, loop_momenta: &L, external_momenta: &E) -> M
637 where
638 M: RefZero + Clone + Neg<Output = M> + AddAssign<M>,
639 L: Index<LoopIndex, Output = M> + Length,
640 E: Index<ExternalIndex, Output = M> + Length,
641 {
642 if loop_momenta.is_empty() {
643 return self.external.apply_typed(external_momenta);
644 }
645
646 if external_momenta.is_empty() {
647 return self.internal.apply_typed(loop_momenta);
648 }
649
650 let mut res = self.internal.apply_typed(loop_momenta);
651 res += self.external.apply_typed(external_momenta);
652 res
653 }
654
655 pub(crate) fn to_momtrop_format(&self) -> (Vec<isize>, Vec<isize>) {
656 (
657 self.internal.to_momtrop_format(),
658 self.external.to_momtrop_format(),
659 )
660 }
661
662 pub(crate) fn format_momentum(&self) -> String {
664 let mut res = String::new();
665 let mut first = true;
666
667 for (i, sign) in (&self.internal).into_iter().enumerate() {
668 if !first {
669 res.push_str(&sign.to_string());
670 } else {
671 first = false;
672 }
673 if sign.is_sign() {
674 res.push_str(&format!("k_{}", i));
675 }
676 }
677
678 for (i, sign) in (&self.external).into_iter().enumerate() {
679 if !first {
680 res.push_str(&sign.to_string());
681 } else {
682 first = false;
683 }
684 if sign.is_sign() {
685 res.push_str(&format!("l_{}", i));
686 }
687 }
688 res
689 }
690
691 #[allow(unused)]
692 pub(crate) fn compute_four_momentum_from_three<T: FloatLike>(
693 &self,
694 loop_moms: &LoopMomenta<F<T>>,
695 external_moms: &ExternalFourMomenta<F<T>>,
696 ) -> FourMomentum<F<T>> {
697 let loop_moms = loop_moms
698 .iter()
699 .map(|m| m.clone().into_on_shell_four_momentum(None))
700 .collect::<TiVec<LoopIndex, _>>();
701
702 self.compute_momentum(&loop_moms, external_moms)
703 }
704
705 pub(crate) fn compute_three_momentum_from_four<T: FloatLike>(
706 &self,
707 loop_moms: &LoopMomenta<F<T>>,
708 external_moms: &ExternalFourMomenta<F<T>>,
709 ) -> ThreeMomentum<F<T>> {
710 let external_moms: ExternalThreeMomenta<F<T>> =
711 external_moms.iter().map(|m| m.spatial.clone()).collect();
712 self.compute_momentum(loop_moms, &external_moms)
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::LoopExtSignature;
719
720 #[test]
721 fn affine_signature_equality_uses_one_global_sign() {
722 let a = LoopExtSignature::from((vec![1], vec![1]));
723 let independent_reversal = LoopExtSignature::from((vec![-1], vec![1]));
724 assert!(!a.equality_up_to_sign(&independent_reversal));
725 assert!(a.equality_up_to_sign(&LoopExtSignature::from((vec![-1], vec![-1]))));
726 for (internal, external) in [
727 (vec![0, 1], vec![0, 0]),
728 (vec![0, 0], vec![-1, 1]),
729 (vec![0, 0], vec![0, 0]),
730 (vec![], vec![]),
731 ] {
732 let reversed = LoopExtSignature::from((
733 internal.iter().map(|value| -*value).collect(),
734 external.iter().map(|value| -*value).collect(),
735 ));
736 assert!(LoopExtSignature::from((internal, external)).equality_up_to_sign(&reversed));
737 }
738 assert!(
739 !LoopExtSignature::from((vec![1], vec![]))
740 .equality_up_to_sign(&LoopExtSignature::from((vec![], vec![1])))
741 );
742 assert!(!a.equality_up_to_sign(&LoopExtSignature::from((vec![1, 0], vec![1]))));
743 }
744
745 #[test]
746 fn spatial_routing_equality_certifies_native_external_frames() {
747 use crate::{
748 integrands::process::sampling::maps::SamplingEvaluationError,
749 momentum::{ThreeMomentum, sample::ExternalThreeMomenta},
750 utils::{ArbPrec, F, FloatLike, QuadFloat},
751 };
752 fn check<T: FloatLike>() {
753 let one = F::<T>::default().one();
754 let zero = one.zero();
755 let left = LoopExtSignature::from((vec![1], vec![0, 0]));
756 let right = LoopExtSignature::from((vec![1], vec![1, 1]));
757 let beam = ThreeMomentum::new(one.from_i64(2), zero.clone(), one.from_i64(3));
758 let cm = ExternalThreeMomenta::from_iter([beam.clone(), -beam]);
759 assert!(!left.equality_up_to_sign(&right));
760 assert!(left.spatial_equality_up_to_sign(&right, &cm).unwrap());
761 let reversed = LoopExtSignature::from((vec![-1], vec![-1, -1]));
762 assert!(left.spatial_equality_up_to_sign(&reversed, &cm).unwrap());
763
764 for displacement in [one.clone(), one.epsilon().square()] {
767 let mut shifted = cm.clone();
768 for momentum in &mut shifted {
769 momentum.py += &displacement;
771 }
772 assert!(!left.spatial_equality_up_to_sign(&right, &shifted).unwrap());
773 }
774 let a = LoopExtSignature::from((vec![1], vec![1, 0]));
775 let wrong_sign = LoopExtSignature::from((vec![-1], vec![1, 0]));
776 assert!(!a.spatial_equality_up_to_sign(&wrong_sign, &cm).unwrap());
777 let doubled = LoopExtSignature::from((vec![1], vec![-1, 0]));
779 assert!(!a.spatial_equality_up_to_sign(&doubled, &cm).unwrap());
780
781 let big = &one / one.epsilon().square();
782 let medium = big.sqrt();
783 let values = [big.clone(), medium.clone(), one.clone(), -medium, -big];
784 assert_eq!(
785 values.iter().fold(zero.clone(), |sum, value| sum + value),
786 zero
787 );
788 let cancelled = ExternalThreeMomenta::from_iter(
789 values.map(|x| ThreeMomentum::new(x, zero.clone(), zero.clone())),
790 );
791 let empty = LoopExtSignature::from((vec![1], vec![0; 5]));
792 let sum = LoopExtSignature::from((vec![1], vec![1; 5]));
793 assert!(!empty.spatial_equality_up_to_sign(&sum, &cancelled).unwrap());
794
795 let huge = F(T::from_f64_exact_binary(2.0_f64.powi(1023)));
798 let tiny = F(T::from_f64_exact_binary(f64::from_bits(1)));
799 let unresolved = ExternalThreeMomenta::from_iter(
800 [huge.clone(), tiny, -huge]
801 .map(|x| ThreeMomentum::new(x, zero.clone(), zero.clone())),
802 );
803 let empty = LoopExtSignature::from((vec![1], vec![0; 3]));
804 let sum = LoopExtSignature::from((vec![1], vec![1; 3]));
805 let error = empty
806 .spatial_equality_up_to_sign(&sum, &unresolved)
807 .unwrap_err();
808 assert!(matches!(
809 error.downcast_ref::<SamplingEvaluationError>(),
810 Some(SamplingEvaluationError::UncertainGeometry { .. })
811 ));
812
813 let mut nonfinite = cm.clone();
814 nonfinite.iter_mut().next().unwrap().px = F(T::from_f64_exact_binary(f64::INFINITY));
815 let error = left
817 .spatial_equality_up_to_sign(&left, &nonfinite)
818 .unwrap_err();
819 assert!(matches!(
820 error.downcast_ref::<SamplingEvaluationError>(),
821 Some(SamplingEvaluationError::Unrepresentable { .. })
822 ));
823 assert!(
824 left.spatial_equality_up_to_sign(&right, &ExternalThreeMomenta::<F<T>>::default())
825 .is_err()
826 );
827 }
828 check::<f64>();
829 check::<QuadFloat>();
830 check::<ArbPrec>();
831 }
832
833 #[test]
834 fn raised_edge_comparison_preserves_complete_affine_routing() {
835 use crate::{
836 dot,
837 graph::{Graph, parse::from_dot::IntoGraph},
838 initialisation::test_initialise,
839 };
840 use linnet::half_edge::involution::EdgeIndex;
841 test_initialise().unwrap();
842 let graph: Graph = dot!(digraph raised_affine_signature {
843 ext [style=invis]
844 edge [num=1 mass=0]
845 node [num=1]
846 ext -> a [id=0]
847 a -> b [id=1]
848 b -> c [id=2]
849 c -> a [id=3]
850 ext -> c [id=4]
851 })
852 .unwrap();
853 let groups = graph.get_raised_edge_groups();
854 assert!(groups.iter().any(|group| group.len() >= 2));
855 for group in groups {
856 for pair in group.windows(2) {
857 assert!(graph.loop_momentum_basis.edges_are_raised(pair[0], pair[1]));
858 }
859 }
860 let mut basis = graph.loop_momentum_basis.clone();
863 basis.edge_signatures[EdgeIndex(1)] = LoopExtSignature::from((vec![1], vec![1, 0]));
864 basis.edge_signatures[EdgeIndex(2)] = LoopExtSignature::from((vec![-1], vec![1, 0]));
865 assert!(!basis.edges_are_raised(EdgeIndex(1), EdgeIndex(2)));
866 basis.edge_signatures[EdgeIndex(2)] = LoopExtSignature::from((vec![-1], vec![-1, 0]));
867 assert!(basis.edges_are_raised(EdgeIndex(1), EdgeIndex(2)));
868 }
869}