Skip to main content

gammalooprs/utils/
mod.rs

1use crate::integrands::process::GenericEvaluatorFloat;
2use crate::model::Model;
3use crate::momentum::sample::{
4    ExternalFourMomenta, ExternalIndex, ExternalThreeMomenta, LoopMomenta, SubspaceData,
5};
6use crate::momentum::signature::{ExternalSignature, LoopSignature};
7use crate::momentum::{FourMomentum, ThreeMomentum};
8use crate::numerator::aind::Aind;
9use crate::numerator::ufo::UFO;
10use crate::settings::runtime::ParameterizationSettings;
11use crate::settings::runtime::SamplingSettings;
12use crate::settings::runtime::kinematic::Externals;
13use crate::settings::runtime::{ParameterizationMapping, ParameterizationMode};
14use crate::utils::hyperdual_utils::new_constant;
15
16use bincode::{Decode, Encode};
17use colored::Colorize;
18use idenso::representations::initialize;
19use itertools::Itertools;
20use linnet::half_edge::involution::EdgeIndex;
21
22use rand::Rng;
23use ref_ops::{RefAdd, RefDiv, RefMul, RefNeg, RefRem, RefSub};
24use rug::float::{Constant, ParseFloatError};
25use rug::ops::{CompleteRound, Pow};
26use rug::{Assign, Float};
27use schemars::JsonSchema;
28use serde::{Deserialize, Deserializer, Serialize};
29use spenso::algebra::algebraic_traits::RefOne;
30use spenso::algebra::algebraic_traits::RefZero;
31use spenso::algebra::complex::Complex;
32use spenso::algebra::complex::R;
33use spenso::algebra::complex::SymbolicaComplex;
34use spenso::algebra::complex::symbolica_traits::ToFloat;
35use spenso::algebra::upgrading_arithmetic::TrySmallestUpgrade;
36use spenso::network::library::TensorLibraryData;
37use spenso::network::library::function_lib::{INBUILTS, Panic, PanicMissingConcrete, SymbolLib};
38use spenso::network::library::symbolic::{ExplicitKey, TensorLibrary};
39use spenso::network::parsing::ShadowedStructure;
40use spenso::structure::concrete_index::ExpandedIndex;
41use spenso::tensors::complex::RealOrComplexTensor;
42use spenso::tensors::data::StorageTensor;
43use spenso::tensors::parametric::to_param::ToAtom;
44use spenso::tensors::parametric::{MixedTensor, ParamTensor};
45use spenso_hep_lib::hep_lib_atom;
46use symbolica::{
47    domains::{
48        dual::HyperDual,
49        float::{FixedPrecision, Float as SymbolicaFloat, FloatLike as SymFloatLike},
50    },
51    prelude::*,
52};
53
54use statrs::function::gamma::{gamma, gamma_lr, gamma_ur};
55use std::cmp::{Ord, Ordering};
56use std::fmt::{Debug, Display};
57use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
58use std::str::FromStr;
59use std::sync::{LazyLock, OnceLock, RwLock};
60use std::time::Duration;
61
62use vakint::Vakint;
63// use symbolica_community::physics::tensors::library::{
64//     gamma5_weyl_data, gamma_data_weyl, proj_m_data_weyl, proj_p_data_weyl, SpensorLibrary,
65//     TensorNamespace,
66// };
67// use symbolica_community::physics::tensors::structure::SpensoStucture;
68// use symbolica::domains::Field;
69use crate::MAX_LOOP;
70use ::tracing::debug;
71use typed_index_collections::TiVec;
72
73pub const GIT_VERSION: &str = env!("CARGO_PKG_VERSION");
74pub const VERSION: &str = "0.0.1";
75pub mod fitting;
76pub mod hyperdual_utils;
77pub mod progress;
78pub mod representations;
79pub mod serde_utils;
80pub use progress::{long_running_progress_style, long_running_progress_style_with_eta_warmup};
81/// can be used instead of commenting out code
82#[macro_export]
83macro_rules! disable {
84    ($($tokens:tt)*) => {};
85}
86
87/// Shorthand for tagged debug events.
88///
89/// `#tag` expands to `tag = true` and the remaining tokens are forwarded to
90/// `tracing::debug!` unchanged.
91///
92/// GammaLoop's logging DSL treats tags as boolean event fields.
93///
94/// Emitting `#generation` makes directives such as `[{generation}]` eligible to
95/// match this event. A negated directive term such as `[{!inspect}]` matches
96/// events where the field is absent. If you need value-sensitive filtering, use
97/// an explicit field such as `inspect = false` or `mode = "summary"` at the
98/// callsite and a directive such as `[{inspect=false}]` or `[{mode=summary}]`.
99///
100/// Separately, the formatter supports sink-only field prefixes:
101/// - `file.<name>` is rendered only in the logfile/json sink
102/// - `display.<name>` is rendered only in the stderr/display sink
103///
104/// For example, `file.integrands = %expr` is useful for attaching large dumps
105/// to the logfile without printing them to stderr.
106///
107/// Pipeline-wide tag set:
108///
109/// Prefer a small stable vocabulary that cuts across the whole pipeline.
110///
111/// Primary phase tags:
112///
113/// | Tag | Meaning |
114/// | --- | --- |
115/// | `#generation` | Work that builds or prepares runtime objects before Monte Carlo integration starts |
116/// | `#integration` | Work performed while evaluating samples or managing the adaptive integration loop |
117/// | `#profile` | UV/IR/profile-style diagnostic scans and their analysis |
118/// | `#persistence` | Reading or writing saved state, manifests, checkpoints, and exported results |
119///
120/// Core domain tags:
121///
122/// | Tag | Meaning |
123/// | --- | --- |
124/// | `#uv` | Ultraviolet counterterms, UV forests, UV profiles, or UV-specific generation/evaluation logic |
125/// | `#ir` | Infrared subtraction, IR profiles, or IR-specific generation/evaluation logic |
126/// | `#subtraction` | Threshold, UV, or IR subtraction logic when the main concern is subtraction rather than the specific regime |
127/// | `#sampling` | Channel choice, discrete axes, parameterizations, or sample-generation choices |
128/// | `#stability` | Precision escalation, retries, instability diagnosis, and numerical safety checks |
129/// | `#observables` | Histogramming, event-to-observable projection, and observable snapshot production |
130/// | `#selectors` | Event-selection logic and selector decisions |
131/// | `#cache` | Cache lookup, reuse, invalidation, or cache-debug instrumentation |
132///
133/// Common work-unit tags:
134///
135/// | Tag | Meaning |
136/// | --- | --- |
137/// | `#graph` | The log is about one graph or graph-local data |
138/// | `#group` | The log is about a graph group or another explicitly grouped aggregate |
139/// | `#orientation` | The log is about orientation-dependent data or choosing/summing orientations |
140/// | `#channel` | The log is about multi-channeling or a particular channel |
141/// | `#cut` | The log is about a cut, raised cut, or cut-local computation |
142/// | `#event` | The log is about generated/retained event objects or event-group processing |
143/// | `#sample` | The log is about one evaluation sample, its coordinates, or per-sample intermediate values |
144/// | `#iteration` | The log is about one adaptive integration iteration or iteration-level summaries |
145/// | `#term` | The log is about one algebraic term, summand, or term-local contribution |
146///
147/// Common purpose tags:
148///
149/// | Tag | Meaning |
150/// | --- | --- |
151/// | `#solver` | The log is about root-finding, linear solves, fitting, or similar numerical solver state |
152/// | `#compile` | The log is about evaluator/code generation or compilation-oriented preparation |
153/// | `#inspect` | The log exposes detailed intermediate state for debugging, rather than a high-level milestone |
154/// | `#summary` | The log is a compact roll-up rather than a step-by-step trace |
155/// | `#dump` | The log emits large or structured payloads such as expressions, tables, or serialized views |
156///
157/// Tag boundaries:
158///
159/// - Prefer `#generation` over `#compile` when the message is a broad generation milestone.
160/// - Add `#compile` only when the message is specifically about evaluator construction or compilation-like work.
161/// - Prefer `#uv` or `#ir` when the regime matters to the user; use `#subtraction` when the subtraction mechanism is the real concern.
162/// - Prefer `#inspect` for verbose intermediate values that are mainly useful while debugging internals.
163/// - Add `#dump` when the payload is large enough that users may want to suppress it separately from lighter debug logs.
164/// - Do not use `#graph`, `#cut`, or `#sample` as substitutes for `#generation` or `#integration`; they refine phase tags rather than replace them.
165///
166/// Naming guidance:
167///
168/// - Use phase tags first. They answer "when in the pipeline did this happen?"
169/// - Use domain tags second. They answer "what subsystem is this about?"
170/// - Use work-unit tags third. They answer "what object is being worked on?"
171/// - Use purpose tags last for optional refinement.
172/// - Prefer tags that are meaningful across multiple modules.
173/// - Avoid tags that merely restate a function name.
174/// - Avoid tags tied to one internal representation unless that representation is a stable concept in user-facing debugging.
175///
176/// `parametric` is usually not a core pipeline tag. It is acceptable as a local refinement when needed, but should not be treated as part of the primary vocabulary unless it becomes a consistently useful cross-cutting concept.
177///
178/// Examples:
179/// - `debug_tags!(#generation, #uv, #graph, #dump; name = %graph.name, "generated graph payload");`
180/// - `debug_tags!(#integration, #summary; inspect = false, "iteration summary");`
181#[macro_export]
182macro_rules! debug_tags {
183    (@collect_tags [$($acc:tt)*] # $tag:ident, $($tail:tt)*) => {
184        $crate::debug_tags!(@collect_tags [$($acc)* $tag = true,] $($tail)*)
185    };
186    (@collect_tags [$($acc:tt)*] # $tag:ident; $($rest:tt)*) => {
187        $crate::debug_tags!(@collect_fields [$($acc)* $tag = true,] [] $($rest)*)
188    };
189    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] log.$name:ident = $value:expr, $($tail:tt)*) => {
190        $crate::debug_tags!(@collect_fields
191            [$($tags)*]
192            [$($fields)*
193                display.$name = %$crate::LogMessage::log_display(&$value),
194                file.$name = %$crate::LogMessage::log_file(&$value),
195            ]
196            $($tail)*
197        )
198    };
199    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $prefix:ident.$name:ident = %$value:expr, $($tail:tt)*) => {
200        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $prefix.$name = %$value,] $($tail)*)
201    };
202    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $prefix:ident.$name:ident = ?$value:expr, $($tail:tt)*) => {
203        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $prefix.$name = ?$value,] $($tail)*)
204    };
205    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $prefix:ident.$name:ident = $value:expr, $($tail:tt)*) => {
206        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $prefix.$name = $value,] $($tail)*)
207    };
208    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $name:ident = %$value:expr, $($tail:tt)*) => {
209        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $name = %$value,] $($tail)*)
210    };
211    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $name:ident = ?$value:expr, $($tail:tt)*) => {
212        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $name = ?$value,] $($tail)*)
213    };
214    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $name:ident = $value:expr, $($tail:tt)*) => {
215        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $name = $value,] $($tail)*)
216    };
217    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $name:ident, $($tail:tt)*) => {
218        $crate::debug_tags!(@collect_fields [$($tags)*] [$($fields)* $name,] $($tail)*)
219    };
220    (@collect_fields [$($tags:tt)*] [$($fields:tt)*] $($rest:tt)+) => {
221        tracing::debug!($($tags)* $($fields)* $($rest)+)
222    };
223    ($($input:tt)*) => {
224        $crate::debug_tags!(@collect_tags [] $($input)*)
225    };
226}
227
228#[allow(unused)]
229const MAX_DIMENSION: usize = MAX_LOOP * 3;
230
231pub(crate) const ESURFACE_SHIFT_THRESHOLD: f64 = 1.0e-13;
232/// Default dimensionless tolerance for E-surface invariant-mass-squared margins.
233pub const DEFAULT_ESURFACE_EXISTENCE_THRESHOLD: f64 = 1.0e-7;
234
235pub const LEFT: usize = 0;
236pub const RIGHT: usize = 1;
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
239pub enum Side {
240    LEFT = 0,
241    RIGHT = 1,
242}
243
244impl From<Side> for usize {
245    fn from(val: Side) -> Self {
246        val as usize
247    }
248}
249
250pub mod linnet_ext;
251pub mod symbolica_ext;
252pub mod tracing;
253
254#[cfg(test)]
255mod tests {
256    #[test]
257    fn debug_tags_macro_supports_tags_and_regular_fields() {
258        crate::debug_tags!(#integration, #summary;
259            inspect = false,
260            answer = 42,
261            "macro compiles with tag shorthands and regular fields"
262        );
263    }
264}
265
266pub trait FloatConvertFrom<U> {
267    fn convert_from(x: &U) -> Self;
268}
269
270//     fn convert_from(x: &f128::f128) -> f64 {
271//         (*x).to_f64().unwrap()
272//     }
273// }
274
275// impl FloatConvertFrom<f128::f128> for f128::f128 {
276//     fn convert_from(x: &f128::f128) -> f128::f128 {
277//         *x
278//     }
279// }
280
281// impl FloatConvertFrom<f64> for f128::f128 {
282//     fn convert_from(x: &f64) -> f128::f128 {
283//         f128::f128::from_f64(*x).unwrap()
284//     }
285// }
286
287#[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)]
288pub struct VarFloat<const N: u32> {
289    #[bincode(with_serde)]
290    float: rug::Float,
291}
292
293impl<const N: u32> Serialize for VarFloat<N> {
294    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
295    where
296        S: serde::Serializer,
297    {
298        let string = self.float.to_string();
299        string.serialize(serializer)
300    }
301}
302
303impl<'de, const N: u32> Deserialize<'de> for VarFloat<N> {
304    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
305    where
306        D: Deserializer<'de>,
307    {
308        let string: String = serde::Deserialize::deserialize(deserializer)?;
309        let val: Self = string
310            .parse()
311            .unwrap_or_else(|_| panic!("failed to parse arb prec from string: {}", string));
312
313        Ok(val)
314    }
315}
316
317impl<const N: u32> FromStr for VarFloat<N> {
318    type Err = ParseFloatError;
319    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
320        let float = rug::Float::parse(s)?;
321        Ok(Self {
322            float: rug::Float::with_val(N, float),
323        })
324    }
325}
326
327#[derive(
328    Debug,
329    Clone,
330    Copy,
331    Default,
332    PartialEq,
333    PartialOrd,
334    Eq,
335    Hash,
336    Serialize,
337    Deserialize,
338    Encode,
339    Decode,
340)]
341#[repr(transparent)]
342pub struct QuadFloat(DoubleFloat);
343
344impl std::fmt::Display for QuadFloat {
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        std::fmt::Display::fmt(&self.0, f)
347    }
348}
349
350impl std::fmt::LowerExp for QuadFloat {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        std::fmt::LowerExp::fmt(&self.0, f)
353    }
354}
355
356impl From<DoubleFloat> for QuadFloat {
357    fn from(value: DoubleFloat) -> Self {
358        Self(value)
359    }
360}
361
362impl From<QuadFloat> for DoubleFloat {
363    fn from(value: QuadFloat) -> Self {
364        value.0
365    }
366}
367
368impl From<&QuadFloat> for DoubleFloat {
369    fn from(value: &QuadFloat) -> Self {
370        value.0
371    }
372}
373
374impl From<SymbolicaFloat> for QuadFloat {
375    fn from(value: SymbolicaFloat) -> Self {
376        Self(value.to_double_float())
377    }
378}
379
380impl From<Float> for QuadFloat {
381    fn from(value: Float) -> Self {
382        Self(SymbolicaFloat::from(value).to_double_float())
383    }
384}
385
386impl From<&Rational> for QuadFloat {
387    fn from(value: &Rational) -> Self {
388        Self(value.into())
389    }
390}
391
392impl<const N: u32> Rem<&VarFloat<N>> for &VarFloat<N> {
393    type Output = VarFloat<N>;
394
395    fn rem(self, rhs: &VarFloat<N>) -> Self::Output {
396        (&self.float % &rhs.float).complete(N).into()
397    }
398}
399
400impl<const N: u32> From<Float> for VarFloat<N> {
401    fn from(x: Float) -> Self {
402        VarFloat {
403            float: rug::Float::with_val(N, x),
404        }
405    }
406}
407
408impl<const N: u32> From<&Rational> for VarFloat<N> {
409    fn from(x: &Rational) -> Self {
410        let n = x.numerator();
411
412        let n = match n {
413            Integer::Double(f) => Float::with_val(N, f),
414            Integer::Large(f) => Float::with_val(N, f),
415            Integer::Single(f) => Float::with_val(N, f),
416        };
417
418        let d = x.denominator();
419
420        let d = match d {
421            Integer::Double(f) => Float::with_val(N, f),
422            Integer::Large(f) => Float::with_val(N, f),
423            Integer::Single(f) => Float::with_val(N, f),
424        };
425
426        let r = n / d;
427
428        VarFloat {
429            float: rug::Float::with_val(N, r),
430        }
431    }
432}
433
434macro_rules! impl_quad_binary_op {
435    ($trait:ident, $fn:ident, $op:tt) => {
436        impl std::ops::$trait for QuadFloat {
437            type Output = Self;
438
439            fn $fn(self, rhs: Self) -> Self::Output {
440                Self(self.0 $op rhs.0)
441            }
442        }
443
444        impl std::ops::$trait<&QuadFloat> for QuadFloat {
445            type Output = Self;
446
447            fn $fn(self, rhs: &Self) -> Self::Output {
448                Self(self.0 $op rhs.0)
449            }
450        }
451
452        impl std::ops::$trait<QuadFloat> for &QuadFloat {
453            type Output = QuadFloat;
454
455            fn $fn(self, rhs: QuadFloat) -> Self::Output {
456                QuadFloat(self.0 $op rhs.0)
457            }
458        }
459
460        impl std::ops::$trait<&QuadFloat> for &QuadFloat {
461            type Output = QuadFloat;
462
463            fn $fn(self, rhs: &QuadFloat) -> Self::Output {
464                QuadFloat(self.0 $op rhs.0)
465            }
466        }
467    };
468}
469
470macro_rules! impl_quad_assign_op {
471    ($trait:ident, $fn:ident, $op:tt) => {
472        impl std::ops::$trait for QuadFloat {
473            fn $fn(&mut self, rhs: Self) {
474                self.0 $op rhs.0;
475            }
476        }
477
478        impl std::ops::$trait<&QuadFloat> for QuadFloat {
479            fn $fn(&mut self, rhs: &Self) {
480                self.0 $op rhs.0;
481            }
482        }
483    };
484}
485
486impl_quad_binary_op!(Add, add, +);
487impl_quad_binary_op!(Sub, sub, -);
488impl_quad_binary_op!(Mul, mul, *);
489impl_quad_binary_op!(Div, div, /);
490
491impl_quad_assign_op!(AddAssign, add_assign, +=);
492impl_quad_assign_op!(SubAssign, sub_assign, -=);
493impl_quad_assign_op!(MulAssign, mul_assign, *=);
494impl_quad_assign_op!(DivAssign, div_assign, /=);
495
496impl Neg for QuadFloat {
497    type Output = Self;
498
499    fn neg(self) -> Self::Output {
500        Self(-self.0)
501    }
502}
503
504impl Neg for &QuadFloat {
505    type Output = QuadFloat;
506
507    fn neg(self) -> Self::Output {
508        QuadFloat(-self.0)
509    }
510}
511
512impl Rem<&QuadFloat> for &QuadFloat {
513    type Output = QuadFloat;
514
515    fn rem(self, rhs: &QuadFloat) -> Self::Output {
516        self.truncating_remainder(rhs)
517    }
518}
519
520impl<const N: u32> std::ops::Mul for VarFloat<N> {
521    type Output = Self;
522
523    fn mul(self, rhs: Self) -> Self::Output {
524        (self.float * rhs.float).into()
525    }
526}
527
528impl<const N: u32> std::ops::Mul<&VarFloat<N>> for VarFloat<N> {
529    type Output = Self;
530
531    fn mul(self, rhs: &Self) -> Self::Output {
532        (self.float * &rhs.float).into()
533    }
534}
535
536impl<const N: u32> std::ops::Mul<VarFloat<N>> for &VarFloat<N> {
537    type Output = VarFloat<N>;
538
539    fn mul(self, rhs: VarFloat<N>) -> Self::Output {
540        (&self.float * &rhs.float).complete(N).into()
541    }
542}
543
544impl<const N: u32> std::ops::Mul<&VarFloat<N>> for &VarFloat<N> {
545    type Output = VarFloat<N>;
546
547    fn mul(self, rhs: &VarFloat<N>) -> Self::Output {
548        (&self.float * &rhs.float).complete(N).into()
549    }
550}
551
552impl<const N: u32> std::ops::MulAssign for VarFloat<N> {
553    fn mul_assign(&mut self, rhs: Self) {
554        self.float *= rhs.float;
555        self.float.set_prec(N);
556    }
557}
558
559impl<const N: u32> std::ops::MulAssign<&VarFloat<N>> for VarFloat<N> {
560    fn mul_assign(&mut self, rhs: &Self) {
561        self.float *= &rhs.float;
562        self.float.set_prec(N);
563    }
564}
565
566impl<const N: u32> std::ops::Add for VarFloat<N> {
567    type Output = Self;
568
569    fn add(self, rhs: Self) -> Self::Output {
570        (self.float + rhs.float).into()
571    }
572}
573
574impl<const N: u32> std::ops::Add<&VarFloat<N>> for VarFloat<N> {
575    type Output = Self;
576
577    fn add(self, rhs: &Self) -> Self::Output {
578        (self.float + &rhs.float).into()
579    }
580}
581
582impl<const N: u32> std::ops::Add<VarFloat<N>> for &VarFloat<N> {
583    type Output = VarFloat<N>;
584
585    fn add(self, rhs: VarFloat<N>) -> Self::Output {
586        (&self.float + &rhs.float).complete(N).into()
587    }
588}
589
590impl<const N: u32> std::ops::Add<&VarFloat<N>> for &VarFloat<N> {
591    type Output = VarFloat<N>;
592
593    fn add(self, rhs: &VarFloat<N>) -> Self::Output {
594        (&self.float + &rhs.float).complete(N).into()
595    }
596}
597
598impl<const N: u32> std::ops::AddAssign for VarFloat<N> {
599    fn add_assign(&mut self, rhs: Self) {
600        self.float += rhs.float;
601        self.float.set_prec(N);
602    }
603}
604
605impl<const N: u32> std::ops::AddAssign<&VarFloat<N>> for VarFloat<N> {
606    fn add_assign(&mut self, rhs: &Self) {
607        self.float += &rhs.float;
608        self.float.set_prec(N);
609    }
610}
611
612impl<const N: u32> std::ops::Sub for VarFloat<N> {
613    type Output = Self;
614
615    fn sub(self, rhs: Self) -> Self::Output {
616        (self.float - rhs.float).into()
617    }
618}
619
620impl<const N: u32> std::ops::Sub<&VarFloat<N>> for VarFloat<N> {
621    type Output = Self;
622
623    fn sub(self, rhs: &Self) -> Self::Output {
624        (self.float - &rhs.float).into()
625    }
626}
627
628impl<const N: u32> std::ops::Sub<VarFloat<N>> for &VarFloat<N> {
629    type Output = VarFloat<N>;
630
631    fn sub(self, rhs: VarFloat<N>) -> Self::Output {
632        (&self.float - &rhs.float).complete(N).into()
633    }
634}
635
636impl<const N: u32> std::ops::Sub<&VarFloat<N>> for &VarFloat<N> {
637    type Output = VarFloat<N>;
638
639    fn sub(self, rhs: &VarFloat<N>) -> Self::Output {
640        (&self.float - &rhs.float).complete(N).into()
641    }
642}
643
644impl<const N: u32> std::ops::SubAssign for VarFloat<N> {
645    fn sub_assign(&mut self, rhs: Self) {
646        self.float -= rhs.float;
647        self.float.set_prec(N);
648    }
649}
650
651impl<const N: u32> std::ops::SubAssign<&VarFloat<N>> for VarFloat<N> {
652    fn sub_assign(&mut self, rhs: &Self) {
653        self.float -= &rhs.float;
654        self.float.set_prec(N);
655    }
656}
657
658impl<const N: u32> Div for VarFloat<N> {
659    type Output = Self;
660
661    fn div(self, rhs: Self) -> Self::Output {
662        (self.float / rhs.float).into()
663    }
664}
665
666impl<const N: u32> Div<&VarFloat<N>> for VarFloat<N> {
667    type Output = Self;
668
669    fn div(self, rhs: &Self) -> Self::Output {
670        (self.float / &rhs.float).into()
671    }
672}
673
674impl<const N: u32> Div<VarFloat<N>> for &VarFloat<N> {
675    type Output = VarFloat<N>;
676
677    fn div(self, rhs: VarFloat<N>) -> Self::Output {
678        (&self.float / &rhs.float).complete(N).into()
679    }
680}
681
682impl<const N: u32> Div<&VarFloat<N>> for &VarFloat<N> {
683    type Output = VarFloat<N>;
684
685    fn div(self, rhs: &VarFloat<N>) -> Self::Output {
686        (&self.float / &rhs.float).complete(N).into()
687    }
688}
689
690impl<const N: u32> std::ops::DivAssign for VarFloat<N> {
691    fn div_assign(&mut self, rhs: Self) {
692        self.float /= rhs.float;
693        self.float.set_prec(N);
694    }
695}
696
697impl<const N: u32> std::ops::DivAssign<&VarFloat<N>> for VarFloat<N> {
698    fn div_assign(&mut self, rhs: &Self) {
699        self.float /= &rhs.float;
700        self.float.set_prec(N);
701    }
702}
703
704impl<const N: u32> std::ops::Neg for VarFloat<N> {
705    type Output = Self;
706
707    fn neg(self) -> Self::Output {
708        (-self.float).into()
709    }
710}
711
712impl<const N: u32> std::ops::Neg for &VarFloat<N> {
713    type Output = VarFloat<N>;
714
715    fn neg(self) -> Self::Output {
716        (-&self.float).complete(N).into()
717    }
718}
719
720impl<const N: u32> std::fmt::Display for VarFloat<N> {
721    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722        write!(f, "{}", self.float)
723    }
724}
725
726impl<const N: u32> std::fmt::LowerExp for VarFloat<N> {
727    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
728        write!(f, "{:e}", self.float)
729    }
730}
731
732impl<const N: u32> RefZero for VarFloat<N> {
733    fn ref_zero(&self) -> Self {
734        Self::new_zero()
735    }
736}
737
738impl<const N: u32> R for VarFloat<N> {}
739
740impl<const N: u32> RefOne for VarFloat<N> {
741    fn ref_one(&self) -> Self {
742        self.one()
743    }
744}
745
746impl<const N: u32> SymFloatLike for VarFloat<N> {
747    #[inline]
748    fn set_from(&mut self, other: &Self) {
749        self.float.assign(&other.float);
750    }
751
752    fn mul_add(&self, a: &Self, b: &Self) -> Self {
753        (&self.float * &a.float + &b.float).complete(N).into()
754    }
755
756    fn is_fully_zero(&self) -> bool {
757        self.float.is_zero()
758    }
759
760    // fn norm(&self) -> Self {
761    //     self.float.clone().abs().into()
762    // }
763
764    fn from_i64(&self, a: i64) -> Self {
765        VarFloat {
766            float: Float::with_val(N, a),
767        }
768    }
769
770    fn from_usize(&self, a: usize) -> Self {
771        VarFloat {
772            float: Float::with_val(N, a),
773        }
774    }
775
776    fn get_precision(&self) -> u32 {
777        N
778    }
779
780    fn zero(&self) -> Self {
781        Self::new_zero()
782    }
783
784    fn one(&self) -> Self {
785        self.from_i64(1)
786    }
787
788    fn new_zero() -> Self {
789        VarFloat {
790            float: Float::new(N),
791        }
792    }
793
794    fn inv(&self) -> Self {
795        self.float.clone().recip().into()
796    }
797
798    fn pow(&self, e: u64) -> Self {
799        rug::ops::Pow::pow(&self.float, e).complete(N).into()
800    }
801
802    fn sample_unit<R: Rng + ?Sized>(&self, rng: &mut R) -> Self {
803        let f: f64 = rng.random();
804        Float::with_val(N, f).into()
805    }
806
807    fn neg(&self) -> Self {
808        (-self.float.clone()).into()
809    }
810
811    fn get_epsilon(&self) -> f64 {
812        2.0f64.powi(1 - (N as i32))
813    }
814
815    fn fixed_precision(&self) -> bool {
816        true
817    }
818}
819
820impl<const N: u32> SingleFloat for VarFloat<N> {
821    fn is_finite(&self) -> bool {
822        self.float.is_finite()
823    }
824
825    fn is_one(&self) -> bool {
826        self.float == 1.
827    }
828
829    fn is_zero(&self) -> bool {
830        self.float == 0.
831    }
832
833    fn from_rational(&self, rat: &Rational) -> Self {
834        rat.into()
835    }
836}
837impl<const N: u32> RealLike for VarFloat<N> {
838    fn to_f64(&self) -> f64 {
839        self.float.to_f64()
840    }
841
842    fn round_to_nearest_integer(&self) -> Integer {
843        self.float.clone().round().to_integer().unwrap().into()
844    }
845
846    fn to_usize_clamped(&self) -> usize {
847        self.float
848            .to_integer()
849            .unwrap()
850            .to_usize()
851            .unwrap_or(usize::MAX)
852    }
853}
854
855impl<const N: u32> Real for VarFloat<N> {
856    fn i(&self) -> Option<Self> {
857        None
858    }
859
860    fn conj(&self) -> Self {
861        self.clone()
862    }
863
864    #[inline(always)]
865    fn pi(&self) -> Self {
866        Float::with_val(N, rug::float::Constant::Pi).into()
867    }
868
869    #[inline(always)]
870    fn e(&self) -> Self {
871        self.one().exp()
872    }
873
874    #[inline(always)]
875    fn euler(&self) -> Self {
876        Float::with_val(N, rug::float::Constant::Euler).into()
877    }
878
879    #[inline(always)]
880    fn phi(&self) -> Self {
881        (self.one() + self.from_i64(5).sqrt()) / Self::from_f64(2.)
882    }
883    fn atan2(&self, x: &Self) -> Self {
884        self.float.clone().atan2(&x.float).into()
885    }
886
887    fn powf(&self, e: &Self) -> Self {
888        self.float.clone().pow(e.float.clone()).into()
889    }
890
891    fn log(&self) -> Self {
892        self.float.ln_ref().complete(N).into()
893    }
894    fn norm(&self) -> Self {
895        self.float.clone().abs().into()
896    }
897
898    delegate! {
899        #[into]
900        to self.float.clone(){
901            fn sqrt(&self) -> Self;
902            fn exp(&self) -> Self;
903            fn sin(&self) -> Self;
904            fn cos(&self) -> Self;
905            fn tan(&self) -> Self;
906            fn asin(&self) -> Self;
907            fn acos(&self) -> Self;
908            fn sinh(&self) -> Self;
909            fn cosh(&self) -> Self;
910            fn tanh(&self) -> Self;
911            fn asinh(&self) -> Self;
912            fn acosh(&self) -> Self;
913            fn atanh(&self) -> Self;
914        }
915    }
916}
917
918impl<const N: u32> From<VarFloat<N>> for SymbolicaFloat {
919    fn from(value: VarFloat<N>) -> Self {
920        SymbolicaFloat::from(value.float)
921    }
922}
923
924impl<const N: u32> From<&VarFloat<N>> for SymbolicaFloat {
925    fn from(value: &VarFloat<N>) -> Self {
926        SymbolicaFloat::from(value.float.clone())
927    }
928}
929
930impl<const N: u32> From<SymbolicaFloat> for VarFloat<N> {
931    fn from(value: SymbolicaFloat) -> Self {
932        Self {
933            float: value.into_inner(),
934        }
935    }
936}
937
938impl From<QuadFloat> for SymbolicaFloat {
939    fn from(value: QuadFloat) -> Self {
940        value.0.into()
941    }
942}
943
944impl From<&QuadFloat> for SymbolicaFloat {
945    fn from(value: &QuadFloat) -> Self {
946        value.0.into()
947    }
948}
949
950impl RefZero for QuadFloat {
951    fn ref_zero(&self) -> Self {
952        Self::new_zero()
953    }
954}
955
956impl R for QuadFloat {}
957
958impl RefOne for QuadFloat {
959    fn ref_one(&self) -> Self {
960        self.one()
961    }
962}
963
964impl SymFloatLike for QuadFloat {
965    #[inline]
966    fn set_from(&mut self, other: &Self) {
967        self.0.set_from(&other.0);
968    }
969
970    fn mul_add(&self, a: &Self, b: &Self) -> Self {
971        Self(self.0.mul_add(&a.0, &b.0))
972    }
973
974    fn is_fully_zero(&self) -> bool {
975        self.0.is_fully_zero()
976    }
977
978    fn from_i64(&self, a: i64) -> Self {
979        Self(self.0.from_i64(a))
980    }
981
982    fn from_usize(&self, a: usize) -> Self {
983        Self(self.0.from_usize(a))
984    }
985
986    fn get_precision(&self) -> u32 {
987        self.0.get_precision()
988    }
989
990    fn zero(&self) -> Self {
991        Self(self.0.zero())
992    }
993
994    fn one(&self) -> Self {
995        Self(self.0.one())
996    }
997
998    fn new_zero() -> Self {
999        Self(DoubleFloat::default().zero())
1000    }
1001
1002    fn inv(&self) -> Self {
1003        Self(self.0.inv())
1004    }
1005
1006    fn pow(&self, e: u64) -> Self {
1007        // Old direct wrapper around Symbolica's DoubleFloat::pow -> Df64::powi.
1008        // Keep this inactive until the upstream bug is fixed in Symbolica.
1009        // Self(self.0.pow(e))
1010
1011        // Temporary local workaround kept active in GammaLoop for now.
1012        // Equivalent explicit implementation:
1013        // let mut base = *self;
1014        // let mut acc = Self::one();
1015        // let mut exponent = e;
1016        // while exponent > 0 {
1017        //     if exponent & 1 == 1 {
1018        //         acc *= base;
1019        //     }
1020        //     exponent >>= 1;
1021        //     if exponent > 0 {
1022        //         base *= base;
1023        //     }
1024        // }
1025        // acc
1026        self.pow_u64_via_mul(e)
1027    }
1028
1029    fn sample_unit<R: Rng + ?Sized>(&self, rng: &mut R) -> Self {
1030        Self(self.0.sample_unit(rng))
1031    }
1032
1033    fn neg(&self) -> Self {
1034        Self(self.0.neg())
1035    }
1036
1037    fn get_epsilon(&self) -> f64 {
1038        self.0.get_epsilon()
1039    }
1040
1041    fn fixed_precision(&self) -> bool {
1042        self.0.fixed_precision()
1043    }
1044}
1045
1046impl SingleFloat for QuadFloat {
1047    fn is_finite(&self) -> bool {
1048        self.0.is_finite()
1049    }
1050
1051    fn is_one(&self) -> bool {
1052        self.0.is_one()
1053    }
1054
1055    fn is_zero(&self) -> bool {
1056        self.0.is_zero()
1057    }
1058
1059    fn from_rational(&self, rat: &Rational) -> Self {
1060        rat.into()
1061    }
1062}
1063
1064impl RealLike for QuadFloat {
1065    fn to_f64(&self) -> f64 {
1066        self.0.to_f64()
1067    }
1068
1069    fn round_to_nearest_integer(&self) -> Integer {
1070        self.0.round_to_nearest_integer()
1071    }
1072
1073    fn to_usize_clamped(&self) -> usize {
1074        self.0.to_usize_clamped()
1075    }
1076}
1077
1078impl Real for QuadFloat {
1079    fn i(&self) -> Option<Self> {
1080        None
1081    }
1082
1083    fn conj(&self) -> Self {
1084        *self
1085    }
1086
1087    #[inline(always)]
1088    fn pi(&self) -> Self {
1089        Self(self.0.pi())
1090    }
1091
1092    #[inline(always)]
1093    fn e(&self) -> Self {
1094        Self(self.0.e())
1095    }
1096
1097    #[inline(always)]
1098    fn euler(&self) -> Self {
1099        Self(self.0.euler())
1100    }
1101
1102    #[inline(always)]
1103    fn phi(&self) -> Self {
1104        Self(self.0.phi())
1105    }
1106
1107    fn atan2(&self, x: &Self) -> Self {
1108        Self(self.0.atan2(&x.0))
1109    }
1110
1111    fn powf(&self, e: &Self) -> Self {
1112        Self(self.0.powf(&e.0))
1113    }
1114
1115    fn log(&self) -> Self {
1116        Self(self.0.log())
1117    }
1118
1119    fn norm(&self) -> Self {
1120        Self(self.0.norm())
1121    }
1122
1123    fn sqrt(&self) -> Self {
1124        Self(self.0.sqrt())
1125    }
1126
1127    fn exp(&self) -> Self {
1128        Self(self.0.exp())
1129    }
1130
1131    fn sin(&self) -> Self {
1132        Self(self.0.sin())
1133    }
1134
1135    fn cos(&self) -> Self {
1136        Self(self.0.cos())
1137    }
1138
1139    fn tan(&self) -> Self {
1140        Self(self.0.tan())
1141    }
1142
1143    fn asin(&self) -> Self {
1144        Self(self.0.asin())
1145    }
1146
1147    fn acos(&self) -> Self {
1148        Self(self.0.acos())
1149    }
1150
1151    fn sinh(&self) -> Self {
1152        Self(self.0.sinh())
1153    }
1154
1155    fn cosh(&self) -> Self {
1156        Self(self.0.cosh())
1157    }
1158
1159    fn tanh(&self) -> Self {
1160        Self(self.0.tanh())
1161    }
1162
1163    fn asinh(&self) -> Self {
1164        Self(self.0.asinh())
1165    }
1166
1167    fn acosh(&self) -> Self {
1168        Self(self.0.acosh())
1169    }
1170
1171    fn atanh(&self) -> Self {
1172        Self(self.0.atanh())
1173    }
1174}
1175
1176impl FloatLike for f128 {
1177    fn E(&self) -> Self {
1178        Self::E()
1179    }
1180
1181    fn PIHALF(&self) -> Self {
1182        Self::PIHALF()
1183    }
1184
1185    fn SQRT_2(&self) -> Self {
1186        Self::from_f64(2.0).sqrt()
1187    }
1188
1189    fn SQRT_2_HALF(&self) -> Self {
1190        Self::from_f64(2.0).sqrt() / Self::from_f64(2.0)
1191    }
1192
1193    fn rem_euclid(&self, rhs: &Self) -> Self {
1194        let r = self.ref_rem(rhs);
1195        if r < r.zero() { r + rhs } else { r }
1196    }
1197
1198    fn FRAC_1_PI(&self) -> Self {
1199        Self::FRAC_1_PI()
1200    }
1201
1202    fn PI(&self) -> Self {
1203        Self::PI()
1204    }
1205
1206    fn TAU(&self) -> Self {
1207        Self::TAU()
1208    }
1209
1210    fn from_f64(x: f64) -> Self {
1211        // There are two reasonable f64 -> higher-precision policies:
1212        // preserve the exact binary64 value or reinterpret the visible decimal
1213        // spelling of the f64. GammaLoop currently chooses the decimal route here
1214        // because these upcasts are overwhelmingly user-authored settings, and
1215        // values like 0.1 are less surprising when they retain their decimal
1216        // semantics instead of exposing the hidden binary64 tail. The exact-binary
1217        // helper is kept alongside this for callers that need a faithful embedding
1218        // of an already-computed f64.
1219        Self::from_f64_decimal(x)
1220    }
1221
1222    fn into_f64(&self) -> f64 {
1223        self.as_f64()
1224    }
1225
1226    fn is_nan(&self) -> bool {
1227        self.is_nan_value()
1228    }
1229
1230    fn is_infinite(&self) -> bool {
1231        self.is_infinite_value()
1232    }
1233
1234    fn floor(&self) -> Self {
1235        self.floor_value()
1236    }
1237
1238    fn try_extract_externals_from_cache(
1239        externals: &Externals,
1240    ) -> Option<&TiVec<ExternalIndex, FourMomentum<F<Self>>>> {
1241        match externals {
1242            Externals::Constant { f_128_cache, .. } => f_128_cache.as_ref(),
1243        }
1244    }
1245
1246    fn epsilon(&self) -> Self {
1247        self.machine_epsilon()
1248    }
1249}
1250
1251impl FloatLike for ArbPrec {
1252    fn E(&self) -> Self {
1253        Self::E()
1254    }
1255
1256    fn PIHALF(&self) -> Self {
1257        Self::PIHALF()
1258    }
1259
1260    fn SQRT_2(&self) -> Self {
1261        Self::from_f64(2.0).sqrt()
1262    }
1263
1264    fn SQRT_2_HALF(&self) -> Self {
1265        Self::from_f64(2.0).sqrt() / Self::from_f64(2.0)
1266    }
1267
1268    fn rem_euclid(&self, rhs: &Self) -> Self {
1269        let r = self.ref_rem(rhs);
1270        if r < r.zero() { r + rhs } else { r }
1271    }
1272
1273    fn FRAC_1_PI(&self) -> Self {
1274        Self::FRAC_1_PI()
1275    }
1276
1277    fn PI(&self) -> Self {
1278        Self::PI()
1279    }
1280
1281    fn TAU(&self) -> Self {
1282        Self::TAU()
1283    }
1284
1285    fn from_f64(x: f64) -> Self {
1286        // There are two reasonable f64 -> higher-precision policies:
1287        // preserve the exact binary64 value or reinterpret the visible decimal
1288        // spelling of the f64. GammaLoop currently chooses the decimal route here
1289        // because these upcasts are overwhelmingly user-authored settings, and
1290        // values like 0.1 are less surprising when they retain their decimal
1291        // semantics instead of exposing the hidden binary64 tail. The exact-binary
1292        // helper is kept alongside this for callers that need a faithful embedding
1293        // of an already-computed f64.
1294        VarFloat::from_f64(x)
1295    }
1296
1297    fn into_f64(&self) -> f64 {
1298        self.to_f64()
1299    }
1300
1301    fn is_nan(&self) -> bool {
1302        self.float.is_nan()
1303    }
1304
1305    fn is_infinite(&self) -> bool {
1306        self.float.is_infinite()
1307    }
1308
1309    fn floor(&self) -> Self {
1310        self.float.clone().floor().into()
1311    }
1312
1313    fn try_extract_externals_from_cache(
1314        _externals: &Externals,
1315    ) -> Option<&TiVec<ExternalIndex, FourMomentum<F<Self>>>> {
1316        None
1317    }
1318
1319    fn epsilon(&self) -> Self {
1320        self.machine_epsilon()
1321    }
1322}
1323
1324impl<const N: u32> VarFloat<N> {
1325    fn machine_epsilon(&self) -> Self {
1326        self.from_i64(2).pow((N - 1) as u64).inv()
1327    }
1328
1329    fn one() -> Self {
1330        VarFloat {
1331            float: rug::Float::with_val(N, 1.0),
1332        }
1333    }
1334    #[allow(non_snake_case)]
1335    fn E() -> Self {
1336        Self::one().exp()
1337    }
1338
1339    #[allow(non_snake_case)]
1340    fn PIHALF() -> Self {
1341        Self::PI() / Self::from_f64(2.0)
1342    }
1343
1344    #[allow(non_snake_case)]
1345    fn PI() -> Self {
1346        VarFloat {
1347            float: rug::Float::with_val(N, Constant::Pi),
1348        }
1349    }
1350
1351    #[allow(non_snake_case)]
1352    fn TAU() -> Self {
1353        let mut tau = Self::PI() + Self::PI();
1354        tau.float.set_prec(N);
1355        tau
1356    }
1357
1358    #[allow(non_snake_case)]
1359    fn FRAC_1_PI() -> Self {
1360        Self::PI().inv()
1361    }
1362
1363    #[allow(dead_code)]
1364    pub(crate) fn from_f64_exact_binary(x: f64) -> Self {
1365        VarFloat {
1366            float: rug::Float::with_val(N, x),
1367        }
1368    }
1369
1370    pub(crate) fn from_f64_decimal(x: f64) -> Self {
1371        if !x.is_finite() {
1372            return Self::from_f64_exact_binary(x);
1373        }
1374
1375        let valid = Float::parse(format!("{}", x)).unwrap();
1376        VarFloat {
1377            float: rug::Float::with_val(N, valid),
1378        }
1379    }
1380
1381    pub(crate) fn from_f64(x: f64) -> Self {
1382        // There are two reasonable f64 -> higher-precision policies:
1383        // preserve the exact binary64 value or reinterpret the visible decimal
1384        // spelling of the f64. GammaLoop currently chooses the decimal route here
1385        // because these upcasts are overwhelmingly user-authored settings, and
1386        // values like 0.1 are less surprising when they retain their decimal
1387        // semantics instead of exposing the hidden binary64 tail. The exact-binary
1388        // helper is kept alongside this for callers that need a faithful embedding
1389        // of an already-computed f64.
1390        Self::from_f64_decimal(x)
1391    }
1392
1393    pub(crate) fn to_f64(&self) -> f64 {
1394        self.float.to_f64()
1395    }
1396
1397    #[allow(dead_code)]
1398    fn as_f64(&self) -> f64 {
1399        self.float.to_f64()
1400    }
1401
1402    #[allow(dead_code)]
1403    fn is_nan_value(&self) -> bool {
1404        self.float.is_nan()
1405    }
1406
1407    #[allow(dead_code)]
1408    fn is_infinite_value(&self) -> bool {
1409        self.float.is_infinite()
1410    }
1411
1412    #[allow(dead_code)]
1413    fn floor_value(&self) -> Self {
1414        self.float.clone().floor().into()
1415    }
1416}
1417
1418impl QuadFloat {
1419    fn pow_u64_via_mul(&self, mut exponent: u64) -> Self {
1420        let mut base = *self;
1421        let mut acc = Self::one();
1422
1423        while exponent > 0 {
1424            if exponent & 1 == 1 {
1425                acc *= base;
1426            }
1427            exponent >>= 1;
1428            if exponent > 0 {
1429                base *= base;
1430            }
1431        }
1432
1433        acc
1434    }
1435
1436    fn machine_epsilon(&self) -> Self {
1437        self.from_i64(2)
1438            .pow((self.get_precision() - 1) as u64)
1439            .inv()
1440    }
1441
1442    #[allow(dead_code)]
1443    fn one() -> Self {
1444        Self(DoubleFloat::from(1.0))
1445    }
1446
1447    #[allow(non_snake_case)]
1448    fn E() -> Self {
1449        Self(DoubleFloat::default().e())
1450    }
1451
1452    #[allow(non_snake_case)]
1453    fn PIHALF() -> Self {
1454        Self::PI() / Self::from_f64(2.0)
1455    }
1456
1457    #[allow(non_snake_case)]
1458    fn PI() -> Self {
1459        Self(DoubleFloat::default().pi())
1460    }
1461
1462    #[allow(non_snake_case)]
1463    fn TAU() -> Self {
1464        Self::PI() + Self::PI()
1465    }
1466
1467    #[allow(non_snake_case)]
1468    fn FRAC_1_PI() -> Self {
1469        Self::PI().inv()
1470    }
1471
1472    #[allow(dead_code)]
1473    pub(crate) fn from_f64_exact_binary(x: f64) -> Self {
1474        Self(DoubleFloat::from(x))
1475    }
1476
1477    pub(crate) fn from_f64_decimal(x: f64) -> Self {
1478        if !x.is_finite() {
1479            return Self::from_f64_exact_binary(x);
1480        }
1481
1482        SymbolicaFloat::parse(
1483            &format!("{}", x),
1484            Some(DoubleFloat::default().get_precision()),
1485        )
1486        .unwrap()
1487        .into()
1488    }
1489
1490    fn from_f64(x: f64) -> Self {
1491        Self::from_f64_decimal(x)
1492    }
1493
1494    fn floor_via_symbolica(&self) -> Self {
1495        SymbolicaFloat::from(self).into_inner().floor().into()
1496    }
1497
1498    fn trunc_toward_zero(&self) -> Self {
1499        if *self < Self::new_zero() {
1500            -(-self).floor_via_symbolica()
1501        } else {
1502            self.floor_via_symbolica()
1503        }
1504    }
1505
1506    fn truncating_remainder(&self, rhs: &Self) -> Self {
1507        if rhs.is_zero() {
1508            return Self::from_f64_exact_binary(f64::NAN);
1509        }
1510
1511        *self - ((*self / *rhs).trunc_toward_zero() * *rhs)
1512    }
1513
1514    fn as_f64(&self) -> f64 {
1515        self.0.to_f64()
1516    }
1517
1518    fn is_nan_value(&self) -> bool {
1519        self.0.to_f64().is_nan()
1520    }
1521
1522    fn is_infinite_value(&self) -> bool {
1523        self.0.to_f64().is_infinite()
1524    }
1525
1526    fn floor_value(&self) -> Self {
1527        self.floor_via_symbolica()
1528    }
1529}
1530
1531impl<const N: u32> Default for VarFloat<N> {
1532    fn default() -> Self {
1533        VarFloat {
1534            float: rug::Float::with_val(N, 0.0),
1535        }
1536    }
1537}
1538
1539impl PrecisionUpgradable for f128 {
1540    type Higher = ArbPrec;
1541    type Lower = f64;
1542
1543    fn higher(&self) -> Self::Higher {
1544        ArbPrec::from(SymbolicaFloat::from(self))
1545    }
1546
1547    fn lower(&self) -> Self::Lower {
1548        self.as_f64()
1549    }
1550}
1551
1552impl PrecisionUpgradable for ArbPrec {
1553    type Higher = ArbPrec;
1554    type Lower = f128;
1555
1556    fn higher(&self) -> Self::Higher {
1557        self.clone()
1558    }
1559
1560    fn lower(&self) -> Self::Lower {
1561        f128::from(self.float.clone())
1562    }
1563}
1564
1565impl<T: Real + PrecisionUpgradable, H: Real, L: Real> PrecisionUpgradable for Complex<T>
1566where
1567    T: PrecisionUpgradable<Higher = H, Lower = L>,
1568{
1569    type Higher = Complex<H>;
1570    type Lower = Complex<L>;
1571
1572    fn higher(&self) -> Self::Higher {
1573        Complex::new(self.re.higher(), self.im.higher())
1574    }
1575
1576    fn lower(&self) -> Self::Lower {
1577        Complex::new(self.re.lower(), self.im.lower())
1578    }
1579}
1580
1581// #[allow(non_camel_case_types)]
1582// pub type f256 = VarFloat<243>;
1583
1584pub trait PrecisionUpgradable {
1585    type Higher;
1586    type Lower;
1587
1588    fn higher(&self) -> Self::Higher;
1589    fn lower(&self) -> Self::Lower;
1590}
1591
1592pub trait FloatLike:
1593    Real
1594    +R
1595    +Default
1596    + Clone
1597    + PartialOrd
1598    + RealLike
1599    + for<'a> RefAdd<&'a Self, Output = Self>
1600    // + for<'a> RefMutAdd<&'a Self, Output = Self>
1601    + RefAdd<Self, Output = Self>
1602    // + RefMutAdd<Self, Output = Self>
1603    + for<'a> RefMul<&'a Self, Output = Self>
1604    // + for<'a> RefMutMul<&'a Self, Output = Self>
1605    + RefMul<Self, Output = Self>
1606    // + RefMutMul<Self, Output = Self>
1607    + for<'a> RefSub<&'a Self, Output = Self>
1608    // + for<'a> RefMutSub<&'a Self, Output = Self>
1609    + RefSub<Self, Output = Self>
1610    // + RefMutSub<Self, Output = Self>
1611    + for<'a> RefDiv<&'a Self, Output = Self>
1612    // + for<'a> RefMutDiv<&'a Self, Output = Self> f64 doesn't have RefMutDiv
1613    + RefDiv<Self, Output = Self>
1614    + for<'a> RefRem<&'a Self, Output = Self>
1615    // + RefMutDiv<Self, Output = Self>
1616    + RefNeg<Output = Self>
1617    + RefZero
1618    + RefOne
1619    // + RefMutNeg<Output = Self> f64 doesn't have RefMutNeg
1620    + PrecisionUpgradable
1621    + Serialize
1622    + Display
1623    + GenericEvaluatorFloat
1624    + Into<symbolica::domains::float::Float>
1625{
1626
1627    #[allow(non_snake_case)]
1628    fn PI(&self) -> Self;
1629    #[allow(non_snake_case)]
1630    fn E(&self) -> Self;
1631    #[allow(non_snake_case)]
1632    fn TAU(&self) -> Self;
1633    #[allow(non_snake_case)]
1634    fn SQRT_2(&self) -> Self;
1635    #[allow(non_snake_case)]
1636    fn SQRT_2_HALF(&self) -> Self;
1637    #[allow(non_snake_case)]
1638    fn PIHALF(&self) -> Self;
1639    #[allow(non_snake_case)]
1640    fn FRAC_1_PI(&self) -> Self;
1641
1642    fn from_f64(x: f64) -> Self;
1643
1644    #[allow(clippy::wrong_self_convention)]
1645    fn into_f64(&self) -> f64; // for inverse gamma in tropical sampling
1646
1647    fn is_nan(&self) -> bool;
1648
1649    fn is_infinite(&self) -> bool;
1650
1651    fn floor(&self) -> Self;
1652
1653    fn square(&self) -> Self {
1654        self.pow(2)
1655    }
1656
1657    fn powi(&self, n: i32) -> Self {
1658        let absn = n.unsigned_abs() as u64;
1659        if n.is_negative() {
1660            self.pow(absn).inv()
1661        } else {
1662            self.pow(absn)
1663        }
1664    }
1665
1666    fn epsilon(&self) -> Self;
1667
1668    fn less_than_epsilon(&self) -> bool {
1669        self < &self.epsilon()
1670    }
1671
1672    fn positive(&self) -> bool {
1673        self > &self.zero()
1674    }
1675
1676    fn max_value(&self) -> Self {
1677        Self::from_f64(f64::MAX)
1678    }
1679
1680    fn min_value(&self) -> Self {
1681        Self::from_f64(f64::MIN)
1682    }
1683
1684    fn ln(&self) -> Self {
1685       self.log()
1686    }
1687
1688    fn rem_euclid(&self, rhs: &Self) -> Self;
1689
1690    fn try_extract_externals_from_cache(externals: &Externals) -> Option<&TiVec<ExternalIndex, FourMomentum<F<Self>>>>;
1691}
1692
1693#[derive(
1694    Debug,
1695    Clone,
1696    PartialEq,
1697    PartialOrd,
1698    Copy,
1699    Default,
1700    Serialize,
1701    Deserialize,
1702    Encode,
1703    Decode,
1704    Hash,
1705    JsonSchema,
1706)]
1707pub struct F<T: FloatLike>(pub T);
1708
1709impl<T: FloatLike> ToCoefficient for F<T> {
1710    fn to_coefficient(self) -> Coefficient {
1711        let z = self.zero();
1712        Coefficient::Float(symbolica::domains::float::Complex {
1713            re: self.into(),
1714            im: z.into(),
1715        })
1716    }
1717}
1718
1719use symbolica::evaluate::{EvaluationDomain, ExportNumber};
1720
1721impl ExportNumber for QuadFloat {
1722    fn export(&self) -> String {
1723        self.to_string()
1724    }
1725
1726    fn is_real(&self) -> bool {
1727        true
1728    }
1729
1730    fn to_complex_double(&self) -> symbolica::domains::float::Complex<f64> {
1731        symbolica::domains::float::Complex::new(self.0.to_f64(), 0.0)
1732    }
1733}
1734
1735impl<const N: u32> ExportNumber for VarFloat<N> {
1736    fn export(&self) -> String {
1737        self.to_string()
1738    }
1739
1740    fn is_real(&self) -> bool {
1741        true
1742    }
1743
1744    fn to_complex_double(&self) -> symbolica::domains::float::Complex<f64> {
1745        symbolica::domains::float::Complex::new(self.to_f64(), 0.0)
1746    }
1747}
1748
1749impl FixedPrecision for QuadFloat {
1750    const BINARY_PRECISION: usize = <DoubleFloat as FixedPrecision>::BINARY_PRECISION;
1751}
1752
1753impl<const N: u32> FixedPrecision for VarFloat<N> {
1754    const BINARY_PRECISION: usize = N as usize;
1755}
1756
1757impl EvaluationDomain for QuadFloat {
1758    const FIXED_PRECISION: Option<u32> = <DoubleFloat as EvaluationDomain>::FIXED_PRECISION;
1759
1760    fn try_from_complex_float(
1761        f: symbolica::domains::float::Complex<symbolica::domains::float::Float>,
1762    ) -> Result<Self, String> {
1763        if f.is_real() {
1764            Ok(Self(f.re.to_double_float()))
1765        } else {
1766            Err(format!(
1767                "Cannot convert from Complex<Float> to {} because the result is not real",
1768                std::any::type_name::<Self>()
1769            ))
1770        }
1771    }
1772}
1773
1774impl<const N: u32> EvaluationDomain for VarFloat<N> {
1775    const FIXED_PRECISION: Option<u32> = Some(N);
1776
1777    fn try_from_complex_float(
1778        f: symbolica::domains::float::Complex<symbolica::domains::float::Float>,
1779    ) -> Result<Self, String> {
1780        if f.is_real() {
1781            Ok(Self::from(f.re))
1782        } else {
1783            Err(format!(
1784                "Cannot convert from Complex<Float> to {} because the result is not real",
1785                std::any::type_name::<Self>()
1786            ))
1787        }
1788    }
1789}
1790
1791impl<T: FloatLike + ExportNumber> ExportNumber for F<T> {
1792    fn export(&self) -> String {
1793        self.0.to_string()
1794    }
1795
1796    fn is_real(&self) -> bool {
1797        self.0.is_real()
1798    }
1799
1800    fn to_complex_double(&self) -> symbolica::domains::float::Complex<f64> {
1801        self.0.to_complex_double()
1802    }
1803}
1804
1805impl<T: FloatLike + FixedPrecision> FixedPrecision for F<T> {
1806    const BINARY_PRECISION: usize = T::BINARY_PRECISION;
1807}
1808
1809impl<T: FloatLike + EvaluationDomain> EvaluationDomain for F<T> {
1810    const FIXED_PRECISION: Option<u32> = T::FIXED_PRECISION;
1811
1812    fn try_from_complex_float(
1813        f: symbolica::domains::float::Complex<symbolica::domains::float::Float>,
1814    ) -> Result<Self, String> {
1815        if f.is_real() {
1816            T::try_from_complex_float(f).map(F)
1817        } else {
1818            Err(format!(
1819                "Cannot convert from Complex<Float> to {} because the result is not real",
1820                std::any::type_name::<Self>()
1821            ))
1822        }
1823    }
1824}
1825
1826impl<T: TensorLibraryData + FloatLike> TensorLibraryData for F<T> {
1827    fn one() -> Self {
1828        F(<T as TensorLibraryData>::one())
1829    }
1830
1831    fn minus_one() -> Self {
1832        F(<T as TensorLibraryData>::minus_one())
1833    }
1834
1835    fn zero() -> Self {
1836        F(<T as TensorLibraryData>::zero())
1837    }
1838}
1839
1840impl From<F<f64>> for Coefficient {
1841    fn from(value: F<f64>) -> Self {
1842        Coefficient::from(value.0)
1843    }
1844}
1845
1846impl<'a> From<&'a F<f64>> for Coefficient {
1847    fn from(x: &'a F<f64>) -> Self {
1848        x.0.into()
1849    }
1850}
1851
1852impl ToFloat for F<f64> {
1853    fn to_float(&self) -> symbolica::domains::float::Float {
1854        symbolica::domains::float::Float::with_val(53, self.0)
1855    }
1856}
1857
1858impl ToAtom for F<f64> {
1859    fn to_atom(self) -> Atom {
1860        Atom::num(self.0)
1861    }
1862}
1863
1864pub trait ToCoefficient {
1865    fn to_coefficient(self) -> Coefficient;
1866}
1867
1868impl<T: FloatLike> From<F<T>> for symbolica::domains::float::Float {
1869    fn from(value: F<T>) -> Self {
1870        value.0.into()
1871    }
1872}
1873
1874impl<T: FloatLike> ToCoefficient for Complex<F<T>> {
1875    fn to_coefficient(self) -> Coefficient {
1876        Coefficient::Float(symbolica::domains::float::Complex {
1877            re: self.re.0.into(),
1878            im: self.im.0.into(),
1879        })
1880    }
1881}
1882
1883impl TrySmallestUpgrade<F<f64>> for Atom {
1884    type LCM = Atom;
1885
1886    fn try_upgrade(&'_ self) -> Option<std::borrow::Cow<'_, Self::LCM>> {
1887        Some(std::borrow::Cow::Borrowed(self))
1888    }
1889}
1890
1891impl TrySmallestUpgrade<Atom> for F<f64> {
1892    type LCM = Atom;
1893
1894    fn try_upgrade(&'_ self) -> Option<std::borrow::Cow<'_, Self::LCM>> {
1895        <f64 as TrySmallestUpgrade<Atom>>::try_upgrade(&self.0)
1896    }
1897}
1898
1899impl<T: FloatLike> R for F<T> {}
1900
1901impl<T: FloatLike> Rem<&F<T>> for &F<T> {
1902    type Output = F<T>;
1903
1904    fn rem(self, rhs: &F<T>) -> Self::Output {
1905        F(self.0.ref_rem(&rhs.0))
1906    }
1907}
1908
1909impl<T: FloatLike> RefZero<F<T>> for &F<T> {
1910    fn ref_zero(&self) -> F<T> {
1911        F(self.0.ref_zero())
1912    }
1913}
1914
1915impl<T: FloatLike> RealLike for F<T> {
1916    delegate! {
1917        to self.0{
1918            fn to_usize_clamped(&self)->usize;
1919            fn to_f64(&self)->f64;
1920            fn round_to_nearest_integer(&self)->Integer;
1921        }
1922    }
1923}
1924
1925impl<T: FloatLike> SingleFloat for F<T> {
1926    delegate! {
1927        to self.0{
1928            fn is_zero(&self)->bool;
1929            fn is_one(&self)->bool;
1930            fn is_finite(&self)->bool;
1931        }
1932    }
1933    fn from_rational(&self, rat: &Rational) -> Self {
1934        F(self.0.from_rational(rat))
1935    }
1936}
1937
1938impl<T: FloatLike> PrecisionUpgradable for F<T>
1939where
1940    T::Higher: FloatLike,
1941    T::Lower: FloatLike,
1942{
1943    type Higher = F<T::Higher>;
1944    type Lower = F<T::Lower>;
1945
1946    fn higher(&self) -> Self::Higher {
1947        F(self.0.higher())
1948    }
1949
1950    fn lower(&self) -> Self::Lower {
1951        F(self.0.lower())
1952    }
1953}
1954
1955impl<T: FloatLike> RefZero for F<T> {
1956    fn ref_zero(&self) -> Self {
1957        F(self.0.zero())
1958    }
1959}
1960
1961impl<T: FloatLike> RefOne for F<T> {
1962    fn ref_one(&self) -> Self {
1963        F(self.0.one())
1964    }
1965}
1966
1967impl<T: FloatLike> TrySmallestUpgrade<F<T>> for F<T> {
1968    type LCM = F<T>;
1969    fn try_upgrade(&'_ self) -> Option<std::borrow::Cow<'_, Self::LCM>> {
1970        Some(std::borrow::Cow::Borrowed(self))
1971    }
1972}
1973
1974impl<T: FloatLike> TrySmallestUpgrade<F<T>> for Complex<F<T>> {
1975    type LCM = Complex<F<T>>;
1976    fn try_upgrade(&'_ self) -> Option<std::borrow::Cow<'_, Self::LCM>> {
1977        Some(std::borrow::Cow::Borrowed(self))
1978    }
1979}
1980
1981impl<T: FloatLike> TrySmallestUpgrade<Complex<F<T>>> for F<T> {
1982    type LCM = Complex<F<T>>;
1983    fn try_upgrade(&'_ self) -> Option<std::borrow::Cow<'_, Self::LCM>> {
1984        let z = self.ref_zero();
1985
1986        Some(std::borrow::Cow::Owned(Complex::new(self.clone(), z)))
1987    }
1988}
1989
1990// impl<T:FloatLike> TrySmallestUpgrade<Complex<F<T>>> for F<T> {
1991//     type LCM = Complex<F<T>>;
1992//     fn try_upgrade(&self) -> Option<std::borrow::Cow<Self::LCM>> {
1993//         Some(std::borrow::Cow::Borrowed(self))
1994//     }
1995// }
1996
1997impl<'a, T: FloatLike> From<&'a Rational> for F<T> {
1998    fn from(x: &'a Rational) -> Self {
1999        F(T::new_zero().from_rational(x))
2000    }
2001}
2002
2003impl<T: FloatLike> From<T> for F<T> {
2004    fn from(x: T) -> Self {
2005        F(x)
2006    }
2007}
2008
2009impl<T: FloatLike> std::fmt::Display for F<T> {
2010    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2011        write!(f, "{}", self.0)
2012    }
2013}
2014
2015impl<T: FloatLike> std::fmt::LowerExp for F<T> {
2016    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2017        let rendered = match f.precision() {
2018            Some(precision) => format!("{:.*e}", precision, self.0),
2019            None => format!("{:e}", self.0),
2020        };
2021
2022        let (is_nonnegative, body) = match rendered.strip_prefix('-') {
2023            Some(body) => (false, body),
2024            None => (true, rendered.as_str()),
2025        };
2026
2027        f.pad_integral(is_nonnegative, "", body)
2028    }
2029}
2030
2031impl<T: FloatLike> SymFloatLike for F<T> {
2032    #[inline]
2033    fn set_from(&mut self, other: &Self) {
2034        self.0.set_from(&other.0);
2035    }
2036
2037    fn mul_add(&self, a: &Self, b: &Self) -> Self {
2038        F(self.0.mul_add(&a.0, &b.0))
2039    }
2040
2041    fn is_fully_zero(&self) -> bool {
2042        self.0.is_fully_zero()
2043    }
2044
2045    fn new_zero() -> Self {
2046        F(T::new_zero())
2047    }
2048    fn sample_unit<R: Rng + ?Sized>(&self, rng: &mut R) -> Self {
2049        F(self.0.sample_unit(rng))
2050    }
2051    fn neg(&self) -> Self {
2052        F(self.0.ref_neg())
2053    }
2054
2055    fn zero(&self) -> Self {
2056        F(self.0.zero())
2057    }
2058    fn one(&self) -> Self {
2059        F(self.0.one())
2060    }
2061    // fn norm(&self) -> Self;
2062    fn from_usize(&self, x: usize) -> Self {
2063        F(self.0.from_usize(x))
2064    }
2065    fn from_i64(&self, x: i64) -> Self {
2066        F(self.0.from_i64(x))
2067    }
2068    fn pow(&self, n: u64) -> Self {
2069        F(self.0.pow(n))
2070    }
2071    fn inv(&self) -> Self {
2072        F(self.0.inv())
2073    }
2074    fn get_precision(&self) -> u32 {
2075        self.0.get_precision()
2076    }
2077    fn get_epsilon(&self) -> f64 {
2078        self.0.get_epsilon()
2079    }
2080    fn fixed_precision(&self) -> bool {
2081        self.0.fixed_precision()
2082    }
2083}
2084
2085impl<T: FloatLike + Constructible> Constructible for F<T> {
2086    fn new_from_i64(a: i64) -> Self {
2087        F(T::new_from_i64(a))
2088    }
2089
2090    fn new_from_usize(a: usize) -> Self {
2091        F(T::new_from_usize(a))
2092    }
2093
2094    fn new_one() -> Self {
2095        F(T::new_one())
2096    }
2097
2098    fn new_sample_unit<R: Rng + ?Sized>(rng: &mut R) -> Self {
2099        F(T::new_sample_unit(rng))
2100    }
2101}
2102
2103impl<T: FloatLike> Real for F<T> {
2104    fn atan2(&self, x: &Self) -> Self {
2105        F(self.0.atan2(&x.0))
2106    }
2107
2108    fn conj(&self) -> Self {
2109        F(self.0.conj())
2110    }
2111
2112    fn i(&self) -> Option<Self> {
2113        None
2114    }
2115
2116    fn powf(&self, e: &Self) -> Self {
2117        F(self.0.powf(&e.0))
2118    }
2119
2120    fn norm(&self) -> Self {
2121        F(self.0.norm())
2122    }
2123
2124    fn e(&self) -> Self {
2125        F(self.0.e())
2126    }
2127    fn phi(&self) -> Self {
2128        F(self.0.phi())
2129    }
2130    fn euler(&self) -> Self {
2131        F(self.0.euler())
2132    }
2133    fn pi(&self) -> Self {
2134        F(self.0.pi())
2135    }
2136    fn sqrt(&self) -> Self {
2137        F(self.0.sqrt())
2138    }
2139    fn log(&self) -> Self {
2140        F(self.0.log())
2141    }
2142    fn exp(&self) -> Self {
2143        F(self.0.exp())
2144    }
2145    fn sin(&self) -> Self {
2146        F(self.0.sin())
2147    }
2148    fn cos(&self) -> Self {
2149        F(self.0.cos())
2150    }
2151    fn tan(&self) -> Self {
2152        F(self.0.tan())
2153    }
2154    fn asin(&self) -> Self {
2155        F(self.0.asin())
2156    }
2157    fn acos(&self) -> Self {
2158        F(self.0.acos())
2159    }
2160    fn sinh(&self) -> Self {
2161        F(self.0.sinh())
2162    }
2163    fn cosh(&self) -> Self {
2164        F(self.0.cosh())
2165    }
2166    fn tanh(&self) -> Self {
2167        F(self.0.tanh())
2168    }
2169    fn asinh(&self) -> Self {
2170        F(self.0.asinh())
2171    }
2172    fn acosh(&self) -> Self {
2173        F(self.0.acosh())
2174    }
2175    fn atanh(&self) -> Self {
2176        F(self.0.atanh())
2177    }
2178}
2179
2180use delegate::delegate;
2181
2182impl<T: FloatLike> F<T> {
2183    pub(crate) fn max(self, other: F<T>) -> F<T> {
2184        if self < other { other } else { self }
2185    }
2186
2187    pub fn negate(&mut self) {
2188        self.0 = -self.0.clone();
2189    }
2190
2191    pub(crate) fn from_ff64(x: F<f64>) -> Self {
2192        F(T::from_f64(x.0))
2193    }
2194
2195    pub(crate) fn zero(&self) -> Self {
2196        F(self.0.zero())
2197    }
2198
2199    pub(crate) fn one(&self) -> Self {
2200        F(self.0.one())
2201    }
2202
2203    #[allow(clippy::wrong_self_convention)]
2204    pub(crate) fn from_usize(&self, x: usize) -> Self {
2205        F(self.0.from_usize(x))
2206    }
2207
2208    #[allow(clippy::wrong_self_convention)]
2209    pub(crate) fn from_i64(&self, x: i64) -> Self {
2210        F(self.0.from_i64(x))
2211    }
2212
2213    pub(crate) fn higher(&self) -> F<T::Higher>
2214    where
2215        T::Higher: FloatLike,
2216    {
2217        F(self.0.higher())
2218    }
2219
2220    pub(crate) fn lower(&self) -> F<T::Lower>
2221    where
2222        T::Lower: FloatLike,
2223    {
2224        F(self.0.lower())
2225    }
2226
2227    pub fn from_f64(x: f64) -> Self {
2228        F(T::from_f64(x))
2229    }
2230
2231    #[allow(clippy::wrong_self_convention)]
2232    pub(crate) fn into_ff64(&self) -> F<f64> {
2233        F(self.0.into_f64())
2234    }
2235
2236    pub(crate) fn abs(&self) -> Self {
2237        F(self.0.norm())
2238    }
2239
2240    pub(crate) fn sqrt(&self) -> Self {
2241        F(self.0.sqrt())
2242    }
2243
2244    pub(crate) fn powf(&self, e: &Self) -> Self {
2245        F(self.0.powf(&e.0))
2246    }
2247
2248    pub(crate) fn log10(&self) -> Self {
2249        let ten = self.from_i64(10);
2250        self.ln() / ten.ln()
2251    }
2252
2253    pub(crate) fn complex_sqrt(&self) -> Complex<Self> {
2254        if self.positive() {
2255            Complex::new(self.sqrt(), self.zero())
2256        } else {
2257            Complex::new(self.zero(), (-self).sqrt())
2258        }
2259    }
2260
2261    pub(crate) fn rem_euclid(&self, rhs: &Self) -> Self {
2262        F(self.0.rem_euclid(&rhs.0))
2263    }
2264
2265    #[allow(non_snake_case)]
2266    pub(crate) fn PI(&self) -> Self {
2267        F(self.0.PI())
2268    }
2269    #[allow(non_snake_case)]
2270    pub fn E(&self) -> Self {
2271        F(self.0.E())
2272    }
2273    #[allow(non_snake_case)]
2274    pub fn TAU(&self) -> Self {
2275        F(self.0.TAU())
2276    }
2277    #[allow(non_snake_case)]
2278    pub fn PIHALF(&self) -> Self {
2279        F(self.0.PIHALF())
2280    }
2281    #[allow(non_snake_case)]
2282    pub fn SQRT_2(&self) -> Self {
2283        F(self.0.SQRT_2())
2284    }
2285    #[allow(non_snake_case)]
2286    pub fn SQRT_2_HALF(&self) -> Self {
2287        F(self.0.SQRT_2_HALF())
2288    }
2289    #[allow(non_snake_case)]
2290    pub(crate) fn FRAC_1_PI(&self) -> Self {
2291        F(self.0.FRAC_1_PI())
2292    }
2293    #[allow(clippy::wrong_self_convention)]
2294    pub(crate) fn into_f64(&self) -> f64 {
2295        self.0.into_f64()
2296    }
2297    pub(crate) fn square(&self) -> Self {
2298        F(self.0.square())
2299    }
2300    pub(crate) fn powi(&self, n: i32) -> Self {
2301        F(self.0.powi(n))
2302    }
2303    pub(crate) fn epsilon(&self) -> Self {
2304        F(self.0.epsilon())
2305    }
2306    pub(crate) fn less_than_epsilon(&self) -> bool {
2307        self.0.less_than_epsilon()
2308    }
2309    pub(crate) fn positive(&self) -> bool {
2310        self.0.positive()
2311    }
2312    pub(crate) fn max_value(&self) -> Self {
2313        F(self.0.max_value())
2314    }
2315    pub(crate) fn min_value(&self) -> Self {
2316        F(self.0.min_value())
2317    }
2318    pub(crate) fn ln(&self) -> Self {
2319        F(self.0.ln())
2320    }
2321    pub(crate) fn inv(&self) -> Self {
2322        F(self.0.inv())
2323    }
2324    pub(crate) fn is_nan(&self) -> bool {
2325        self.0.is_nan()
2326    }
2327    pub(crate) fn is_infinite(&self) -> bool {
2328        self.0.is_infinite()
2329    }
2330    pub fn floor(&self) -> Self {
2331        F(self.0.floor())
2332    }
2333}
2334
2335// impl CompiledEvaluatorFloat for F<f64> {
2336//     fn evaluate(
2337//         eval: &mut symbolica::evaluate::CompiledEvaluator,
2338//         args: &[Self],
2339//         out: &mut [Self],
2340//     ) {
2341//         // cast to f64
2342//         let args_f64: Vec<f64> = args.iter().map(|x| x.0).collect_vec();
2343//         let mut out_f64 = out.iter().map(|x| x.0).collect_vec();
2344
2345//         eval.evaluate_double(&args_f64, &mut out_f64);
2346
2347//         // write the result to out
2348//         out.iter_mut()
2349//             .zip(out_f64)
2350//             .for_each(|(out_ff64, out_f64)| *out_ff64 = F(out_f64));
2351//     }
2352// }
2353
2354impl<T: FloatLike> Add<F<T>> for F<T> {
2355    type Output = F<T>;
2356    fn add(self, rhs: F<T>) -> Self::Output {
2357        F(self.0 + rhs.0)
2358    }
2359}
2360
2361impl<T: FloatLike> Add<&F<T>> for F<T> {
2362    type Output = F<T>;
2363    fn add(self, rhs: &F<T>) -> Self::Output {
2364        F(self.0 + &rhs.0)
2365    }
2366}
2367
2368impl<T: FloatLike> Add<&F<T>> for &F<T> {
2369    type Output = F<T>;
2370    fn add(self, rhs: &F<T>) -> Self::Output {
2371        F(self.0.ref_add(&rhs.0))
2372    }
2373}
2374
2375impl<T: FloatLike> Add<F<T>> for &F<T> {
2376    type Output = F<T>;
2377    fn add(self, rhs: F<T>) -> Self::Output {
2378        F(self.0.ref_add(rhs.0))
2379    }
2380}
2381
2382impl<T: FloatLike> AddAssign<&F<T>> for F<T> {
2383    fn add_assign(&mut self, rhs: &F<T>) {
2384        self.0 += &rhs.0;
2385    }
2386}
2387
2388impl<T: FloatLike> AddAssign<F<T>> for F<T> {
2389    fn add_assign(&mut self, rhs: F<T>) {
2390        self.0 += rhs.0;
2391    }
2392}
2393
2394impl<T: FloatLike> Sub<F<T>> for F<T> {
2395    type Output = F<T>;
2396    fn sub(self, rhs: F<T>) -> Self::Output {
2397        F(self.0 - rhs.0)
2398    }
2399}
2400
2401impl<T: FloatLike> Sub<&F<T>> for F<T> {
2402    type Output = F<T>;
2403    fn sub(self, rhs: &F<T>) -> Self::Output {
2404        F(self.0 - &rhs.0)
2405    }
2406}
2407
2408impl<T: FloatLike> Sub<&F<T>> for &F<T> {
2409    type Output = F<T>;
2410    fn sub(self, rhs: &F<T>) -> Self::Output {
2411        F(self.0.ref_sub(&rhs.0))
2412    }
2413}
2414
2415impl<T: FloatLike> Sub<F<T>> for &F<T> {
2416    type Output = F<T>;
2417    fn sub(self, rhs: F<T>) -> Self::Output {
2418        F(self.0.ref_sub(rhs.0))
2419    }
2420}
2421
2422impl<T: FloatLike> SubAssign<&F<T>> for F<T> {
2423    fn sub_assign(&mut self, rhs: &F<T>) {
2424        self.0 -= &rhs.0;
2425    }
2426}
2427
2428impl<T: FloatLike> SubAssign<F<T>> for F<T> {
2429    fn sub_assign(&mut self, rhs: F<T>) {
2430        self.0 -= rhs.0;
2431    }
2432}
2433
2434impl<T: FloatLike> Mul<F<T>> for F<T> {
2435    type Output = F<T>;
2436    fn mul(self, rhs: F<T>) -> Self::Output {
2437        F(self.0 * rhs.0)
2438    }
2439}
2440
2441impl<T: FloatLike> Mul<&F<T>> for F<T> {
2442    type Output = F<T>;
2443    fn mul(self, rhs: &F<T>) -> Self::Output {
2444        F(self.0 * &rhs.0)
2445    }
2446}
2447
2448impl<T: FloatLike> Mul<&F<T>> for &F<T> {
2449    type Output = F<T>;
2450    fn mul(self, rhs: &F<T>) -> Self::Output {
2451        F(self.0.ref_mul(&rhs.0))
2452    }
2453}
2454
2455impl<T: FloatLike> Mul<F<T>> for &F<T> {
2456    type Output = F<T>;
2457    fn mul(self, rhs: F<T>) -> Self::Output {
2458        F(self.0.ref_mul(rhs.0))
2459    }
2460}
2461
2462impl<T: FloatLike> MulAssign<&F<T>> for F<T> {
2463    fn mul_assign(&mut self, rhs: &F<T>) {
2464        self.0 *= &rhs.0;
2465    }
2466}
2467
2468impl<T: FloatLike> MulAssign<F<T>> for F<T> {
2469    fn mul_assign(&mut self, rhs: F<T>) {
2470        self.0 *= rhs.0;
2471    }
2472}
2473
2474impl<T: FloatLike> Div<F<T>> for F<T> {
2475    type Output = F<T>;
2476    fn div(self, rhs: F<T>) -> Self::Output {
2477        F(self.0 / rhs.0)
2478    }
2479}
2480
2481impl<T: FloatLike> Div<&F<T>> for F<T> {
2482    type Output = F<T>;
2483    fn div(self, rhs: &F<T>) -> Self::Output {
2484        F(self.0 / &rhs.0)
2485    }
2486}
2487
2488impl<T: FloatLike> Div<&F<T>> for &F<T> {
2489    type Output = F<T>;
2490    fn div(self, rhs: &F<T>) -> Self::Output {
2491        F(self.0.ref_div(&rhs.0))
2492    }
2493}
2494
2495impl<T: FloatLike> Div<F<T>> for &F<T> {
2496    type Output = F<T>;
2497    fn div(self, rhs: F<T>) -> Self::Output {
2498        F(self.0.ref_div(rhs.0))
2499    }
2500}
2501
2502impl<T: FloatLike> DivAssign<&F<T>> for F<T> {
2503    fn div_assign(&mut self, rhs: &F<T>) {
2504        self.0 /= &rhs.0;
2505    }
2506}
2507
2508impl<T: FloatLike> DivAssign<F<T>> for F<T> {
2509    fn div_assign(&mut self, rhs: F<T>) {
2510        self.0 /= rhs.0;
2511    }
2512}
2513
2514impl<T: FloatLike> Neg for F<T> {
2515    type Output = F<T>;
2516    fn neg(self) -> Self::Output {
2517        F(-self.0)
2518    }
2519}
2520
2521impl<T: FloatLike> Neg for &F<T> {
2522    type Output = F<T>;
2523    fn neg(self) -> Self::Output {
2524        F(self.0.ref_neg())
2525    }
2526}
2527
2528pub trait RefDefault {
2529    fn default(&self) -> Self;
2530}
2531
2532impl<T: FloatLike> RefDefault for T {
2533    fn default(&self) -> Self {
2534        self.zero()
2535    }
2536}
2537
2538impl<T: FloatLike> RefDefault for F<T> {
2539    fn default(&self) -> Self {
2540        F(self.0.default())
2541    }
2542}
2543impl PrecisionUpgradable for f64 {
2544    type Higher = f128;
2545    type Lower = f64;
2546
2547    fn higher(&self) -> Self::Higher {
2548        f128::from_f64(*self)
2549    }
2550
2551    fn lower(&self) -> Self::Lower {
2552        *self
2553    }
2554}
2555
2556impl FloatLike for f64 {
2557    fn PI(&self) -> Self {
2558        std::f64::consts::PI
2559    }
2560
2561    fn SQRT_2(&self) -> Self {
2562        std::f64::consts::SQRT_2
2563    }
2564
2565    fn SQRT_2_HALF(&self) -> Self {
2566        std::f64::consts::SQRT_2 / 2.0
2567    }
2568
2569    fn PIHALF(&self) -> Self {
2570        std::f64::consts::PI / 2.0
2571    }
2572
2573    fn E(&self) -> Self {
2574        std::f64::consts::E
2575    }
2576
2577    fn TAU(&self) -> Self {
2578        std::f64::consts::TAU
2579    }
2580
2581    fn FRAC_1_PI(&self) -> Self {
2582        std::f64::consts::FRAC_1_PI
2583    }
2584
2585    fn from_f64(x: f64) -> Self {
2586        x
2587    }
2588
2589    fn into_f64(&self) -> f64 {
2590        *self
2591    }
2592
2593    fn is_nan(&self) -> bool {
2594        f64::is_nan(*self)
2595    }
2596
2597    fn is_infinite(&self) -> bool {
2598        f64::is_infinite(*self)
2599    }
2600
2601    fn floor(&self) -> Self {
2602        f64::floor(*self)
2603    }
2604
2605    fn rem_euclid(&self, rhs: &Self) -> Self {
2606        f64::rem_euclid(*self, *rhs)
2607    }
2608
2609    fn try_extract_externals_from_cache(
2610        externals: &Externals,
2611    ) -> Option<&TiVec<ExternalIndex, FourMomentum<F<Self>>>> {
2612        match externals {
2613            Externals::Constant { f_64_cache, .. } => f_64_cache.as_ref(),
2614        }
2615    }
2616
2617    fn epsilon(&self) -> Self {
2618        f64::EPSILON
2619    }
2620}
2621impl From<F<f64>> for f64 {
2622    fn from(value: F<f64>) -> Self {
2623        value.0
2624    }
2625}
2626
2627impl From<F<f64>> for Rational {
2628    fn from(value: F<f64>) -> Self {
2629        value.0.try_into().unwrap()
2630    }
2631}
2632
2633#[allow(non_camel_case_types)]
2634// Keep this one-line fallback close to the active alias: `VarFloat<113>` is a
2635// slower but safe binary128-like replacement when debugging `DoubleFloat`
2636// issues, and the `f128` trait impls/effective epsilon logic are written so
2637// flipping this alias remains a valid drop-in escape hatch.
2638// pub type f128 = VarFloat<113>;
2639pub type f128 = QuadFloat;
2640pub type ArbPrec = VarFloat<1000>;
2641
2642/// An iterator which iterates two other iterators simultaneously
2643#[derive(Clone, Debug)]
2644#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
2645pub struct ZipEq<I, J> {
2646    a: I,
2647    b: J,
2648}
2649
2650/// An iterator which iterates two other iterators simultaneously and checks
2651/// if the sizes are equal in debug mode.
2652#[allow(unused)]
2653pub(crate) fn zip_eq<I, J>(i: I, j: J) -> ZipEq<I::IntoIter, J::IntoIter>
2654where
2655    I: IntoIterator,
2656    J: IntoIterator,
2657{
2658    ZipEq {
2659        a: i.into_iter(),
2660        b: j.into_iter(),
2661    }
2662}
2663
2664impl<I, J> Iterator for ZipEq<I, J>
2665where
2666    I: Iterator,
2667    J: Iterator,
2668{
2669    type Item = (I::Item, J::Item);
2670
2671    fn next(&mut self) -> Option<Self::Item> {
2672        match (self.a.next(), self.b.next()) {
2673            (None, None) => None,
2674            (Some(a), Some(b)) => Some((a, b)),
2675            (None, Some(_)) => {
2676                #[cfg(debug_assertions)]
2677                panic!("Unequal length of iterators; first iterator finished first");
2678                #[cfg(not(debug_assertions))]
2679                None
2680            }
2681            (Some(_), None) => {
2682                #[cfg(debug_assertions)]
2683                panic!("Unequal length of iterators; second iterator finished first");
2684                #[cfg(not(debug_assertions))]
2685                None
2686            }
2687        }
2688    }
2689
2690    fn size_hint(&self) -> (usize, Option<usize>) {
2691        let sa = self.a.size_hint();
2692        let sb = self.b.size_hint();
2693        (sa.0.min(sb.0), sa.1.zip(sb.1).map(|(ua, ub)| ua.min(ub)))
2694    }
2695}
2696
2697impl<I, J> ExactSizeIterator for ZipEq<I, J>
2698where
2699    I: ExactSizeIterator,
2700    J: ExactSizeIterator,
2701{
2702}
2703
2704pub(crate) fn parse_python_expression(expression: &str) -> Atom {
2705    initialize();
2706    let _ = UFO.metric;
2707    let processed_string = String::from(expression)
2708        .replace("**", "^")
2709        .replace("cmath.sqrt", "sqrt")
2710        .replace("cmath.pi", "pi")
2711        .replace("math.sqrt", "sqrt")
2712        .replace("math.pi", "pi");
2713
2714    parse!(processed_string)
2715}
2716
2717/// Format a mean ± sdev as mean(sdev) with the correct number of digits.
2718/// Based on the Python package gvar.
2719pub fn format_uncertainty(mean: F<f64>, sdev: F<f64>) -> String {
2720    let mean = mean.0;
2721    let sdev = sdev.0;
2722
2723    fn ndec(x: f64, offset: usize) -> i32 {
2724        let mut ans = (offset as f64 - x.log10()) as i32;
2725        if ans > 0 && x * 10.0.powi(ans) >= [0.5, 9.5, 99.5][offset] {
2726            ans -= 1;
2727        }
2728        if ans < 0 { 0 } else { ans }
2729    }
2730    let v = mean;
2731    let dv = sdev.abs();
2732
2733    // special cases
2734    if v.is_nan() || dv.is_nan() {
2735        format!("{:e} ± {:e}", v, dv)
2736    } else if dv.is_infinite() {
2737        format!("{:e} ± inf", v)
2738    } else if v.is_zero() && !(1e-4..1e5).contains(&dv) {
2739        if dv.is_zero() {
2740            "0(0)".to_owned()
2741        } else {
2742            let e = format!("{:.1e}", dv);
2743            let mut ans = e.split('e');
2744            let e1 = ans.next().unwrap();
2745            let e2 = ans.next().unwrap();
2746            "0.0(".to_owned() + e1 + ")e" + e2
2747        }
2748    } else if v.is_zero() {
2749        if dv >= 9.95 {
2750            format!("0({:.0})", dv)
2751        } else if dv >= 0.995 {
2752            format!("0.0({:.1})", dv)
2753        } else {
2754            let ndecimal = ndec(dv, 2);
2755            format!(
2756                "{:.*}({:.0})",
2757                ndecimal as usize,
2758                v,
2759                dv * (10.).powi(ndecimal)
2760            )
2761        }
2762    } else if dv.is_zero() {
2763        let e = format!("{:e}", v);
2764        let mut ans = e.split('e');
2765        let e1 = ans.next().unwrap();
2766        let e2 = ans.next().unwrap();
2767        if e2 != "0" {
2768            e1.to_owned() + "(0)e" + e2
2769        } else {
2770            e1.to_owned() + "(0)"
2771        }
2772    } else if dv > 1e4 * v.abs() {
2773        format!("{:.1e} ± {:.2e}", v, dv)
2774    } else if v.abs() >= 1e6 || v.abs() < 1e-5 {
2775        // exponential notation for large |self.mean|
2776        let exponent = v.abs().log10().floor();
2777        let fac = (10.0).powf(&exponent);
2778        let mantissa = format_uncertainty(F(v / fac), F(dv / fac));
2779        let e = format!("{:.0e}", fac);
2780        let mut ee = e.split('e');
2781        mantissa + "e" + ee.nth(1).unwrap()
2782    }
2783    // normal cases
2784    else if dv >= 9.95 {
2785        if v.abs() >= 9.5 {
2786            format!("{:.0}({:.0})", v, dv)
2787        } else {
2788            let ndecimal = ndec(v.abs(), 1);
2789            format!("{:.*}({:.*})", ndecimal as usize, v, ndecimal as usize, dv)
2790        }
2791    } else if dv >= 0.995 {
2792        if v.abs() >= 0.95 {
2793            format!("{:.1}({:.1})", v, dv)
2794        } else {
2795            let ndecimal = ndec(v.abs(), 1);
2796            format!("{:.*}({:.*})", ndecimal as usize, v, ndecimal as usize, dv)
2797        }
2798    } else {
2799        let ndecimal = ndec(v.abs(), 1).max(ndec(dv, 2));
2800        format!(
2801            "{:.*}({:.0})",
2802            ndecimal as usize,
2803            v,
2804            dv * (10.).powi(ndecimal)
2805        )
2806    }
2807}
2808
2809/// Compare two slices, selecting on length first
2810#[allow(unused)]
2811pub(crate) fn compare_slice<T: Ord>(slice1: &[T], slice2: &[T]) -> Ordering {
2812    match slice1.len().cmp(&slice2.len()) {
2813        Ordering::Equal => (),
2814        non_eq => return non_eq,
2815    }
2816
2817    let l = slice1.len();
2818    // Slice to the loop iteration range to enable bound check
2819    // elimination in the compiler
2820    let lhs = &slice1[..l];
2821    let rhs = &slice2[..l];
2822
2823    for i in 0..l {
2824        match lhs[i].cmp(&rhs[i]) {
2825            Ordering::Equal => (),
2826            non_eq => return non_eq,
2827        }
2828    }
2829
2830    Ordering::Equal
2831}
2832
2833pub trait Signum {
2834    fn multiply_sign(&self, sign: i8) -> Self;
2835}
2836
2837// impl Signum for f128::f128 {
2838//     #[inline]
2839//     fn multiply_sign(&self, sign: i8) -> f128::f128 {
2840//         match sign {
2841//             1 => *self,
2842//             0 => f128::f128::zero(),
2843//             -1 => self.neg(),
2844//             _ => unreachable!("Sign should be -1,0,1"),
2845//         }
2846//     }
2847// }
2848
2849impl Signum for f64 {
2850    #[inline]
2851    fn multiply_sign(&self, sign: i8) -> f64 {
2852        match sign {
2853            1 => *self,
2854            0 => self.zero(),
2855            -1 => -self,
2856            _ => unreachable!("Sign should be -1,0,1"),
2857        }
2858    }
2859}
2860
2861impl Signum for f32 {
2862    #[inline]
2863    fn multiply_sign(&self, sign: i8) -> Self {
2864        match sign {
2865            1 => *self,
2866            0 => 0.0,
2867            -1 => self.neg(),
2868            _ => unreachable!("Sign should be -1,0,1"),
2869        }
2870    }
2871}
2872
2873impl<T: FloatLike> Signum for Complex<F<T>> {
2874    #[inline]
2875    fn multiply_sign(&self, sign: i8) -> Complex<F<T>> {
2876        match sign {
2877            1 => self.clone(),
2878            0 => self.ref_zero(),
2879            -1 => -self.clone(),
2880            _ => unreachable!("Sign should be -1,0,1"),
2881        }
2882    }
2883}
2884
2885impl<T: FloatLike> Signum for FourMomentum<F<T>> {
2886    #[inline]
2887    fn multiply_sign(&self, sign: i8) -> FourMomentum<F<T>> {
2888        match sign {
2889            1 => self.clone(),
2890            0 => self.ref_zero(),
2891            -1 => -self,
2892            _ => unreachable!("Sign should be -1,0,1"),
2893        }
2894    }
2895}
2896
2897#[allow(unused)]
2898#[inline]
2899/// Invert with better precision
2900pub(crate) fn finv<T: FloatLike>(c: Complex<F<T>>) -> Complex<F<T>> {
2901    let norm = c.norm_squared();
2902    c.conj() / norm
2903}
2904
2905#[allow(unused)]
2906#[inline]
2907pub(crate) fn powi<T: FloatLike>(c: Complex<F<T>>, n: i32) -> Complex<F<T>> {
2908    if n.is_negative() {
2909        let u = -n as u64;
2910        finv(c.pow(u))
2911    } else {
2912        let u = n as u64;
2913        c.pow(u)
2914    }
2915}
2916
2917#[allow(unused)]
2918pub(crate) fn evaluate_signature<T>(
2919    signature: &[i8],
2920    momenta: &[FourMomentum<F<T>>],
2921) -> FourMomentum<F<T>>
2922where
2923    T: FloatLike,
2924{
2925    let mut momentum = momenta[0].zero();
2926    for (&sign, mom) in zip_eq(signature, momenta) {
2927        match sign {
2928            0 => {}
2929            1 => momentum += mom,
2930            -1 => momentum -= mom,
2931            _ => {
2932                #[cfg(debug_assertions)]
2933                panic!("Sign should be -1,0,1")
2934            }
2935        }
2936    }
2937
2938    momentum
2939}
2940
2941#[allow(unused)]
2942#[inline]
2943pub(crate) fn pinch_dampening_function<T: FloatLike>(
2944    dampening_arg: F<T>,
2945    delta_t: F<T>,
2946    powers: (u64, u64),
2947    multiplier: f64,
2948) -> F<T> {
2949    // Make sure the function is even in t-tstar
2950    assert!(powers.1.is_multiple_of(2));
2951    let a = dampening_arg.pow(powers.0);
2952    &a / (&a + F::<T>::from_f64(multiplier) * delta_t.pow(powers.1))
2953}
2954
2955pub(crate) fn h_dual<T: FloatLike>(
2956    t: &HyperDual<F<T>>,
2957    tstar: Option<HyperDual<F<T>>>,
2958    sigma: Option<F<T>>,
2959    h_function_settings: &crate::settings::runtime::HFunctionSettings,
2960) -> HyperDual<F<T>> {
2961    let sqrt_pi = new_constant(t, &t.values[0].PI().sqrt());
2962    let sig = if let Some(s) = sigma {
2963        new_constant(t, &s)
2964    } else {
2965        new_constant(t, &F::<T>::from_f64(h_function_settings.sigma))
2966    };
2967    let power = h_function_settings.power;
2968    match h_function_settings.function {
2969        crate::settings::runtime::HFunction::Exponential => {
2970            (-(t.clone() * t) / (sig.clone() * sig.clone())).exp()
2971                * new_constant(t, &F::<T>::from_f64(2_f64))
2972                / (sqrt_pi * sig)
2973        }
2974        crate::settings::runtime::HFunction::PolyExponential => {
2975            // Result of \int_0^{\infty} dt (t/sigma)^{-p} exp(2-t^2/sigma^2-sigma^2/t^2)
2976            let normalisation = match power {
2977                None | Some(0) => {
2978                    sqrt_pi.clone() * &sig / new_constant(t, &F::<T>::from_f64(2_f64))
2979                }
2980                Some(1) => new_constant(t, &F::<T>::from_f64(0.841_568_215_070_771_4)) * &sig,
2981                Some(3) => new_constant(t, &F::<T>::from_f64(1.033_476_847_068_688_6)) * &sig,
2982                Some(4) => new_constant(t, &F::<T>::from_f64(1.329_340_388_179_137)) * &sig,
2983                Some(6) => new_constant(t, &F::<T>::from_f64(2.880_237_507_721_463_7)) * &sig,
2984                Some(7) => new_constant(t, &F::<T>::from_f64(4.783_566_971_347_609)) * &sig,
2985                Some(9) => new_constant(t, &F::<T>::from_f64(16.225_745_976_182_285)) * &sig,
2986                Some(10) => new_constant(t, &F::<T>::from_f64(32.735_007_058_911_25)) * &sig,
2987                Some(12) => new_constant(t, &F::<T>::from_f64(155.837_465_922_583_42)) * &sig,
2988                Some(13) => new_constant(t, &F::<T>::from_f64(364.658_500_356_566_04)) * &sig,
2989                Some(15) => new_constant(t, &F::<T>::from_f64(2_257.637_553_015_473)) * &sig,
2990                Some(16) => new_constant(t, &F::<T>::from_f64(5_939.804_418_537_864)) * &sig,
2991                _ => panic!(
2992                    "Value {} of power in poly exponential h function not supported",
2993                    power.unwrap()
2994                ),
2995            };
2996            let prefactor = match power {
2997                None | Some(0) => normalisation.inv(),
2998                Some(p) => (t.clone() / &sig).inv().pow(p as u64) / normalisation,
2999            };
3000            prefactor
3001                * (new_constant(t, &F::<T>::from_f64(2_f64))
3002                    - (t.clone() * t) / (sig.clone() * &sig)
3003                    - (sig.clone() * sig) / (t.clone() * t))
3004                    .exp()
3005        }
3006        crate::settings::runtime::HFunction::PolyLeftRightExponential => {
3007            // Result of \int_0^{\infty} dt (t/sigma)^{-p} exp( -((t^2/sigma^2 +1)/ (t/sigma) -2) )
3008            let normalisation = match power {
3009                None | Some(0) => new_constant(t, &F::<T>::from_f64(2.066_953_694_137_377)) * &sig,
3010                Some(1) => new_constant(t, &F::<T>::from_f64(1.683_136_430_141_542_8)) * &sig,
3011                Some(3) => new_constant(t, &F::<T>::from_f64(3.750_090_124_278_92)) * &sig,
3012                Some(4) => new_constant(t, &F::<T>::from_f64(9.567_133_942_695_218)) * &sig,
3013                Some(6) => new_constant(t, &F::<T>::from_f64(139.373_101_752_153_5)) * &sig,
3014                Some(7) => new_constant(t, &F::<T>::from_f64(729.317_000_713_132_1)) * &sig,
3015                Some(9) => new_constant(t, &F::<T>::from_f64(32_336.242_742_929_753)) * &sig,
3016                Some(10) => new_constant(t, &F::<T>::from_f64(263_205.217_049_469)) * &sig,
3017                Some(12) => new_constant(t, &F::<T>::from_f64(2.427_503_717_893_097_5e7)) * &sig,
3018                Some(13) => new_constant(t, &F::<T>::from_f64(2.694_265_921_644_289e8)) * &sig,
3019                Some(15) => new_constant(t, &F::<T>::from_f64(9.040_742_057_760_125e12)) * &sig,
3020                Some(16) => new_constant(t, &F::<T>::from_f64(1.452_517_480_246_491_3e14)) * &sig,
3021                _ => panic!(
3022                    "Value {} of power in poly exponential h function not supported",
3023                    power.unwrap()
3024                ),
3025            };
3026
3027            // println!("normalisation: {}", normalisation);
3028            // println!("t: {}", t);
3029            // println!("sig: {}", sig);
3030            // println!("power: {:?}", power);
3031
3032            let prefactor = match power {
3033                None | Some(0) => normalisation.inv(),
3034                Some(p) => (t.clone() / &sig).inv().pow(p as u64) / normalisation,
3035            };
3036
3037            // println!("prefactor: {}", prefactor);
3038            prefactor
3039                * (new_constant(t, &F::<T>::from_f64(2_f64))
3040                    - ((t.clone() * t) / (sig.clone() * &sig) + t.one()) / (t.clone() / &sig))
3041                    .exp()
3042        }
3043        crate::settings::runtime::HFunction::ExponentialCT => {
3044            let delta_t_sq = (tstar.clone().unwrap() - t) * (tstar.clone().unwrap() - t);
3045            let tstar_sq = tstar.clone().unwrap() * tstar.unwrap();
3046            // info!("dampener: {}", dampener);
3047            // info!("delta_t_sq: {}", delta_t_sq);
3048            // info!("tstar_sq: {}", tstar_sq);
3049            // info!(
3050            //     "Exp arg: {}",
3051            //     -sig.inv() * (delta_t_sq / tstar_sq + sig * sig * (dampener * dampener))
3052            // );
3053            // info!(
3054            //     "result: {}",
3055            //     (-sig.inv() * (delta_t_sq / tstar_sq + sig * sig * (dampener * dampener))).exp()
3056            // );
3057            if h_function_settings.enabled_dampening {
3058                let dampener = delta_t_sq.clone() / (delta_t_sq.clone() - &tstar_sq);
3059                (-sig.inv()
3060                    * (delta_t_sq.clone() / tstar_sq
3061                        + sig.clone() * sig * (dampener.clone() * dampener)))
3062                    .exp()
3063            } else {
3064                (-sig.inv() * (delta_t_sq / tstar_sq)).exp()
3065            }
3066        }
3067    }
3068}
3069
3070pub(crate) fn h<T: FloatLike>(
3071    t: &F<T>,
3072    tstar: Option<F<T>>,
3073    sigma: Option<F<T>>,
3074    h_function_settings: &crate::settings::runtime::HFunctionSettings,
3075) -> F<T> {
3076    let sqrt_pi = t.PI().sqrt();
3077    let sig = if let Some(s) = sigma {
3078        s
3079    } else {
3080        F::<T>::from_f64(h_function_settings.sigma)
3081    };
3082    let power = h_function_settings.power;
3083    match h_function_settings.function {
3084        crate::settings::runtime::HFunction::Exponential => {
3085            (-(t.square()) / (sig.square())).exp() * F::<T>::from_f64(2_f64) / (sqrt_pi * &sig)
3086        }
3087        crate::settings::runtime::HFunction::PolyExponential => {
3088            // Result of \int_0^{\infty} dt (t/sigma)^{-p} exp(2-t^2/sigma^2-sigma^2/t^2)
3089            let normalisation = match power {
3090                None | Some(0) => sqrt_pi * &sig / F::<T>::from_f64(2_f64),
3091                Some(1) => F::<T>::from_f64(0.841_568_215_070_771_4) * &sig,
3092                Some(3) => F::<T>::from_f64(1.033_476_847_068_688_6) * &sig,
3093                Some(4) => F::<T>::from_f64(1.329_340_388_179_137) * &sig,
3094                Some(6) => F::<T>::from_f64(2.880_237_507_721_463_7) * &sig,
3095                Some(7) => F::<T>::from_f64(4.783_566_971_347_609) * &sig,
3096                Some(9) => F::<T>::from_f64(16.225_745_976_182_285) * &sig,
3097                Some(10) => F::<T>::from_f64(32.735_007_058_911_25) * &sig,
3098                Some(12) => F::<T>::from_f64(155.837_465_922_583_42) * &sig,
3099                Some(13) => F::<T>::from_f64(364.658_500_356_566_04) * &sig,
3100                Some(15) => F::<T>::from_f64(2_257.637_553_015_473) * &sig,
3101                Some(16) => F::<T>::from_f64(5_939.804_418_537_864) * &sig,
3102                _ => panic!(
3103                    "Value {} of power in poly exponential h function not supported",
3104                    power.unwrap()
3105                ),
3106            };
3107            let prefactor = match power {
3108                None | Some(0) => normalisation.inv(),
3109                Some(p) => (t / &sig).powi(-(p as i32)) / normalisation,
3110            };
3111            prefactor
3112                * (F::<T>::from_f64(2_f64)
3113                    - (t.square()) / (sig.square())
3114                    - (sig.square()) / (t.square()))
3115                .exp()
3116        }
3117        crate::settings::runtime::HFunction::PolyLeftRightExponential => {
3118            // Result of \int_0^{\infty} dt (t/sigma)^{-p} exp( -((t^2/sigma^2 +1)/ (t/sigma) -2) )
3119            let normalisation = match power {
3120                None | Some(0) => F::<T>::from_f64(2.066_953_694_137_377) * &sig,
3121                Some(1) => F::<T>::from_f64(1.683_136_430_141_542_8) * &sig,
3122                Some(3) => F::<T>::from_f64(3.750_090_124_278_92) * &sig,
3123                Some(4) => F::<T>::from_f64(9.567_133_942_695_218) * &sig,
3124                Some(6) => F::<T>::from_f64(139.373_101_752_153_5) * &sig,
3125                Some(7) => F::<T>::from_f64(729.317_000_713_132_1) * &sig,
3126                Some(9) => F::<T>::from_f64(32_336.242_742_929_753) * &sig,
3127                Some(10) => F::<T>::from_f64(263_205.217_049_469) * &sig,
3128                Some(12) => F::<T>::from_f64(2.427_503_717_893_097_5e7) * &sig,
3129                Some(13) => F::<T>::from_f64(2.694_265_921_644_289e8) * &sig,
3130                Some(15) => F::<T>::from_f64(9.040_742_057_760_125e12) * &sig,
3131                Some(16) => F::<T>::from_f64(1.452_517_480_246_491_3e14) * &sig,
3132                _ => panic!(
3133                    "Value {} of power in poly exponential h function not supported",
3134                    power.unwrap()
3135                ),
3136            };
3137
3138            // println!("normalisation: {}", normalisation);
3139            // println!("t: {}", t);
3140            // println!("sig: {}", sig);
3141            // println!("power: {:?}", power);
3142
3143            let prefactor = match power {
3144                None | Some(0) => normalisation.inv(),
3145                Some(p) => (t / &sig).powi(-(p as i32)) / normalisation,
3146            };
3147
3148            // println!("prefactor: {}", prefactor);
3149            prefactor
3150                * (F::<T>::from_f64(2_f64) - ((t.square()) / (sig.square()) + t.one()) / (t / sig))
3151                    .exp()
3152        }
3153        crate::settings::runtime::HFunction::ExponentialCT => {
3154            let delta_t_sq = (tstar.clone().unwrap() - t).square();
3155            let tstar_sq = tstar.unwrap().square();
3156            // info!("dampener: {}", dampener);
3157            // info!("delta_t_sq: {}", delta_t_sq);
3158            // info!("tstar_sq: {}", tstar_sq);
3159            // info!(
3160            //     "Exp arg: {}",
3161            //     -sig.inv() * (delta_t_sq / tstar_sq + sig * sig * (dampener * dampener))
3162            // );
3163            // info!(
3164            //     "result: {}",
3165            //     (-sig.inv() * (delta_t_sq / tstar_sq + sig * sig * (dampener * dampener))).exp()
3166            // );
3167            if h_function_settings.enabled_dampening {
3168                let dampener = delta_t_sq.clone() / (delta_t_sq.clone() - &tstar_sq);
3169                (-sig.inv() * (delta_t_sq.clone() / tstar_sq + sig.square() * (dampener.square())))
3170                    .exp()
3171            } else {
3172                (-sig.inv() * (delta_t_sq / tstar_sq)).exp()
3173            }
3174        }
3175    }
3176}
3177
3178/// Calculate the determinant of any complex-valued input matrix using LU-decomposition.
3179/// Original C-code by W. Gong and D.E. Soper.
3180#[allow(unused)]
3181pub(crate) fn determinant<T: FloatLike>(bb: &[Complex<F<T>>], dimension: usize) -> Complex<F<T>> {
3182    let one = bb[0].re.one();
3183    let zero = one.zero();
3184    // Define matrix related variables.
3185    let mut determinant = Complex::new(one.clone(), zero.clone());
3186    let mut indx = [0; MAX_DIMENSION];
3187    let mut d = 1; // initialize parity parameter
3188
3189    // Inintialize the matrix to be decomposed with the transferred matrix b.
3190    let mut aa = bb.to_vec();
3191
3192    // Define parameters used in decomposition.
3193    let mut imax = 0;
3194    let mut flag = 1;
3195    let mut dumc;
3196    let mut sum;
3197
3198    let mut aamax;
3199    let mut dumr;
3200    let mut vv = vec![zero.clone(); MAX_DIMENSION];
3201
3202    // Get the implicit scaling information.
3203    for i in 0..dimension {
3204        aamax = zero.clone();
3205        for j in 0..dimension {
3206            let r = aa[i * dimension + j].norm_squared();
3207            if r > aamax {
3208                aamax = r;
3209            }
3210        }
3211        // Set a flag to check if the determinant is zero.
3212        if aamax.is_zero() {
3213            flag = 0;
3214        }
3215        // Save the scaling.
3216        vv[i] = aamax.inv();
3217    }
3218    if flag == 1 {
3219        for j in 0..dimension {
3220            for i in 0..j {
3221                sum = aa[i * dimension + j].clone();
3222                for k in 0..i {
3223                    sum -= &aa[i * dimension + k] * &aa[k * dimension + j];
3224                }
3225                aa[i * dimension + j] = sum;
3226            }
3227            //Initialize for the search for largest pivot element.
3228            aamax = zero.clone();
3229            for i in j..dimension {
3230                sum = aa[i * dimension + j].clone();
3231                for k in 0..j {
3232                    sum -= &aa[i * dimension + k] * &aa[k * dimension + j];
3233                }
3234                aa[i * dimension + j] = sum.clone();
3235                // Figure of merit for the pivot.
3236                dumr = &vv[i] * sum.norm_squared();
3237                // Is it better than the best so far?
3238                if dumr >= aamax {
3239                    imax = i;
3240                    aamax = dumr;
3241                }
3242            }
3243            // See if we need to interchange rows.
3244            if j != imax {
3245                for k in 0..dimension {
3246                    dumc = aa[imax * dimension + k].clone();
3247                    aa[imax * dimension + k] = aa[j * dimension + k].clone();
3248                    aa[j * dimension + k] = dumc.clone();
3249                }
3250                // Change the parity of d.
3251                d = -d;
3252                // Interchange the scale factor.
3253                vv[imax] = vv[j].clone();
3254            }
3255            indx[j] = imax;
3256            if j + 1 != dimension {
3257                dumc = aa[j * dimension + j].inv();
3258                for i in j + 1..dimension {
3259                    aa[i * dimension + j] *= &dumc;
3260                }
3261            }
3262        }
3263    }
3264    // Calculate the determinant using the decomposed matrix.
3265    if flag == 0 {
3266        determinant = Complex::new(zero.clone(), zero.clone());
3267    } else {
3268        // Multiply the diagonal elements.
3269        for diagonal in 0..dimension {
3270            determinant *= &aa[diagonal * dimension + diagonal];
3271        }
3272        determinant *= &zero.from_i64(d);
3273    }
3274    determinant
3275}
3276
3277#[allow(unused)]
3278pub(crate) fn next_combination_with_replacement(state: &mut [usize], max_entry: usize) -> bool {
3279    for i in (0..state.len()).rev() {
3280        if state[i] < max_entry {
3281            state[i] += 1;
3282            for j in i + 1..state.len() {
3283                state[j] = state[i]
3284            }
3285            return true;
3286        }
3287    }
3288    false
3289}
3290
3291pub(crate) fn compute_loop_part<T: FloatLike>(
3292    loop_signature: &LoopSignature,
3293    loop_moms: &LoopMomenta<F<T>>,
3294) -> ThreeMomentum<F<T>> {
3295    loop_signature.apply_typed(loop_moms)
3296}
3297
3298pub(crate) fn compute_loop_part_subspace<T: FloatLike>(
3299    loop_signature: &LoopSignature,
3300    loop_moms: &LoopMomenta<F<T>>,
3301    subspace: &SubspaceData,
3302) -> ThreeMomentum<F<T>> {
3303    let projected: LoopSignature = subspace.project_loop_signature(loop_signature).collect();
3304    projected.apply_typed(loop_moms)
3305}
3306
3307pub(crate) fn compute_shift_part<T: FloatLike>(
3308    external_signature: &ExternalSignature,
3309    external_moms: &ExternalFourMomenta<F<T>>,
3310) -> FourMomentum<F<T>> {
3311    external_signature
3312        .apply_typed::<FourMomentum<F<T>>, ExternalIndex, ExternalFourMomenta<F<T>>>(external_moms)
3313}
3314
3315pub(crate) fn compute_shift_part_subspace<T: FloatLike>(
3316    loop_signature: &LoopSignature,
3317    external_signature: &ExternalSignature,
3318    loop_moms: &LoopMomenta<F<T>>,
3319    external_moms: &ExternalThreeMomenta<F<T>>,
3320    subspace: &SubspaceData,
3321) -> ThreeMomentum<F<T>> {
3322    let projected_loop_complement: LoopSignature = subspace
3323        .project_complement_loop_signature(loop_signature)
3324        .collect();
3325
3326    let loop_part = projected_loop_complement.apply_typed(loop_moms);
3327    let external_part: ThreeMomentum<F<T>> = external_signature
3328        .apply_typed::<ThreeMomentum<F<T>>, ExternalIndex, ExternalThreeMomenta<F<T>>>(
3329            external_moms,
3330        );
3331    loop_part + external_part
3332}
3333
3334pub(crate) fn compute_t_part_of_shift_part<T: FloatLike>(
3335    external_signature: &ExternalSignature,
3336    external_moms: &ExternalFourMomenta<F<T>>,
3337) -> F<T> {
3338    // external_signature.panic_validate_basis(external_moms);
3339    external_signature
3340        .apply_iter(external_moms.iter().map(|m| m.temporal.value.clone()))
3341        .unwrap_or(external_moms[ExternalIndex(0)].temporal.value.zero())
3342}
3343
3344// Bilinear form for E-surface defined as sqrt[(k+p1)^2+m1sq] + sqrt[(k+p2)^2+m2sq] + e_shift
3345// The Bilinear system then reads 4 k.a.k + 4 k.n + C = 0
3346#[allow(unused, clippy::type_complexity)]
3347pub(crate) fn one_loop_e_surface_bilinear_form<T: FloatLike>(
3348    p1: &[F<T>; 3],
3349    p2: &[F<T>; 3],
3350    m1_sq: F<T>,
3351    m2_sq: F<T>,
3352    e_shift: F<T>,
3353) -> ([[F<T>; 3]; 3], [F<T>; 3], F<T>) {
3354    let zero = e_shift.zero();
3355    let two = zero.from_i64(2);
3356    let e_shift_sq = e_shift.square();
3357    let p1_sq = p1[0].square() + p1[1].square() + p1[2].square();
3358    let p2_sq = p2[0].square() + p2[1].square() + p2[2].square();
3359
3360    let zeros = [zero.clone(), zero.clone(), zero.clone()];
3361    let mut a = [zeros.clone(), zeros.clone(), zeros.clone()];
3362    a[0][0] = (&p1[0] - &p2[0] - &e_shift) * (&p2[0] - &p1[0] - &e_shift);
3363    a[0][1] = (&p1[0] - &p2[0]) * (&p2[1] - &p1[1]);
3364    a[1][0] = a[0][1].clone();
3365    a[0][2] = (&p1[0] - &p2[0]) * (&p2[2] - &p1[2]);
3366    a[2][0] = a[0][2].clone();
3367    a[1][1] = (&p1[1] - &p2[1] - &e_shift) * (&p2[1] - &p1[1] - &e_shift);
3368    a[1][2] = (&p1[1] - &p2[1]) * (&p2[2] - &p1[2]);
3369    a[2][1] = a[1][2].clone();
3370    a[2][2] = (&p1[2] - &p2[2] - &e_shift) * (&p2[2] - &p1[2] - &e_shift);
3371
3372    let mut b = zeros.clone();
3373    b[0] =
3374        (&p2[0] - &p1[0]) * (&m1_sq - &m2_sq + &p1_sq - &p2_sq) + &e_shift_sq * (&p1[0] + &p2[0]);
3375    b[1] =
3376        (&p2[1] - &p1[1]) * (&m1_sq - &m2_sq + &p1_sq - &p2_sq) + &e_shift_sq * (&p1[1] + &p2[1]);
3377    b[2] =
3378        (&p2[2] - &p1[2]) * (&m1_sq - &m2_sq + &p1_sq - &p2_sq) + &e_shift_sq * (&p1[2] + &p2[2]);
3379
3380    let c = -&e_shift_sq * &e_shift_sq + &two * &e_shift_sq * (&m1_sq + &m2_sq + &p1_sq + &p2_sq)
3381        - (&m1_sq - &m2_sq + &p1_sq - &p2_sq) * (&m1_sq - &m2_sq + &p1_sq - &p2_sq);
3382
3383    (a, b, c)
3384}
3385
3386use color_eyre::Result;
3387use eyre::eyre;
3388#[allow(unused)]
3389use std::fmt::LowerExp;
3390
3391pub trait ApproxEq<U: LowerExp, T: LowerExp>: LowerExp {
3392    fn approx_eq(&self, other: &U, tolerance: &T) -> bool;
3393
3394    fn approx_eq_slice(lhs: &[Self], rhs: &[U], tolerance: &T) -> bool
3395    where
3396        Self: Sized,
3397    {
3398        lhs.iter()
3399            .zip_eq(rhs)
3400            .all(|(l, r)| l.approx_eq(r, tolerance))
3401    }
3402
3403    fn approx_eq_iterator<'a, I, J>(lhs: I, rhs: J, tolerance: &'a T) -> bool
3404    where
3405        Self: Sized + 'a,
3406        U: 'a,
3407        I: IntoIterator<Item = &'a Self>,
3408        J: IntoIterator<Item = &'a U>,
3409    {
3410        lhs.into_iter()
3411            .zip_eq(rhs)
3412            .all(|(l, r)| l.approx_eq(r, tolerance))
3413    }
3414
3415    fn assert_approx_eq(&self, other: &U, tolerance: &T) {
3416        assert!(
3417            self.approx_eq(other, tolerance),
3418            "assert_approx_eq failed: \n{:+e} != \n{:+e} with tolerance {:+e}",
3419            self,
3420            other,
3421            tolerance
3422        )
3423    }
3424    fn approx_eq_res(&self, other: &U, tolerance: &T) -> Result<()> {
3425        if self.approx_eq(other, tolerance) {
3426            Ok(())
3427        } else {
3428            Err(eyre!(
3429                "assert_approx_eq failed: \n{:+e} != \n{:+e} with tolerance {:+e}",
3430                self,
3431                other,
3432                tolerance
3433            ))
3434        }
3435    }
3436}
3437
3438// pub trait ApproxEqable: Real+PartialOrd+ for<'a> RefSub<&'a Self,Output = Self>+IsZero{}
3439
3440// impl<T: Real+PartialOrd+ for<'a> RefSub<&'a T,Output = T>+IsZero> ApproxEqable for T{}
3441
3442impl<T: FloatLike> ApproxEq<F<T>, F<T>> for F<T> {
3443    fn approx_eq(&self, other: &F<T>, tolerance: &F<T>) -> bool {
3444        if other.is_zero() {
3445            self.norm() < tolerance.clone()
3446        } else {
3447            ((self.ref_sub(other)) / other).norm() < tolerance.clone()
3448        }
3449    }
3450}
3451
3452impl<T: FloatLike> ApproxEq<Complex<F<T>>, F<T>> for Complex<F<T>> {
3453    fn approx_eq(&self, other: &Complex<F<T>>, tolerance: &F<T>) -> bool {
3454        if !self.norm().re.approx_eq(&other.norm().re, tolerance) {
3455            return false;
3456        } else if self.norm().is_zero() || other.norm().is_zero() {
3457            return true;
3458        }
3459        let two_pi = self.re.PI() + self.re.PI();
3460        let arg_self = self.arg().rem_euclid(&two_pi);
3461        let arg_other = other.arg().rem_euclid(&two_pi);
3462        if !arg_self.approx_eq(&arg_other, tolerance) {
3463            return false;
3464        }
3465        true
3466    }
3467    fn approx_eq_res(&self, other: &Complex<F<T>>, tolerance: &F<T>) -> Result<()> {
3468        if !self.norm().re.approx_eq(&other.norm().re, tolerance) {
3469            return Err(eyre!(
3470                "Norms are not approximately equal: \n{:+e} != \n{:+e} with tolerance {:+e}",
3471                &self.norm().re,
3472                other.norm().re,
3473                tolerance
3474            ));
3475        } else if self.norm().is_zero() || other.norm().is_zero() {
3476            return Ok(());
3477        }
3478
3479        let two_pi = self.re.PI() + self.re.PI();
3480        let arg_self = self.arg().rem_euclid(&two_pi);
3481        let arg_other = other.arg().rem_euclid(&two_pi);
3482        // let arg_diff = (&self.arg() - &other.arg()).rem_euclid(&two_pi);
3483        // let arg_zero = self.re.zero();
3484        if !arg_self.approx_eq(&arg_other, tolerance) {
3485            return Err(eyre!(
3486                "Phases are not approximately equal: \n{:+e} - \n{:+e}= \n{:+e}!=0 with tolerance {:+e}",
3487                arg_self,
3488                arg_other,
3489                &arg_self - &arg_other,
3490                tolerance
3491            ));
3492        }
3493        Ok(())
3494    }
3495}
3496
3497impl<T: FloatLike> ApproxEq<F<T>, F<T>> for Complex<F<T>> {
3498    fn approx_eq(&self, other: &F<T>, tolerance: &F<T>) -> bool {
3499        self.re.approx_eq(other, tolerance) && self.im.approx_eq(tolerance, tolerance)
3500    }
3501    fn approx_eq_res(&self, other: &F<T>, tolerance: &F<T>) -> Result<()> {
3502        if self.im.approx_eq(tolerance, tolerance) {
3503            return Err(eyre!(
3504                "Non-zero imaginary part: \n{:+e} with tolerance {:+e}",
3505                &self.im,
3506                tolerance
3507            ));
3508        }
3509        if !self.re.approx_eq(other, tolerance) {
3510            return Err(eyre!(
3511                "Real parts are not approximately equal: \n{:+e} != \n{:+e} with tolerance {:+e}",
3512                &self.re,
3513                other,
3514                tolerance
3515            ));
3516        }
3517        Ok(())
3518    }
3519}
3520
3521impl<T: FloatLike> ApproxEq<Complex<F<T>>, F<T>> for F<T> {
3522    fn approx_eq(&self, other: &Complex<F<T>>, tolerance: &F<T>) -> bool {
3523        other.re.approx_eq(self, tolerance) && other.im.approx_eq(tolerance, tolerance)
3524    }
3525
3526    fn approx_eq_res(&self, other: &Complex<F<T>>, tolerance: &F<T>) -> Result<()> {
3527        if other.im.approx_eq(tolerance, tolerance) {
3528            return Err(eyre!(
3529                "Non-zero imaginary part: \n{:+e} with tolerance {:+e}",
3530                &other.im,
3531                tolerance
3532            ));
3533        }
3534        if !other.re.approx_eq(self, tolerance) {
3535            return Err(eyre!(
3536                "Real parts are not approximately equal: \n{:+e} != \n{:+e} with tolerance {:+e}",
3537                &other.re,
3538                self,
3539                tolerance
3540            ));
3541        }
3542        Ok(())
3543    }
3544}
3545
3546#[allow(unused)]
3547pub(crate) fn one_loop_eval_e_surf<T: FloatLike>(
3548    k: &[F<T>; 3],
3549    p1: &[F<T>; 3],
3550    p2: &[F<T>; 3],
3551    m1_sq: F<T>,
3552    m2_sq: F<T>,
3553    e_shift: F<T>,
3554) -> F<T> {
3555    ((&k[0] + &p1[0]) * (&k[0] + &p1[0])
3556        + (&k[1] + &p1[1]) * (&k[1] + &p1[1])
3557        + (&k[2] + &p1[2]) * (&k[2] + &p1[2])
3558        + m1_sq)
3559        .sqrt()
3560        + ((&k[0] + &p2[0]) * (&k[0] + &p2[0])
3561            + (&k[1] + &p2[1]) * (&k[1] + &p2[1])
3562            + (&k[2] + &p2[2]) * (&k[2] + &p2[2])
3563            + m2_sq)
3564            .sqrt()
3565        + e_shift
3566}
3567
3568#[allow(unused)]
3569pub(crate) fn one_loop_eval_e_surf_k_derivative<T: FloatLike>(
3570    k: &[F<T>; 3],
3571    p1: &[F<T>; 3],
3572    p2: &[F<T>; 3],
3573    m1_sq: F<T>,
3574    m2_sq: F<T>,
3575) -> [F<T>; 3] {
3576    let e1 = ((&k[0] + &p1[0]) * (&k[0] + &p1[0])
3577        + (&k[1] + &p1[1]) * (&k[1] + &p1[1])
3578        + (&k[2] + &p1[2]) * (&k[2] + &p1[2])
3579        + m1_sq)
3580        .sqrt();
3581    let e2 = ((&k[0] + &p2[0]) * (&k[0] + &p2[0])
3582        + (&k[1] + &p2[1]) * (&k[1] + &p2[1])
3583        + (&k[2] + &p2[2]) * (&k[2] + &p2[2])
3584        + m2_sq)
3585        .sqrt();
3586    [
3587        (&k[0] + &p1[0]) / &e1 + (&k[0] + &p2[0]) / &e2,
3588        (&k[1] + &p1[1]) / &e1 + (&k[1] + &p2[1]) / &e2,
3589        (&k[2] + &p1[2]) / &e1 + (&k[2] + &p2[2]) / &e2,
3590    ]
3591}
3592
3593#[allow(unused)]
3594pub(crate) fn one_loop_get_e_surf_t_scaling<T: FloatLike>(
3595    k: &[F<T>; 3],
3596    p1: &[F<T>; 3],
3597    p2: &[F<T>; 3],
3598    m1_sq: F<T>,
3599    m2_sq: F<T>,
3600    e_shift: F<T>,
3601) -> [F<T>; 2] {
3602    let zero = e_shift.zero();
3603    let one = zero.one();
3604    let (a, b, c_coef) = one_loop_e_surface_bilinear_form(p1, p2, m1_sq, m2_sq, e_shift);
3605    let mut a_coef = zero.clone();
3606    for i in 0..=2 {
3607        for j in 0..=2 {
3608            a_coef += &k[i] * &a[i][j] * &k[j];
3609        }
3610    }
3611    a_coef *= zero.from_i64(4);
3612    let mut b_coef = zero.clone();
3613    for i in 0..=2 {
3614        b_coef += &k[i] * &b[i];
3615    }
3616    b_coef *= zero.from_i64(4);
3617    let discr = b_coef.square() - zero.from_i64(4) * &a_coef * &c_coef;
3618    if discr < zero {
3619        [zero.clone(), zero.clone()]
3620    } else {
3621        [
3622            (-&b_coef + discr.sqrt()) / (zero.from_i64(2) * &a_coef),
3623            (-&b_coef - discr.sqrt()) / (zero.from_i64(2) * &a_coef),
3624        ]
3625    }
3626}
3627
3628pub(crate) fn box_muller<T: FloatLike>(x1: F<T>, x2: F<T>) -> (F<T>, F<T>) {
3629    let r = (-x1.from_i64(2) * x1.log()).sqrt();
3630    let theta = r.from_i64(2) * r.PI() * x2;
3631    (r.clone() * theta.cos(), r * theta.sin())
3632}
3633
3634pub(crate) fn compute_surface_and_volume<T: FloatLike>(n_dim: usize, radius: F<T>) -> (F<T>, F<T>) {
3635    let mut surface = radius.from_i64(2);
3636    let one = radius.one();
3637    let mut volume = one.clone();
3638    for i in 1..n_dim + 1 {
3639        (surface, volume) = (
3640            one.from_i64(2) * one.PI() * volume,
3641            surface / one.from_i64(i as i64),
3642        );
3643    }
3644    (
3645        surface * radius.pow(n_dim as u64),
3646        volume * radius.pow(n_dim as u64),
3647    )
3648}
3649
3650pub(crate) fn get_n_dim_for_n_loop_momenta(
3651    settings: &SamplingSettings,
3652    n_loop_momenta: usize,
3653    n_edges: Option<usize>, // for tropical parameterization, we need to know the number of edges
3654) -> usize {
3655    if let Some(parameterization_settings) = settings.get_parameterization_settings() {
3656        match parameterization_settings.mode {
3657            ParameterizationMode::HyperSphericalFlat => {
3658                // Because we use Box-Muller, we need to have an even number of angular dimensions
3659                let mut n_dim = 3 * n_loop_momenta;
3660                if n_dim % 2 == 1 {
3661                    n_dim += 1;
3662                }
3663                n_dim + 1
3664            }
3665            ParameterizationMode::HyperSpherical
3666            | ParameterizationMode::Cartesian
3667            | ParameterizationMode::Spherical
3668            | ParameterizationMode::RelativeSpherical
3669            | ParameterizationMode::SphericalCommonRadial
3670            | ParameterizationMode::SphericalProductCommonRadial
3671            | ParameterizationMode::MomentumSpace => 3 * n_loop_momenta,
3672        }
3673    } else {
3674        let tropical_part = 2 * n_edges.expect("No tropical subgraph table generated, please run without tropical sampling or regenerate with tables") - 1;
3675        let d_l = 3 * n_loop_momenta;
3676        if d_l % 2 == 1 {
3677            tropical_part + d_l + 1
3678        } else {
3679            tropical_part + d_l
3680        }
3681    }
3682}
3683
3684pub(crate) fn global_parameterize<T: FloatLike>(
3685    x: &[F<T>],
3686    e_cm: F<T>,
3687    settings: &ParameterizationSettings,
3688) -> (Vec<[F<T>; 3]>, F<T>) {
3689    debug!("b: {}", settings.b);
3690    let zero = e_cm.zero();
3691    let one = zero.one();
3692    match settings.mode {
3693        ParameterizationMode::HyperSpherical | ParameterizationMode::HyperSphericalFlat => {
3694            let mut jac = one.clone();
3695
3696            let radius: F<T> = match settings.mapping {
3697                ParameterizationMapping::Log => {
3698                    // r = e_cm * ln(1 + b*x/(1-x))
3699                    let b = F::<T>::from_f64(settings.b);
3700                    let radius = &e_cm * (&one + &b * &x[0] / (&one - &x[0])).log();
3701                    jac *= &e_cm * &b / (&one - &x[0]) / (&one + &x[0] * (&b - &one));
3702                    radius
3703                }
3704                ParameterizationMapping::Power => {
3705                    // r = e_cm * b * (x/(1-x))^p; p=1 reproduces the linear map.
3706                    let b = F::<T>::from_f64(settings.b);
3707                    let power = F::<T>::from_f64(settings.power);
3708                    let odds = &x[0] / (&one - &x[0]);
3709                    let power_minus_one = &power - &one;
3710                    let radius = &e_cm * &b * odds.powf(&power);
3711                    jac *=
3712                        &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x[0]).square();
3713                    radius
3714                }
3715                ParameterizationMapping::Linear => {
3716                    // r = e_cm * b * x/(1-x)
3717                    let b = F::<T>::from_f64(settings.b);
3718                    let radius = &e_cm * &b * &x[0] / (&one - &x[0]);
3719                    jac *= (&e_cm * &b + &radius).powi(2) / &e_cm / &b;
3720                    radius
3721                }
3722            };
3723            match settings.mode {
3724                ParameterizationMode::HyperSpherical => {
3725                    let phi = zero.from_i64(2) * zero.PI() * &x[1];
3726                    jac *= zero.from_i64(2) * zero.PI();
3727
3728                    let mut cos_thetas = Vec::with_capacity(x.len() - 2);
3729                    let mut sin_thetas = Vec::with_capacity(x.len() - 2);
3730
3731                    for (i, xi) in x[2..].iter().enumerate() {
3732                        let cos_theta = -&one + zero.from_i64(2) * xi;
3733                        jac *= zero.from_i64(2);
3734                        let sin_theta = (&one - cos_theta.square()).sqrt();
3735                        if i > 0 {
3736                            jac *= sin_theta.pow(i as u64);
3737                        }
3738                        cos_thetas.push(cos_theta);
3739                        sin_thetas.push(sin_theta);
3740                    }
3741
3742                    let mut concatenated_vecs = Vec::with_capacity(x.len() / 3);
3743                    let mut base = radius.clone();
3744                    for (cos_theta, sin_theta) in cos_thetas.iter().zip(sin_thetas.iter()) {
3745                        concatenated_vecs.push(&base * cos_theta);
3746                        base *= sin_theta;
3747                    }
3748                    concatenated_vecs.push(&base * phi.cos());
3749                    concatenated_vecs.push(&base * phi.sin());
3750
3751                    jac *= radius.pow((x.len() - 1) as u64); // hyperspherical coords
3752
3753                    (
3754                        concatenated_vecs
3755                            .chunks(3)
3756                            .map(|v| [v[0].clone(), v[1].clone(), v[2].clone()])
3757                            .collect(),
3758                        jac,
3759                    )
3760                }
3761                ParameterizationMode::HyperSphericalFlat => {
3762                    // As we will use Box Muller we expect an even number of random variables
3763                    assert!(x[1..].len().is_multiple_of(2));
3764                    let mut normal_distributed_xs = vec![];
3765                    for x_pair in x[1..].chunks(2) {
3766                        let (z1, z2) = box_muller(x_pair[0].clone(), x_pair[1].clone());
3767                        normal_distributed_xs.push(z1);
3768                        normal_distributed_xs.push(z2);
3769                    }
3770                    // ignore the last variable generated if we had to pad to get the 3*n_loop_momenta
3771                    if normal_distributed_xs.len() % 3 != 0 {
3772                        normal_distributed_xs.pop();
3773                    }
3774                    let curr_norm = normal_distributed_xs[..]
3775                        .iter()
3776                        .map(|x| x.square())
3777                        .reduce(|acc, e| acc + &e)
3778                        .unwrap_or(zero.clone())
3779                        .sqrt();
3780                    let surface =
3781                        compute_surface_and_volume(normal_distributed_xs.len() - 1, radius.clone())
3782                            .0;
3783                    jac *= surface;
3784                    let rescaling_factor = &radius / &curr_norm;
3785                    (
3786                        normal_distributed_xs
3787                            .chunks(3)
3788                            .map(|v| {
3789                                [
3790                                    &v[0] * &rescaling_factor,
3791                                    &v[1] * &rescaling_factor,
3792                                    &v[2] * &rescaling_factor,
3793                                ]
3794                            })
3795                            .collect(),
3796                        jac,
3797                    )
3798                }
3799                _ => unreachable!(),
3800            }
3801        }
3802        ParameterizationMode::Cartesian | ParameterizationMode::Spherical => {
3803            let mut jac = one.clone();
3804            let mut vecs = Vec::with_capacity(x.len() / 3);
3805            for xi in x.chunks(3) {
3806                let (vec_i, jac_i) = parameterize3d(xi, e_cm.clone(), settings);
3807                vecs.push(vec_i);
3808                jac *= jac_i;
3809            }
3810            (vecs, jac)
3811        }
3812        ParameterizationMode::RelativeSpherical => {
3813            if x.len() != 6 {
3814                panic!(
3815                    "relative_spherical parameterization currently requires exactly two loop three-momenta"
3816                );
3817            }
3818            let spherical_settings = ParameterizationSettings {
3819                mode: ParameterizationMode::Spherical,
3820                mapping: settings.mapping.clone(),
3821                b: settings.b,
3822                power: settings.power,
3823                lmb_basis_ids: Default::default(),
3824            };
3825            let (common, common_jac) = parameterize3d(&x[0..3], e_cm.clone(), &spherical_settings);
3826            let (relative, relative_jac) =
3827                parameterize3d(&x[3..6], e_cm.clone(), &spherical_settings);
3828            let half = &one / one.from_i64(2);
3829            let first = [
3830                &common[0] + &half * &relative[0],
3831                &common[1] + &half * &relative[1],
3832                &common[2] + &half * &relative[2],
3833            ];
3834            let second = [
3835                &common[0] - &half * &relative[0],
3836                &common[1] - &half * &relative[1],
3837                &common[2] - &half * &relative[2],
3838            ];
3839            (vec![first, second], common_jac * relative_jac)
3840        }
3841        ParameterizationMode::SphericalCommonRadial => {
3842            if x.is_empty() || !x.len().is_multiple_of(3) {
3843                panic!(
3844                    "spherical_common_radial parameterization requires complete loop three-momenta"
3845                );
3846            }
3847            let n_loop_momenta = x.len() / 3;
3848
3849            let mut jac = one.clone();
3850            let radius = match settings.mapping {
3851                ParameterizationMapping::Log => {
3852                    let b = F::<T>::from_f64(settings.b);
3853                    let radius = &e_cm * (&one + &b * &x[0] / (&one - &x[0])).log();
3854                    jac *= &e_cm * &b / (&one - &x[0]) / (&one + &x[0] * (&b - &one));
3855                    radius
3856                }
3857                ParameterizationMapping::Power => {
3858                    let b = F::<T>::from_f64(settings.b);
3859                    let power = F::<T>::from_f64(settings.power);
3860                    let odds = &x[0] / (&one - &x[0]);
3861                    let power_minus_one = &power - &one;
3862                    let radius = &e_cm * &b * odds.powf(&power);
3863                    jac *=
3864                        &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x[0]).square();
3865                    radius
3866                }
3867                ParameterizationMapping::Linear => {
3868                    let b = F::<T>::from_f64(settings.b);
3869                    let radius = &e_cm * &b * &x[0] / (&one - &x[0]);
3870                    jac *= (&e_cm * &b + &radius).powi(2) / &e_cm / &b;
3871                    radius
3872                }
3873            };
3874
3875            let mut remaining_fraction = one.clone();
3876            let mut stick_jac = one.clone();
3877            let mut radii = Vec::with_capacity(n_loop_momenta);
3878            for i in 0..n_loop_momenta {
3879                let fraction = if i + 1 == n_loop_momenta {
3880                    remaining_fraction.clone()
3881                } else {
3882                    stick_jac *= &remaining_fraction;
3883                    let fraction = &remaining_fraction * &x[1 + i];
3884                    remaining_fraction *= &one - &x[1 + i];
3885                    fraction
3886                };
3887                radii.push(&radius * fraction);
3888            }
3889
3890            jac *= radius.pow((n_loop_momenta - 1) as u64) * stick_jac;
3891            for radius_i in &radii {
3892                jac *= radius_i.square();
3893            }
3894
3895            let angle_offset = n_loop_momenta;
3896            let mut momenta = Vec::with_capacity(n_loop_momenta);
3897            for (i, radius_i) in radii.iter().enumerate() {
3898                let phi = F::<T>::from_f64(2.) * zero.PI() * &x[angle_offset + 2 * i];
3899                jac *= F::<T>::from_f64(2.) * zero.PI();
3900                let cos_theta = -&one + F::<T>::from_f64(2.) * &x[angle_offset + 2 * i + 1];
3901                jac *= F::<T>::from_f64(2.);
3902                let sin_theta = (&one - cos_theta.square()).sqrt();
3903                momenta.push([
3904                    radius_i * &sin_theta * phi.cos(),
3905                    radius_i * &sin_theta * phi.sin(),
3906                    radius_i * cos_theta,
3907                ]);
3908            }
3909
3910            (momenta, jac)
3911        }
3912        ParameterizationMode::SphericalProductCommonRadial => {
3913            if x.len() != 6 {
3914                panic!(
3915                    "spherical_product_common_radial parameterization currently requires exactly two loop three-momenta"
3916                );
3917            }
3918            let mut branch_x = x.to_vec();
3919            let branch_settings = if x[0] < F::<T>::from_f64(0.5) {
3920                branch_x[0] = &x[0] * F::<T>::from_f64(2.0);
3921                ParameterizationSettings {
3922                    mode: ParameterizationMode::Spherical,
3923                    mapping: settings.mapping.clone(),
3924                    b: settings.b,
3925                    power: settings.power,
3926                    lmb_basis_ids: Default::default(),
3927                }
3928            } else {
3929                branch_x[0] = (&x[0] - F::<T>::from_f64(0.5)) * F::<T>::from_f64(2.0);
3930                ParameterizationSettings {
3931                    mode: ParameterizationMode::SphericalCommonRadial,
3932                    mapping: settings.mapping.clone(),
3933                    b: settings.b,
3934                    power: settings.power,
3935                    lmb_basis_ids: Default::default(),
3936                }
3937            };
3938            let (momenta, jac) = global_parameterize(&branch_x, e_cm, &branch_settings);
3939            (momenta, jac * F::<T>::from_f64(2.0))
3940        }
3941        ParameterizationMode::MomentumSpace => (x.as_chunks::<3>().0.to_vec(), one),
3942    }
3943}
3944
3945#[allow(unused)]
3946pub(crate) fn global_inv_parameterize<T: FloatLike>(
3947    moms: &[ThreeMomentum<F<T>>],
3948    e_cm: F<T>,
3949    settings: &ParameterizationSettings,
3950) -> (Vec<F<T>>, F<T>) {
3951    let one = e_cm.one();
3952    let zero = one.zero();
3953
3954    match settings.mode {
3955        ParameterizationMode::HyperSpherical => {
3956            let mut inv_jac = one.clone();
3957            let mut xs = Vec::with_capacity(moms.len() * 3);
3958
3959            let cartesian_xs = moms
3960                .iter()
3961                .flat_map(|lv| lv.clone().into_iter())
3962                .collect::<Vec<F<T>>>();
3963
3964            let mut k_r_sq = cartesian_xs
3965                .iter()
3966                .map(|xi| xi.square())
3967                .reduce(|acc, e| acc + &e)
3968                .unwrap_or(zero.clone());
3969            // cover the degenerate case
3970            if k_r_sq.is_zero() {
3971                return (vec![zero.clone(); cartesian_xs.len()], zero);
3972            }
3973            let k_r = k_r_sq.sqrt();
3974            match settings.mapping {
3975                ParameterizationMapping::Log => {
3976                    let b = F::<T>::from_f64(settings.b);
3977                    let x1 = &one - &b / (-&one + &b + (&k_r / &e_cm).exp());
3978                    inv_jac /= e_cm * &b / (&one - &x1) / (&one + &x1 * (&b - &one));
3979                    xs.push(x1);
3980                }
3981                ParameterizationMapping::Power => {
3982                    let b = F::<T>::from_f64(settings.b);
3983                    let power = F::<T>::from_f64(settings.power);
3984                    let inv_power = &one / &power;
3985                    let odds = (&k_r / (&e_cm * &b)).powf(&inv_power);
3986                    let x1 = &odds / (&one + &odds);
3987                    let power_minus_one = &power - &one;
3988                    inv_jac /=
3989                        &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x1).square();
3990                    xs.push(x1);
3991                }
3992                ParameterizationMapping::Linear => {
3993                    let b = F::<T>::from_f64(settings.b);
3994                    inv_jac /= (&e_cm * &b + &k_r).powi(2) / &e_cm / &b;
3995                    xs.push(&k_r / (&e_cm * &b + &k_r));
3996                }
3997            };
3998
3999            let y = cartesian_xs[cartesian_xs.len() - 2].clone();
4000            let x = cartesian_xs[cartesian_xs.len() - 1].clone();
4001            let xphi = if x < zero {
4002                &one + F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * x.atan2(&y)
4003            } else {
4004                F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * x.atan2(&y)
4005            };
4006            xs.push(xphi);
4007            inv_jac /= F::<T>::from_f64(2.) * zero.PI();
4008
4009            for (i, x) in cartesian_xs[..cartesian_xs.len() - 2].iter().enumerate() {
4010                xs.push(F::<T>::from_f64(0.5) * (&one + x / k_r_sq.sqrt()));
4011                inv_jac /= F::<T>::from_f64(2.);
4012                if i > 0 {
4013                    inv_jac /= (&one - (x * x / &k_r_sq)).sqrt().powi(i as i32);
4014                }
4015                k_r_sq -= x * x;
4016            }
4017
4018            inv_jac /= k_r.powi((cartesian_xs.len() - 1) as i32);
4019
4020            (xs, inv_jac)
4021        }
4022        ParameterizationMode::HyperSphericalFlat => {
4023            panic!(
4024                "Inverse of flat hyperspherical sampling is not available since it is not bijective."
4025            );
4026        }
4027        ParameterizationMode::Cartesian | ParameterizationMode::Spherical => {
4028            let mut inv_jac = one;
4029            let mut xs = Vec::with_capacity(moms.len() * 3);
4030            for (i, mom) in moms.iter().enumerate() {
4031                let (xs_i, inv_jac_i) = inv_parametrize3d(mom, e_cm.clone(), settings);
4032
4033                xs.extend(xs_i);
4034                inv_jac *= inv_jac_i;
4035            }
4036            (xs, inv_jac)
4037        }
4038        ParameterizationMode::RelativeSpherical => {
4039            if moms.len() != 2 {
4040                panic!(
4041                    "relative_spherical inverse parameterization currently requires exactly two loop three-momenta"
4042                );
4043            }
4044            let half = &one / one.from_i64(2);
4045            let common = ThreeMomentum::new(
4046                (&moms[0].px + &moms[1].px) * &half,
4047                (&moms[0].py + &moms[1].py) * &half,
4048                (&moms[0].pz + &moms[1].pz) * &half,
4049            );
4050            let relative = ThreeMomentum::new(
4051                &moms[0].px - &moms[1].px,
4052                &moms[0].py - &moms[1].py,
4053                &moms[0].pz - &moms[1].pz,
4054            );
4055            let spherical_settings = ParameterizationSettings {
4056                mode: ParameterizationMode::Spherical,
4057                mapping: settings.mapping.clone(),
4058                b: settings.b,
4059                power: settings.power,
4060                lmb_basis_ids: Default::default(),
4061            };
4062            let (common_xs, common_inv_jac) =
4063                inv_parametrize3d(&common, e_cm.clone(), &spherical_settings);
4064            let (relative_xs, relative_inv_jac) =
4065                inv_parametrize3d(&relative, e_cm.clone(), &spherical_settings);
4066            (
4067                common_xs.into_iter().chain(relative_xs).collect(),
4068                common_inv_jac * relative_inv_jac,
4069            )
4070        }
4071        ParameterizationMode::SphericalCommonRadial => {
4072            if moms.is_empty() {
4073                panic!(
4074                    "spherical_common_radial inverse parameterization requires at least one loop three-momentum"
4075                );
4076            }
4077
4078            let phi_x = |x: &F<T>, y: &F<T>| {
4079                if y < &zero {
4080                    &one + F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * y.atan2(x)
4081                } else {
4082                    F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * y.atan2(x)
4083                }
4084            };
4085
4086            let radii_sq = moms
4087                .iter()
4088                .map(|mom| mom.px.square() + mom.py.square() + mom.pz.square())
4089                .collect::<Vec<_>>();
4090            let radii = radii_sq
4091                .iter()
4092                .map(|radius_sq| radius_sq.sqrt())
4093                .collect::<Vec<_>>();
4094            let radius = radii
4095                .iter()
4096                .fold(zero.clone(), |acc, radius_i| acc + radius_i);
4097
4098            let mut xs = Vec::with_capacity(3 * moms.len());
4099            if radius.is_zero() || radii.iter().any(F::is_zero) {
4100                xs.resize(3 * moms.len(), zero.clone());
4101                return (xs, zero);
4102            }
4103
4104            let mut inv_jac = one.clone();
4105            let radial_x = match settings.mapping {
4106                ParameterizationMapping::Log => {
4107                    let b = F::<T>::from_f64(settings.b);
4108                    let x1 = &one - &b / (-&one + &b + (&radius / &e_cm).exp());
4109                    inv_jac /= &e_cm * &b / (&one - &x1) / (&one + &x1 * (&b - &one));
4110                    x1
4111                }
4112                ParameterizationMapping::Power => {
4113                    let b = F::<T>::from_f64(settings.b);
4114                    let power = F::<T>::from_f64(settings.power);
4115                    let inv_power = &one / &power;
4116                    let odds = (&radius / (&e_cm * &b)).powf(&inv_power);
4117                    let x1 = &odds / (&one + &odds);
4118                    let power_minus_one = &power - &one;
4119                    inv_jac /=
4120                        &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x1).square();
4121                    x1
4122                }
4123                ParameterizationMapping::Linear => {
4124                    let b = F::<T>::from_f64(settings.b);
4125                    inv_jac /= (&e_cm * &b + &radius).powi(2) / &e_cm / &b;
4126                    &radius / (&e_cm * &b + &radius)
4127                }
4128            };
4129
4130            xs.push(radial_x);
4131            let mut remaining_fraction = one.clone();
4132            let mut stick_jac = one.clone();
4133            for (i, radius_i) in radii.iter().enumerate().take(moms.len() - 1) {
4134                let fraction = radius_i / &radius;
4135                xs.push(&fraction / &remaining_fraction);
4136                stick_jac *= &remaining_fraction;
4137                remaining_fraction -= fraction;
4138                if remaining_fraction.is_zero() && i + 2 < moms.len() {
4139                    return (vec![zero.clone(); 3 * moms.len()], zero);
4140                }
4141            }
4142
4143            inv_jac /= radius.pow((moms.len() - 1) as u64) * stick_jac;
4144            for radius_sq in &radii_sq {
4145                inv_jac /= radius_sq;
4146            }
4147
4148            for (mom, radius_i) in moms.iter().zip(radii.iter()) {
4149                xs.push(phi_x(&mom.px, &mom.py));
4150                inv_jac /= F::<T>::from_f64(2.) * zero.PI();
4151                xs.push(F::<T>::from_f64(0.5) * (&one + &mom.pz / radius_i));
4152                inv_jac /= F::<T>::from_f64(2.);
4153            }
4154
4155            (xs, inv_jac)
4156        }
4157        ParameterizationMode::SphericalProductCommonRadial => {
4158            if moms.len() != 2 {
4159                panic!(
4160                    "spherical_product_common_radial inverse parameterization currently requires exactly two loop three-momenta"
4161                );
4162            }
4163            let spherical_settings = ParameterizationSettings {
4164                mode: ParameterizationMode::Spherical,
4165                mapping: settings.mapping.clone(),
4166                b: settings.b,
4167                power: settings.power,
4168                lmb_basis_ids: Default::default(),
4169            };
4170            let common_radial_settings = ParameterizationSettings {
4171                mode: ParameterizationMode::SphericalCommonRadial,
4172                mapping: settings.mapping.clone(),
4173                b: settings.b,
4174                power: settings.power,
4175                lmb_basis_ids: Default::default(),
4176            };
4177            let (mut xs, product_inv_jac) =
4178                global_inv_parameterize(moms, e_cm.clone(), &spherical_settings);
4179            let (_, common_inv_jac) = global_inv_parameterize(moms, e_cm, &common_radial_settings);
4180            if let Some(first_x) = xs.first_mut() {
4181                *first_x = &*first_x / F::<T>::from_f64(2.0);
4182            }
4183            (xs, product_inv_jac + common_inv_jac)
4184        }
4185        ParameterizationMode::MomentumSpace => (
4186            moms.iter()
4187                .flat_map(|mom| [mom.px.clone(), mom.py.clone(), mom.pz.clone()])
4188                .collect(),
4189            one,
4190        ),
4191    }
4192}
4193
4194/// Map a vector in the unit hypercube to the infinite hypercube.
4195/// Also compute the Jacobian.
4196pub(crate) fn parameterize3d<T: FloatLike>(
4197    x: &[F<T>],
4198    e_cm: F<T>,
4199    settings: &ParameterizationSettings,
4200) -> ([F<T>; 3], F<T>) {
4201    let zero = e_cm.zero();
4202    let one = zero.one();
4203    let mut l_space = [zero.clone(), zero.clone(), zero.clone()];
4204    let mut jac = one.clone();
4205
4206    match settings.mode {
4207        ParameterizationMode::Cartesian => match settings.mapping {
4208            ParameterizationMapping::Log => {
4209                for i in 0..3 {
4210                    let x = x[i].clone();
4211                    l_space[i] = &e_cm * (&x / (&one - &x)).log();
4212                    jac *= &e_cm / (&x - &x * &x);
4213                }
4214            }
4215            ParameterizationMapping::Linear => {
4216                for i in 0..3 {
4217                    let x = x[i].clone();
4218                    l_space[i] = &e_cm * (&one / (&one - &x) - &one / &x);
4219                    jac *= &e_cm * (&one / (&x * &x) + &one / ((&one - &x) * (&one - &x)));
4220                }
4221            }
4222            ParameterizationMapping::Power => {
4223                panic!("Power radial mapping is only supported for spherical coordinates");
4224            }
4225        },
4226        ParameterizationMode::Spherical => {
4227            let radius = match settings.mapping {
4228                ParameterizationMapping::Log => {
4229                    // r = &e_cm * ln(1 + b*&x/(1-&x))
4230                    let x = x[0].clone();
4231                    let b = F::<T>::from_f64(settings.b);
4232                    let radius = &e_cm * (&one + &b * &x / (&one - &x)).log();
4233                    jac *= &e_cm * &b / (&one - &x) / (&one + &x * (&b - &one));
4234
4235                    radius
4236                }
4237                ParameterizationMapping::Power => {
4238                    // r = e_cm * b * (x/(1-x))^p; p=1 reproduces the linear map.
4239                    let x = x[0].clone();
4240                    let b = F::<T>::from_f64(settings.b);
4241                    let power = F::<T>::from_f64(settings.power);
4242                    let odds = &x / (&one - &x);
4243                    let power_minus_one = &power - &one;
4244                    let radius = &e_cm * &b * odds.powf(&power);
4245                    jac *= &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x).square();
4246
4247                    radius
4248                }
4249                ParameterizationMapping::Linear => {
4250                    // r = &e_cm * b * x/(1-x)
4251                    let b = F::<T>::from_f64(settings.b);
4252                    let radius = &e_cm * &b * &x[0] / (&one - &x[0]);
4253                    jac *= (&e_cm * &b + &radius).powi(2) / &e_cm / &b;
4254                    radius
4255                }
4256            };
4257            let phi = F::<T>::from_f64(2.) * zero.PI() * &x[1];
4258            jac *= F::<T>::from_f64(2.) * zero.PI();
4259
4260            let cos_theta = -&one + F::<T>::from_f64(2.) * &x[2]; // out of range
4261            jac *= F::<T>::from_f64(2.);
4262            let sin_theta = (&one - cos_theta.square()).sqrt();
4263
4264            l_space[0] = &radius * &sin_theta * phi.cos();
4265            l_space[1] = &radius * &sin_theta * phi.sin();
4266            l_space[2] = &radius * &cos_theta;
4267
4268            jac *= radius.square(); // spherical coord
4269        }
4270        _ => {
4271            panic!(
4272                "Inappropriate parameterization mapping specified for parameterize: {:?}.",
4273                settings.mode.clone()
4274            );
4275        }
4276    }
4277
4278    (l_space, jac)
4279}
4280
4281pub(crate) fn inv_parametrize3d<T: FloatLike>(
4282    mom: &ThreeMomentum<F<T>>,
4283    e_cm: F<T>,
4284    settings: &ParameterizationSettings,
4285) -> ([F<T>; 3], F<T>) {
4286    let one = e_cm.one();
4287    let zero = one.zero();
4288    if settings.mode != ParameterizationMode::Spherical {
4289        panic!("Inverse mapping is only implemented for spherical coordinates");
4290    }
4291
4292    let mut jac = one.clone();
4293
4294    let x = &mom.px;
4295    let y = &mom.py;
4296    let z = &mom.pz;
4297
4298    let k_r_sq = x.square() + y.square() + z.square();
4299    let k_r = k_r_sq.sqrt();
4300
4301    let x2 = if y < &zero {
4302        &one + F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * y.atan2(x)
4303    } else {
4304        F::<T>::from_f64(0.5) * zero.FRAC_1_PI() * y.atan2(x)
4305    };
4306
4307    // cover the degenerate case
4308    if k_r_sq.is_zero() {
4309        return ([zero.clone(), x2.clone(), zero.clone()], zero.clone());
4310    }
4311
4312    let x1 = match settings.mapping {
4313        ParameterizationMapping::Log => {
4314            let b = F::<T>::from_f64(settings.b);
4315            let x1 = &one - &b / (-&one + &b + (&k_r / &e_cm).exp());
4316            jac /= &e_cm * &b / (&one - &x1) / (&one + &x1 * (&b - &one));
4317            x1
4318        }
4319        ParameterizationMapping::Power => {
4320            let b = F::<T>::from_f64(settings.b);
4321            let power = F::<T>::from_f64(settings.power);
4322            let inv_power = &one / &power;
4323            let odds = (&k_r / (&e_cm * &b)).powf(&inv_power);
4324            let x1 = &odds / (&one + &odds);
4325            let power_minus_one = &power - &one;
4326            jac /= &e_cm * &b * &power * odds.powf(&power_minus_one) / (&one - &x1).square();
4327            x1
4328        }
4329        ParameterizationMapping::Linear => {
4330            let b = F::<T>::from_f64(settings.b);
4331            jac /= (&e_cm * &b + &k_r).powi(2) / &e_cm / &b;
4332            &k_r / (&e_cm * &b + &k_r)
4333        }
4334    };
4335
4336    let x3 = F::<T>::from_f64(0.5) * (&one + z / &k_r);
4337    jac /= F::<T>::from_f64(2.) * zero.PI();
4338    jac /= F::<T>::from_f64(2.);
4339    jac /= k_r.square();
4340
4341    let x = [x1, x2, x3];
4342
4343    (x, jac)
4344}
4345
4346pub const MINUTE: usize = 60;
4347pub const HOUR: usize = 3_600;
4348pub const DAY: usize = 86_400;
4349pub const WEEK: usize = 604_800;
4350pub(crate) fn format_wdhms(seconds: usize) -> String {
4351    let mut compound_duration = vec![];
4352    if seconds == 0 {
4353        compound_duration.push("0s".to_string());
4354        return compound_duration.join(" ");
4355    }
4356
4357    let mut sec = seconds % WEEK;
4358    // weeks
4359    let ws = seconds / WEEK;
4360    if ws != 0 {
4361        compound_duration.push(format!("{ws}w"));
4362    }
4363
4364    // days
4365    let ds = sec / DAY;
4366    sec %= DAY;
4367    if ds != 0 {
4368        compound_duration.push(format!("{ds}d"));
4369    }
4370
4371    // hours
4372    let hs = sec / HOUR;
4373    sec %= HOUR;
4374    if hs != 0 {
4375        compound_duration.push(format!("{hs}h"));
4376    }
4377
4378    // minutes
4379    let ms = sec / MINUTE;
4380    sec %= MINUTE;
4381    if ms != 0 {
4382        compound_duration.push(format!("{ms}m"));
4383    }
4384
4385    // seconds
4386    if sec != 0 {
4387        compound_duration.push(format!("{sec}s"));
4388    }
4389
4390    compound_duration.join(" ")
4391}
4392
4393pub(crate) fn format_wdhms_from_duration(duration: Duration) -> String {
4394    format_wdhms(duration.as_secs() as usize)
4395}
4396
4397#[allow(unused)]
4398pub(crate) fn inverse_gamma_lr(a: f64, p: f64, n_iter: usize) -> f64 {
4399    // this algorithm is taken from https://dl.acm.org/doi/pdf/10.1145/22721.23109
4400
4401    // get an estimate for x0 to start newton iterations.
4402    let q = 1.0 - p;
4403
4404    if (1.0 - 1.0e-8..=1.0 + 1.0e-8).contains(&a) {
4405        return -q.ln();
4406    }
4407
4408    let gamma_a = gamma(a);
4409    let b = q * gamma_a;
4410    let c = 0.577_215_664_901_532_9;
4411
4412    let mut x0 = 0.5;
4413    if a < 1.0 {
4414        if b > 0.6 || (b >= 0.45 && a >= 0.3) {
4415            let u = if b * q > 10e-8 {
4416                (p * gamma(a + 1.0)).powf(a.recip())
4417            } else {
4418                (-q / a - c).exp()
4419            };
4420            x0 = u / (1.0 - u / (a + 1.0));
4421        } else if a < 0.3 && (0.35..=0.6).contains(&b) {
4422            let t = (-c - b).exp();
4423            let u = t * t.exp();
4424            x0 = t * u.exp();
4425        } else if (0.15..=0.35).contains(&b) || ((0.15..0.45).contains(&b) && a >= 0.3) {
4426            let y = -b.ln();
4427            let u = y - (1.0 - a) * y.ln();
4428            x0 = y - (1.0 - a) * y.ln() - (1.0 + (1.0 - a) / (1.0 + u)).ln();
4429        } else if 0.01 < b && b < 0.15 {
4430            let y = -b.ln();
4431            let u = y - (1.0 - a) * y.ln();
4432            x0 = y
4433                - (1.0 - a) * u.ln()
4434                - ((u * u + 2.0 * (3.0 - a) * u + (2.0 - a) * (3.0 - a))
4435                    / (u * u + (5.0 - a) * u + 2.0))
4436                    .ln();
4437        } else if b <= 0.01 {
4438            let y = -b.ln();
4439            let c1 = (a - 1.0) * y.ln();
4440            let c2 = (a - 1.0) * (1.0 + c1);
4441            let c3 = (a - 1.0) * (-0.5 * c1 * c1 + (a - 2.0) * c1 + (3.0 * a - 5.0) * 0.5);
4442            let c4 = (a - 1.0)
4443                * (1.0 / 3.0 * c1 * c1 * c1 - (3.0 * a - 5.0) * 0.5 * c1 * c1
4444                    + (a * a - 6.0 * a + 7.0) * c1
4445                    + (11.0 * a * a - 46.0 * a + 47.0) / 6.0);
4446            let c5 = (a - 1.0)
4447                * (-0.25 * c1 * c1 * c1 * c1
4448                    + (11.0 * a - 7.0) / 6.0 * c1 * c1 * c1
4449                    + (-3.0 * a * a - 13.0) * c1 * c1
4450                    + (2.0 * a * a * a - 25.0 * a * a + 72.0 * a - 61.0) * 0.5 * c1
4451                    + (25.0 * a * a * a - 195.0 * a * a + 477.0 * a - 379.0) / 12.0);
4452            x0 = y + c1 + c2 / (y) + c3 / (y * y) + c4 / (y * y * y) + c5 / (y * y * y * y);
4453
4454            if b <= 1.0e-28 {
4455                return x0;
4456            }
4457        }
4458    } else {
4459        let (pref, tau) = if p < 0.5 { (-1.0, p) } else { (1.0, q) };
4460        let t = (-2.0 * tau.ln()).sqrt();
4461
4462        let a_0 = 3.31125922108741;
4463        let a_1 = 11.6616720288968;
4464        let a_2 = 4.28342155967104;
4465        let a_3 = 0.213623493715853;
4466
4467        let b_1 = 6.61053765625462;
4468        let b_2 = 6.40691597760039;
4469        let b_3 = 1.27364489782223;
4470        let b_4 = 3.611_708_101_884_203e-2;
4471
4472        let t2 = t * t;
4473        let t3 = t2 * t;
4474        let t4 = t3 * t;
4475
4476        let numerator = a_0 + a_1 * t + a_2 * t2 + a_3 * t3;
4477        let denominator = 1.0 + b_1 * t + b_2 * t2 + b_3 * t3 + b_4 * t4;
4478
4479        let s = pref * (t - numerator / denominator);
4480        let s2 = s * s;
4481        let s3 = s * s2;
4482        let s4 = s * s3;
4483        let s5 = s * s4;
4484
4485        let a_sqrt = a.sqrt();
4486
4487        let w = a + s * a_sqrt + (s2 - 1.0) / 3.0 + (s3 - 7.0 * s) / (36.0 * a_sqrt)
4488            - (3.0 * s4 + 7.0 * s2 - 16.0) / (810.0 * a)
4489            + (9.0 * s5 + 256.0 * s3 - 433.0 * s) / (38880.0 * a * a_sqrt);
4490
4491        if a >= 500.0 && (1.0 - w / a).abs() < 1.0e-6 {
4492            return w;
4493        } else if p > 0.5 {
4494            if w < 3.0 * a {
4495                x0 = w;
4496            } else {
4497                let d = 2f64.max(a * (a - 1.0));
4498                if b > 10f64.powf(-d) {
4499                    let u = -b.ln() + (a - 1.0) * w.ln() - (1.0 + (1.0 - a) / (1.0 + w)).ln();
4500                    x0 = -b.ln() + (a - 1.0) * u.ln() - (1.0 + (1.0 - a) / (1.0 + u)).ln();
4501                } else {
4502                    let y = -b.ln();
4503                    let c1 = (a - 1.0) * y.ln();
4504                    let c2 = (a - 1.0) * (1.0 + c1);
4505                    let c3 = (a - 1.0) * (-0.5 * c1 * c1 + (a - 2.0) * c1 + (3.0 * a - 5.0) * 0.5);
4506                    let c4 = (a - 1.0)
4507                        * (1.0 / 3.0 * c1 * c1 * c1 - (3.0 * a - 5.0) * 0.5 * c1 * c1
4508                            + (a * a - 6.0 * a + 7.0) * c1
4509                            + (11.0 * a * a - 46.0 * a + 47.0) / 6.0);
4510                    let c5 = (a - 1.0)
4511                        * (-0.25 * c1 * c1 * c1 * c1
4512                            + (11.0 * a - 7.0) / 6.0 * c1 * c1 * c1
4513                            + (-3.0 * a * a - 13.0) * c1 * c1
4514                            + (2.0 * a * a * a - 25.0 * a * a + 72.0 * a - 61.0) * 0.5 * c1
4515                            + (25.0 * a * a * a - 195.0 * a * a + 477.0 * a - 379.0) / 12.0);
4516                    x0 = y + c1 + c2 / (y) + c3 / (y * y) + c4 / (y * y * y) + c5 / (y * y * y * y);
4517                }
4518            }
4519        } else {
4520            // this part is heavily simplified from the paper, if any issues occur this estimate
4521            // will need more refinement.
4522            let v = (p * gamma(a + 1.0)).ln();
4523            x0 = ((v + w) / a).exp();
4524        }
4525    }
4526
4527    // start iteration
4528    let mut x_n = x0;
4529    for _ in 0..n_iter {
4530        let r = x_n.powf(a - 1.0) * (-x_n).exp() / gamma_a;
4531        if x_n <= 0. {
4532            x_n = 1.0e-16;
4533        }
4534        let t_n = if p <= 0.5 {
4535            (gamma_lr(a, x_n) - p) / r
4536        } else {
4537            -(gamma_ur(a, x_n) - q) / r
4538        };
4539        let w_n = (a - 1.0 - x_n) / 2.0;
4540
4541        let h_n = if t_n.abs() <= 0.1 && (w_n * t_n).abs() <= 0.1 {
4542            t_n + w_n * t_n * t_n
4543        } else {
4544            t_n
4545        };
4546
4547        x_n -= h_n;
4548    }
4549
4550    x_n
4551}
4552
4553#[allow(unused)]
4554pub(crate) fn inv_3x3_sig_matrix(mat: [[isize; 3]; 3]) -> [[isize; 3]; 3] {
4555    let denom = -mat[0][2] * mat[1][1] * mat[2][0]
4556        + mat[0][1] * mat[1][2] * mat[2][0]
4557        + mat[0][2] * mat[1][0] * mat[2][1]
4558        - mat[0][0] * mat[1][2] * mat[2][1]
4559        - mat[0][1] * mat[1][0] * mat[2][2]
4560        + mat[0][0] * mat[1][1] * mat[2][2];
4561    if denom != 1 && denom != -1 {
4562        panic!("Non invertible signature matrix.");
4563    }
4564    let mut inv_mat = [[0; 3]; 3];
4565    inv_mat[0][0] = (-mat[1][2] * mat[2][1] + mat[1][1] * mat[2][2]) * denom;
4566    inv_mat[0][1] = (mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]) * denom;
4567    inv_mat[0][2] = (-mat[0][2] * mat[1][1] + mat[0][1] * mat[1][2]) * denom;
4568    inv_mat[1][0] = (mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]) * denom;
4569    inv_mat[1][1] = (-mat[0][2] * mat[2][0] + mat[0][0] * mat[2][2]) * denom;
4570    inv_mat[1][2] = (mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]) * denom;
4571    inv_mat[2][0] = (-mat[1][1] * mat[2][0] + mat[1][0] * mat[2][1]) * denom;
4572    inv_mat[2][1] = (mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]) * denom;
4573    inv_mat[2][2] = (-mat[0][1] * mat[1][0] + mat[0][0] * mat[1][1]) * denom;
4574
4575    inv_mat
4576}
4577
4578pub mod index_vec;
4579
4580#[allow(unused)]
4581pub(crate) fn format_for_compare_digits(x: F<f64>, y: F<f64>) -> (String, String) {
4582    let mut string_x = format!("{:.16e}", x);
4583    let mut string_y = format!("{:.16e}", y);
4584
4585    #[allow(clippy::comparison_chain)]
4586    if string_x.len() > string_y.len() {
4587        for _ in 0..(string_x.len() - string_y.len()) {
4588            string_y.push(' ');
4589        }
4590    } else if string_y.len() > string_x.len() {
4591        for _ in 0..(string_y.len() - string_x.len()) {
4592            string_x.push(' ');
4593        }
4594    }
4595
4596    let string_vec = string_x
4597        .chars()
4598        .zip(string_y.chars())
4599        .map(|(char_x, char_y)| {
4600            if char_x == char_y {
4601                (char_x.to_string().green(), char_y.to_string().green())
4602            } else {
4603                (char_x.to_string().red(), char_y.to_string().red())
4604            }
4605        })
4606        .collect_vec();
4607
4608    let string_x = string_vec.iter().map(|(x, _)| x).join("");
4609    let string_y = string_vec.iter().map(|(_, y)| y).join("");
4610
4611    (string_x, string_y)
4612}
4613
4614#[allow(unused)]
4615pub(crate) fn format_evaluation_time(time: Duration) -> String {
4616    let seconds = time.as_secs_f64();
4617    let (value, unit) = if seconds >= 1.0 {
4618        (seconds, "s")
4619    } else if seconds >= 1.0e-3 {
4620        (seconds * 1.0e3, "ms")
4621    } else {
4622        (seconds * 1.0e6, "µs")
4623    };
4624
4625    let precision = if value >= 100.0 {
4626        0
4627    } else if value >= 10.0 {
4628        1
4629    } else {
4630        2
4631    };
4632
4633    format!("{value:.precision$} {unit}")
4634}
4635
4636pub(crate) fn duration_from_secs_f64_saturating(time: f64) -> Duration {
4637    if time.is_nan() || time <= 0.0 {
4638        return Duration::ZERO;
4639    }
4640
4641    if time.is_infinite() {
4642        return Duration::MAX;
4643    }
4644
4645    let max_seconds = Duration::MAX.as_secs();
4646    if time >= max_seconds as f64 {
4647        return Duration::MAX;
4648    }
4649
4650    let seconds = time.trunc() as u64;
4651    let fractional = (time - seconds as f64).clamp(0.0, 0.999_999_999_999);
4652    let nanos = (fractional * 1_000_000_000.0) as u32;
4653    Duration::new(seconds, nanos)
4654}
4655
4656pub(crate) fn format_evaluation_time_from_f64(time: f64) -> String {
4657    format_evaluation_time(duration_from_secs_f64_saturating(time))
4658}
4659
4660pub(crate) fn normalize_tabled_separator_rows(rendered: &str) -> String {
4661    let original_lines = rendered.lines().collect_vec();
4662    let visible_lines = original_lines
4663        .iter()
4664        .map(|line| strip_ansi_escape_codes(line).chars().collect_vec())
4665        .collect_vec();
4666
4667    original_lines
4668        .iter()
4669        .enumerate()
4670        .map(|(row_index, line)| {
4671            let visible_line = &visible_lines[row_index];
4672            if is_top_border_row(visible_line) {
4673                return rebuild_top_border_row(&visible_lines, row_index);
4674            }
4675
4676            if is_internal_separator_row(visible_line) {
4677                return rebuild_internal_separator_row(&visible_lines, row_index);
4678            }
4679
4680            if is_bottom_border_row(visible_line) {
4681                return rebuild_bottom_border_row(&visible_lines, row_index);
4682            }
4683
4684            (*line).to_string()
4685        })
4686        .join("\n")
4687}
4688
4689fn rebuild_top_border_row(visible_lines: &[Vec<char>], row_index: usize) -> String {
4690    rebuild_border_row(visible_lines, row_index, '╭', '╮', |_, _, has_below| {
4691        if has_below { '┬' } else { '─' }
4692    })
4693}
4694
4695fn rebuild_internal_separator_row(visible_lines: &[Vec<char>], row_index: usize) -> String {
4696    rebuild_border_row(
4697        visible_lines,
4698        row_index,
4699        '├',
4700        '┤',
4701        |row_index, column, has_below| match (
4702            row_index > 0 && has_vertical_border(&visible_lines[row_index - 1], column),
4703            has_below,
4704        ) {
4705            (false, false) => '─',
4706            (false, true) => '┬',
4707            (true, false) => '┴',
4708            (true, true) => '┼',
4709        },
4710    )
4711}
4712
4713fn rebuild_bottom_border_row(visible_lines: &[Vec<char>], row_index: usize) -> String {
4714    rebuild_border_row(
4715        visible_lines,
4716        row_index,
4717        '╰',
4718        '╯',
4719        |row_index, column, _| {
4720            if row_index > 0 && has_vertical_border(&visible_lines[row_index - 1], column) {
4721                '┴'
4722            } else {
4723                '─'
4724            }
4725        },
4726    )
4727}
4728
4729fn rebuild_border_row<F>(
4730    visible_lines: &[Vec<char>],
4731    row_index: usize,
4732    left: char,
4733    right: char,
4734    mut intersection_for_column: F,
4735) -> String
4736where
4737    F: FnMut(usize, usize, bool) -> char,
4738{
4739    let border = &visible_lines[row_index];
4740    if border.len() < 2 {
4741        return border.iter().collect();
4742    }
4743
4744    let last = border.len() - 1;
4745    let mut rebuilt = String::with_capacity(border.len());
4746    rebuilt.push(left);
4747
4748    for column in 1..last {
4749        let has_vertical_below = row_index + 1 < visible_lines.len()
4750            && has_vertical_border(&visible_lines[row_index + 1], column);
4751        rebuilt.push(intersection_for_column(
4752            row_index,
4753            column,
4754            has_vertical_below,
4755        ));
4756    }
4757
4758    rebuilt.push(right);
4759    rebuilt
4760}
4761
4762fn has_vertical_border(line: &[char], column: usize) -> bool {
4763    line.get(column)
4764        .copied()
4765        .is_some_and(|ch| matches!(ch, '│' | '├' | '┤' | '┬' | '┴' | '┼'))
4766}
4767
4768fn is_internal_separator_row(line: &[char]) -> bool {
4769    matches!(line.first(), Some('├')) && matches!(line.last(), Some('┤'))
4770}
4771
4772fn is_top_border_row(line: &[char]) -> bool {
4773    matches!(line.first(), Some('╭')) && matches!(line.last(), Some('╮'))
4774}
4775
4776fn is_bottom_border_row(line: &[char]) -> bool {
4777    matches!(line.first(), Some('╰')) && matches!(line.last(), Some('╯'))
4778}
4779
4780fn strip_ansi_escape_codes(line: &str) -> String {
4781    let mut stripped = String::with_capacity(line.len());
4782    let mut chars = line.chars().peekable();
4783
4784    while let Some(ch) = chars.next() {
4785        if ch == '\u{1b}' && chars.peek() == Some(&'[') {
4786            chars.next();
4787            for code in chars.by_ref() {
4788                if ('@'..='~').contains(&code) {
4789                    break;
4790                }
4791            }
4792            continue;
4793        }
4794
4795        stripped.push(ch);
4796    }
4797
4798    stripped
4799}
4800
4801pub(crate) fn into_complex_ff64<T: FloatLike>(c: &Complex<F<T>>) -> Complex<F<f64>> {
4802    Complex::new(c.re.into_ff64(), c.im.into_ff64())
4803}
4804
4805#[test]
4806fn complex_compare() {
4807    let ltd = Complex::new(0.11773583919739394, -0.22157450463964778).map(F);
4808
4809    let cff = Complex::new(0.11773583589023458, -0.22157450446824836).map(F);
4810
4811    ltd.approx_eq_res(&cff, &F(0.00000001)).unwrap();
4812}
4813
4814#[cfg(test)]
4815mod formatting_tests {
4816    use super::{ArbPrec, F, FloatLike, f128};
4817
4818    fn assert_has_scientific_exponent(rendered: &str) {
4819        let (mantissa, exponent) = rendered
4820            .split_once('e')
4821            .unwrap_or_else(|| panic!("missing exponent marker in: {rendered}"));
4822        assert!(
4823            !mantissa.is_empty(),
4824            "missing mantissa in scientific output: {rendered}"
4825        );
4826        assert!(
4827            exponent.parse::<i32>().is_ok(),
4828            "invalid exponent in scientific output: {rendered}"
4829        );
4830    }
4831
4832    fn assert_fractional_digits(rendered: &str, expected_digits: usize) {
4833        let (mantissa, _) = rendered
4834            .split_once('e')
4835            .unwrap_or_else(|| panic!("missing exponent marker in: {rendered}"));
4836        let (_, fractional) = mantissa
4837            .trim_start_matches(['+', '-'])
4838            .split_once('.')
4839            .unwrap_or_else(|| panic!("missing decimal point in mantissa: {rendered}"));
4840        assert_eq!(
4841            fractional.len(),
4842            expected_digits,
4843            "unexpected number of fractional digits in: {rendered}"
4844        );
4845    }
4846
4847    fn assert_scientific_formatting_for_backend<T: FloatLike>(x: F<T>) {
4848        let plain = format!("{:e}", x.clone());
4849        assert_has_scientific_exponent(&plain);
4850
4851        let plus = format!("{:+e}", x.clone());
4852        assert_has_scientific_exponent(&plus);
4853        assert!(plus.starts_with('+') || plus.starts_with('-'));
4854
4855        let precision = format!("{:+.16e}", x.clone());
4856        assert_has_scientific_exponent(&precision);
4857        assert!(
4858            precision.contains('.'),
4859            "missing decimal point in scientific output: {precision}"
4860        );
4861
4862        let padded = format!("{:>24.6e}", x);
4863        assert!(
4864            padded.len() >= 24,
4865            "formatted width should be at least requested width: {padded}"
4866        );
4867        assert_has_scientific_exponent(padded.trim_start());
4868    }
4869
4870    #[test]
4871    fn display_matches_underlying_f64() {
4872        let value = F(12.5f64);
4873        assert_eq!(format!("{}", value), "12.5");
4874    }
4875
4876    #[test]
4877    fn lower_exp_keeps_exponent_with_precision() {
4878        let rendered = format!("{:+.16e}", F(1.23456789e-8f64));
4879        assert_has_scientific_exponent(&rendered);
4880        assert_fractional_digits(&rendered, 16);
4881    }
4882
4883    #[test]
4884    fn lower_exp_width_and_alignment_behave_as_expected() {
4885        let rendered = format!("{:>20.4e}", F(12.345f64));
4886        assert_eq!(rendered.len(), 20);
4887        assert_has_scientific_exponent(rendered.trim_start());
4888
4889        let left_aligned = format!("{:<20.4e}", F(12.345f64));
4890        assert_eq!(left_aligned.len(), 20);
4891        assert_has_scientific_exponent(left_aligned.trim_end());
4892    }
4893
4894    #[test]
4895    fn lower_exp_sign_and_zero_pad_behave_as_expected() {
4896        let positive = format!("{:+020.4e}", F(12.345f64));
4897        assert_eq!(positive.len(), 20);
4898        assert!(positive.starts_with('+'));
4899        assert_has_scientific_exponent(&positive);
4900
4901        let negative = format!("{:+020.4e}", F(-12.345f64));
4902        assert_eq!(negative.len(), 20);
4903        assert!(negative.starts_with('-'));
4904        assert_has_scientific_exponent(&negative);
4905    }
4906
4907    #[test]
4908    fn lower_exp_precision_controls_fractional_digits() {
4909        let p2 = format!("{:.2e}", F(1.0f64 / 3.0f64));
4910        assert_fractional_digits(&p2, 2);
4911
4912        let p9 = format!("{:.9e}", F(1.0f64 / 3.0f64));
4913        assert_fractional_digits(&p9, 9);
4914    }
4915
4916    #[test]
4917    fn lower_exp_works_across_float_backends() {
4918        assert_scientific_formatting_for_backend(F::<f64>::from_f64(1.23456789e-8));
4919        assert_scientific_formatting_for_backend(F::<f128>::from_f64(1.23456789e-8));
4920        assert_scientific_formatting_for_backend(F::<ArbPrec>::from_f64(1.23456789e-8));
4921    }
4922
4923    #[test]
4924    fn quad_square_matches_multiplication() {
4925        let zero = F::<f128>::from_f64(0.0);
4926        let zero_sq = zero.square();
4927        assert!(!zero_sq.is_nan());
4928        assert!(!zero_sq.is_infinite());
4929        assert_eq!(zero_sq, zero);
4930
4931        let x = F::<f128>::from_f64(500.0);
4932        let x_sq = x.square();
4933        assert!(!x_sq.is_nan());
4934        assert!(!x_sq.is_infinite());
4935        assert_eq!(x_sq, x * x);
4936        assert_eq!(x_sq.sqrt(), x);
4937    }
4938}
4939
4940pub mod symbols;
4941pub use symbols::{GS, W_};
4942
4943type TensorLibStore = RwLock<TensorLibrary<MixedTensor<F<f64>, ExplicitKey<Aind>>, Aind>>;
4944type FunLibStore =
4945    SymbolLib<RealOrComplexTensor<F<f64>, ShadowedStructure<Aind>>, PanicMissingConcrete>;
4946
4947pub static TENSORLIB: LazyLock<TensorLibStore> = LazyLock::new(|| RwLock::new(hep_lib_atom()));
4948
4949pub static FUN_LIB: LazyLock<FunLibStore> = LazyLock::new(|| {
4950    let mut lib = PanicMissingConcrete::new_lib();
4951    lib.insert(INBUILTS.conj, |a| match a {
4952        RealOrComplexTensor::Complex(c) => RealOrComplexTensor::Complex(c.map_data(|x| x.conj())),
4953        RealOrComplexTensor::Real(r) => RealOrComplexTensor::Real(r),
4954    });
4955    lib
4956});
4957
4958pub static PARAM_FUN_LIB: LazyLock<SymbolLib<ParamTensor<ShadowedStructure<Aind>>, Panic>> =
4959    LazyLock::new(|| {
4960        let mut lib = Panic::new_lib();
4961        lib.insert(INBUILTS.conj, |a: ParamTensor<ShadowedStructure<Aind>>| {
4962            a.map_data_self(|x| x.conj())
4963        });
4964        lib
4965    });
4966
4967pub static INT_FUN_LIB: LazyLock<
4968    SymbolLib<RealOrComplexTensor<i64, ShadowedStructure<Aind>>, PanicMissingConcrete>,
4969> = LazyLock::new(|| {
4970    let mut lib = PanicMissingConcrete::new_lib();
4971    lib.insert(INBUILTS.conj, |a| match a {
4972        RealOrComplexTensor::Complex(c) => RealOrComplexTensor::Complex(c.map_data(|x| x.conj())),
4973        RealOrComplexTensor::Real(r) => RealOrComplexTensor::Real(r),
4974    });
4975    lib
4976});
4977
4978pub static VAKINT: OnceLock<Result<Vakint>> = OnceLock::new();
4979
4980pub fn init_vakint() -> Result<()> {
4981    let r: &Result<Vakint, eyre::Report> =
4982        VAKINT.get_or_init(|| Vakint::new().map_err(|e| eyre!(e.to_string())));
4983
4984    match r {
4985        Ok(_) => Ok(()),
4986        Err(e) => Err(eyre!(e.to_string())),
4987    }
4988}
4989
4990pub fn vakint() -> Result<&'static Vakint> {
4991    let r = VAKINT
4992        .get()
4993        .ok_or_else(|| eyre!("Vakint not initialised"))?;
4994    match r {
4995        Ok(v) => Ok(v),
4996        Err(e) => Err(eyre!(e.to_string())),
4997    }
4998}
4999
5000impl<T: FloatLike> momtrop::float::MomTropFloat for F<T> {
5001    #[inline]
5002    fn PI(&self) -> Self {
5003        self.PI()
5004    }
5005
5006    #[inline]
5007    fn abs(&self) -> Self {
5008        self.abs()
5009    }
5010
5011    #[inline]
5012    fn cos(&self) -> Self {
5013        <F<T> as Real>::cos(self)
5014    }
5015
5016    #[inline]
5017    fn exp(&self) -> Self {
5018        <F<T> as Real>::exp(self)
5019    }
5020
5021    #[inline]
5022    fn one(&self) -> Self {
5023        <F<T> as SymFloatLike>::one(self)
5024    }
5025
5026    #[inline]
5027    fn from_f64(&self, value: f64) -> Self {
5028        F::from_f64(value)
5029    }
5030
5031    #[inline]
5032    fn from_isize(&self, value: isize) -> Self {
5033        Self(self.0.from_i64(value as i64))
5034    }
5035
5036    #[inline]
5037    fn inv(&self) -> Self {
5038        <F<T> as SymFloatLike>::inv(self)
5039    }
5040
5041    #[inline]
5042    fn ln(&self) -> Self {
5043        <F<T> as Real>::log(self)
5044    }
5045
5046    #[inline]
5047    fn powf(&self, power: &Self) -> Self {
5048        <F<T> as Real>::powf(self, power)
5049    }
5050
5051    #[inline]
5052    fn sin(&self) -> Self {
5053        <F<T> as Real>::sin(self)
5054    }
5055
5056    #[inline]
5057    fn sqrt(&self) -> Self {
5058        <F<T> as Real>::sqrt(self)
5059    }
5060
5061    #[inline]
5062    fn zero(&self) -> Self {
5063        <F<T> as SymFloatLike>::zero(self)
5064    }
5065
5066    #[inline]
5067    fn to_f64(&self) -> f64 {
5068        self.into_f64()
5069    }
5070}
5071
5072pub trait Length {
5073    fn len(&self) -> usize;
5074    fn is_empty(&self) -> bool {
5075        self.len() == 0
5076    }
5077}
5078
5079impl<I, T> Length for TiVec<I, T> {
5080    fn len(&self) -> usize {
5081        self.len()
5082    }
5083
5084    fn is_empty(&self) -> bool {
5085        self.is_empty()
5086    }
5087}
5088
5089impl<T> Length for Vec<T> {
5090    fn len(&self) -> usize {
5091        self.len()
5092    }
5093}
5094
5095pub(crate) fn ose_atom_from_index(index: EdgeIndex) -> Atom {
5096    function!(
5097        GS.ose,
5098        usize::from(index) as i64 // Atom::from(FlatIndex::from(0))
5099    )
5100}
5101
5102pub(crate) fn cut_energy(index: EdgeIndex) -> Atom {
5103    function!(GS.energy, usize::from(index) as i64)
5104}
5105
5106pub(crate) fn external_energy_atom_from_index(index: EdgeIndex) -> Atom {
5107    GS.emr_mom(index, Atom::from(ExpandedIndex::from_iter([0])))
5108}
5109
5110pub mod newton_solver;
5111use include_dir::{Dir, include_dir};
5112static BUILTIN_MODELS: Dir = include_dir!("$CARGO_MANIFEST_DIR/../../assets/models/json");
5113
5114pub fn load_generic_model(name: &str) -> Model {
5115    if let Some(file) = BUILTIN_MODELS.get_file(format!("{}/{}.json", name, name)) {
5116        Model::from_str(file.contents_utf8().unwrap().into(), "json").unwrap()
5117    } else {
5118        panic!("Model {} not found in built-in models.", name);
5119    }
5120}
5121
5122#[cfg(test)]
5123pub mod test_utils;
5124
5125#[cfg(feature = "python_api")]
5126impl<'py> pyo3::IntoPyObject<'py> for F<f64> {
5127    type Target = pyo3::types::PyFloat;
5128    type Output = pyo3::Bound<'py, Self::Target>;
5129    type Error = std::convert::Infallible;
5130
5131    #[inline]
5132    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
5133        Ok(pyo3::types::PyFloat::new(py, self.0))
5134    }
5135}
5136
5137#[cfg(feature = "python_api")]
5138impl<'py> pyo3::IntoPyObject<'py> for &F<f64> {
5139    type Target = pyo3::types::PyFloat;
5140    type Output = pyo3::Bound<'py, Self::Target>;
5141    type Error = std::convert::Infallible;
5142
5143    #[inline]
5144    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
5145        (*self).into_pyobject(py)
5146    }
5147}
5148
5149#[cfg(feature = "python_api")]
5150impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for F<f64> {
5151    type Error = pyo3::PyErr;
5152
5153    fn extract(obj: pyo3::Borrowed<'a, 'py, pyo3::types::PyAny>) -> Result<Self, Self::Error> {
5154        obj.extract::<f64>().map(F)
5155    }
5156}