Skip to main content

gammalooprs/utils/
mod.rs

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