qfall_math/rational/q/arithmetic/
exp.rs

1// Copyright © 2023 Marvin Beckmann and Sven Moog
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! Implementations to call the exponential function on a [`Q`].
10
11use crate::{
12    rational::{PolyOverQ, Q},
13    traits::Evaluate,
14};
15use flint_sys::fmpq::fmpq_mul_2exp;
16
17impl Q {
18    /// Computes `e^self`.
19    ///
20    /// For exponents below 709, the value is converted to [`f64`] for the calculation.
21    /// As a result, the precision is limited to the precision of [`f64`].
22    /// This means that values smaller than `e^-745` are rounded to `0`.
23    /// The [`f64`] calculation is relatively fast.
24    /// For exponents above 709, a different algorithm is used.
25    /// Its error bound is not calculated at this point, but probably below 10%.
26    ///
27    /// Returns `e^self`.
28    ///
29    /// # Examples
30    /// ```
31    /// use qfall_math::rational::Q;
32    ///
33    /// let evaluation = Q::from((17, 3)).exp();
34    /// let e = Q::ONE.exp();
35    /// ```
36    ///
37    /// # Panics ...
38    /// - if the exponent is too large (exponent*log_2(e)) >= 2^36.
39    ///   This would use up more than 64 GB of RAM.
40    pub fn exp(&self) -> Self {
41        // f64::exp starts mapping to f64::INFINITY between 709.7 and 709.8.
42        if self <= &Q::from(709) {
43            Q::from(f64::from(self).exp())
44        } else {
45            // e^x = 2^(log_2(e^x)) = 2^(x*log_2(e))
46            let log_2_of_e = Q::from(f64::log2(1f64.exp()));
47
48            let base_2_exponent = self * log_2_of_e;
49            let base_2_int_exponent: i64 = (&base_2_exponent.floor()).try_into().unwrap();
50            let base_2_remainder_exponent = base_2_exponent - base_2_int_exponent;
51
52            // Divide x*log_2(e) into an integer part i and the remainder r.
53            // 2^(i+r) = 2^i * 2^r
54            // 2^r ~= r+1 for r in [0, 1]
55            let mut out: Q = base_2_remainder_exponent + 1;
56            unsafe { fmpq_mul_2exp(&mut out.value, &out.value, base_2_int_exponent as u64) }
57            out
58        }
59    }
60
61    /// Computes `e^self` using taylor series approximation of the exponential function.
62    ///
63    /// Parameters:
64    /// - `length_taylor_polynomial`: the length of the taylor series
65    ///   approximation of the exponential function
66    ///
67    /// Returns `e^self`.
68    ///
69    /// # Examples
70    /// ```
71    /// use qfall_math::rational::Q;
72    /// use std::str::FromStr;
73    ///
74    /// // sum_{k=0}^999 (17/3)^k/k!
75    /// let evaluation = Q::from((17, 3)).exp_taylor(1000_u32);
76    /// ```
77    pub fn exp_taylor(&self, length_taylor_polynomial: impl Into<u32>) -> Self {
78        let exp_taylor_series = PolyOverQ::exp_function_taylor(length_taylor_polynomial);
79        exp_taylor_series.evaluate(self)
80    }
81}
82
83#[cfg(test)]
84mod test_exp {
85    use super::*;
86    use flint_sys::fmpz::fmpz_get_d_2exp;
87
88    /// Ensure that `e^0 = 1`.
89    #[test]
90    fn zero() {
91        assert_eq!(Q::ONE, Q::ZERO.exp());
92    }
93
94    /// Ensure that `e^1 = e`.
95    #[test]
96    fn one() {
97        let e = Q::ONE.exp();
98
99        assert_eq!(e, Q::from(1f64.exp()));
100    }
101
102    /// Show the exp behavior for large negative inputs zero e^-745, but values smaller than e^-745 do.
103    #[test]
104    fn large_negative_exponent() {
105        let small = Q::from(-745).exp();
106        let small_2 = Q::from((-7451, 10)).exp();
107        let zero = Q::from((-7452, 10)).exp();
108
109        assert_ne!(small, Q::ZERO);
110        assert_eq!(small, small_2);
111        assert_eq!(zero, Q::ZERO);
112    }
113
114    /// Test a large input very close to where exp stops using f64 for calculation.
115    /// It should not saturate f64 into infinity.
116    /// Additionally, the the greater relationship should be correct when transitioning
117    /// to the other algorithm.
118    #[test]
119    fn algorithm_transition_correct() {
120        let result_less_than_709 = (Q::from(709) - Q::from((1, u64::MAX))).exp();
121
122        let result_709 = Q::from(709).exp();
123
124        assert_ne!(f64::from(&result_less_than_709), f64::INFINITY);
125        assert!(result_less_than_709 < result_709);
126    }
127
128    /// Ensure that e^300 is the same when calculated with [`Q`] or [`f64`].
129    #[test]
130    fn medium_exponent() {
131        let a = Q::from(300).exp();
132
133        assert_eq!(a, Q::from(300f64.exp()));
134    }
135
136    /// Ensure that the exponential function for large values outputs different results.
137    #[test]
138    fn large_exponent() {
139        let large_1 = Q::from(u32::MAX).exp();
140        let large_2 = Q::from(u32::MAX - 1).exp();
141
142        assert!(large_1 > large_2);
143    }
144
145    /// Ensure that the exponential function for [`u32::MAX`] has an
146    /// error of at most 6%.
147    #[test]
148    #[allow(clippy::unnecessary_mut_passed)]
149    fn large_exponent_error_bound() {
150        let large = Q::from(u32::MAX).exp();
151
152        // large = e^u32::MAX ~= 1.490856880... × 10^1865280596
153        // <https://www.wolframalpha.com/input?i=1%2Be%5E4294967295>
154
155        // Calculations with these large numbers takes a lot of computing time.
156        // Therefore, we norm them with 2^-6196328016 for further calculations.
157        // 1.490856880 × 10^1865280596 * 2^-6196328016 = 2.42298514666...
158        // 10^1865280596 = 2^6196328016.7006445
159        // value_prime = large * 2^-6196328016
160        let mut exponent = 0;
161        let mut value_prime = unsafe { fmpz_get_d_2exp(&mut exponent, &large.value.num) };
162        value_prime *= 2f64.powi((exponent - 6196328016) as i32);
163
164        // Allow 6% Error (Error Bound chosen based on calculation result)
165        let upper_bound = 1.06 * 2.42298514666;
166        let lower_bound = 0.94 * 2.42298514666;
167
168        assert!(lower_bound < value_prime);
169        assert!(upper_bound > value_prime);
170    }
171}
172
173#[cfg(test)]
174mod test_exp_taylor {
175    use crate::rational::Q;
176
177    /// Ensure that `0` is returned if the length `0` is provided
178    #[test]
179    fn zero_length() {
180        let q = Q::from((17, 3));
181
182        assert_eq!(Q::default(), q.exp_taylor(0_u32));
183    }
184
185    /// Test correct evaluation for some explicit values
186    #[test]
187    fn ten_length_value() {
188        assert_eq!(Q::from((98641, 36288)), Q::ONE.exp_taylor(10_u32));
189        assert_eq!(
190            Q::from((2492063827u64, 1785641760)),
191            Q::from((1, 3)).exp_taylor(10_u32)
192        );
193        assert_eq!(
194            Q::from((5729869, 11160261)),
195            Q::from((-2, 3)).exp_taylor(10_u32)
196        );
197    }
198}