factorion_lib/
calculation_tasks.rs

1//! This module handles the calulation of pending calculation tasks
2use std::sync::OnceLock;
3
4use crate::calculation_results::Number;
5
6use crate::{
7    calculation_results::{Calculation, CalculationResult},
8    math,
9};
10
11use crate::FLOAT_PRECISION;
12
13use crate::rug::Integer;
14use crate::rug::{Float, ops::Pow};
15
16pub mod recommended {
17    use factorion_math::rug::Complete;
18    use factorion_math::rug::integer::IntegerExt64;
19
20    use crate::rug::Integer;
21    // Limit for exact calculation, set to limit calculation time
22    pub static UPPER_CALCULATION_LIMIT: fn() -> Integer = || 1_000_000.into();
23    // Limit for approximation, set to ensure enough accuracy (5 decimals)
24    pub static UPPER_APPROXIMATION_LIMIT: fn() -> Integer =
25        || Integer::u64_pow_u64(10, 300).complete();
26    // Limit for exact subfactorial calculation, set to limit calculation time
27    pub static UPPER_SUBFACTORIAL_LIMIT: fn() -> Integer = || 1_000_000.into();
28    // Limit for exact termial calculation, set to limit calculation time (absurdly high)
29    pub static UPPER_TERMIAL_LIMIT: fn() -> Integer = || Integer::u64_pow_u64(10, 10000).complete();
30    // Limit for approximation, set to ensure enough accuracy (5 decimals)
31    // Based on max float. (bits)
32    pub static UPPER_TERMIAL_APPROXIMATION_LIMIT: u32 = 1073741822;
33}
34
35static UPPER_CALCULATION_LIMIT: OnceLock<Integer> = OnceLock::new();
36static UPPER_APPROXIMATION_LIMIT: OnceLock<Integer> = OnceLock::new();
37static UPPER_SUBFACTORIAL_LIMIT: OnceLock<Integer> = OnceLock::new();
38static UPPER_TERMIAL_LIMIT: OnceLock<Integer> = OnceLock::new();
39static UPPER_TERMIAL_APPROXIMATION_LIMIT: OnceLock<u32> = OnceLock::new();
40
41use crate::AlreadyInit;
42pub fn init(
43    upper_calculation_limit: Integer,
44    upper_approximation_limit: Integer,
45    upper_subfactorial_limit: Integer,
46    upper_termial_limit: Integer,
47    upper_termial_approximation_limit: u32,
48) -> Result<(), AlreadyInit> {
49    static INITIALIZING: std::sync::Mutex<()> = std::sync::Mutex::new(());
50    let _guard = INITIALIZING.lock();
51    UPPER_CALCULATION_LIMIT
52        .set(upper_calculation_limit)
53        .map_err(|_| AlreadyInit)?;
54    UPPER_APPROXIMATION_LIMIT
55        .set(upper_approximation_limit)
56        .map_err(|_| AlreadyInit)?;
57    UPPER_SUBFACTORIAL_LIMIT
58        .set(upper_subfactorial_limit)
59        .map_err(|_| AlreadyInit)?;
60    UPPER_TERMIAL_LIMIT
61        .set(upper_termial_limit)
62        .map_err(|_| AlreadyInit)?;
63    UPPER_TERMIAL_APPROXIMATION_LIMIT
64        .set(upper_termial_approximation_limit)
65        .map_err(|_| AlreadyInit)?;
66    Ok(())
67}
68pub fn init_default() -> Result<(), AlreadyInit> {
69    use recommended::*;
70    init(
71        UPPER_CALCULATION_LIMIT(),
72        UPPER_APPROXIMATION_LIMIT(),
73        UPPER_SUBFACTORIAL_LIMIT(),
74        UPPER_TERMIAL_LIMIT(),
75        UPPER_TERMIAL_APPROXIMATION_LIMIT,
76    )
77}
78
79/// Representation of the calculation to be done
80#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
81pub struct CalculationJob {
82    pub base: CalculationBase,
83    /// Type of the calculation
84    pub level: i32,
85    /// Number of negations encountered
86    pub negative: u32,
87}
88/// The basis of a calculation, wheter [Number] or [CalculationJob].
89#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
90pub enum CalculationBase {
91    Num(Number),
92    Calc(Box<CalculationJob>),
93}
94
95impl CalculationJob {
96    /// Execute the calculation. \
97    /// If include_steps is enabled, will return all intermediate results.
98    pub fn execute(self, include_steps: bool) -> Vec<Option<Calculation>> {
99        let CalculationJob {
100            mut base,
101            mut level,
102            mut negative,
103        } = self;
104        let size = {
105            let mut n = 1;
106            let mut b = &base;
107            while let CalculationBase::Calc(inner) = b {
108                n += 1;
109                b = &inner.base;
110            }
111            n
112        };
113        // TODO: Maybe ignore include steps if size is too big (we can't respond properly anyway)
114        let mut steps = Vec::with_capacity(size);
115        let mut calcs = loop {
116            match base {
117                CalculationBase::Num(num) => {
118                    break vec![
119                        Self::calculate_appropriate_factorial(num.clone(), level, negative).map(
120                            |res| Calculation {
121                                value: num,
122                                steps: vec![(level, negative)],
123                                result: res,
124                            },
125                        ),
126                    ];
127                }
128                CalculationBase::Calc(calc) => {
129                    steps.push((level, negative));
130                    CalculationJob {
131                        base,
132                        level,
133                        negative,
134                    } = *calc;
135                }
136            }
137        };
138        for (level, negative) in steps.into_iter().rev() {
139            let calc = if include_steps {
140                calcs.last().cloned()
141            } else {
142                calcs.pop()
143            };
144            match calc {
145                Some(Some(Calculation {
146                    result: res,
147                    mut steps,
148                    value: number,
149                })) => {
150                    let factorial = Self::calculate_appropriate_factorial(res, level, negative)
151                        .map(|res| {
152                            steps.push((level, negative));
153                            Calculation {
154                                value: number,
155                                steps,
156                                result: res,
157                            }
158                        });
159                    calcs.push(factorial);
160                }
161                _ => return calcs,
162            };
163        }
164        calcs
165    }
166    fn calculate_appropriate_factorial(
167        num: Number,
168        level: i32,
169        negative: u32,
170    ) -> Option<CalculationResult> {
171        let prec = *FLOAT_PRECISION
172            .get()
173            .expect("Limit uninitialized, use init");
174        let calc_num = match &num {
175            CalculationResult::Approximate(base, exponent) => {
176                let res = base.as_float() * Float::with_val(prec, 10).pow(exponent);
177                if Float::is_finite(&(res.clone() * math::APPROX_FACT_SAFE_UPPER_BOUND_FACTOR)) {
178                    res.to_integer().unwrap()
179                } else {
180                    return Some(if base.as_float() < &0.0 {
181                        CalculationResult::ComplexInfinity
182                    } else if level < 0 {
183                        let termial = math::approximate_approx_termial(
184                            (Float::from(base.clone()), exponent.clone()),
185                            -level as u32,
186                        );
187                        CalculationResult::Approximate(termial.0.into(), termial.1)
188                    } else {
189                        CalculationResult::ApproximateDigitsTower(
190                            false,
191                            false,
192                            1,
193                            math::length(exponent, prec) + exponent,
194                        )
195                    });
196                }
197            }
198            CalculationResult::ApproximateDigits(was_neg, digits) => {
199                return Some(if digits.is_negative() {
200                    CalculationResult::Float(Float::new(prec).into())
201                } else if *was_neg {
202                    CalculationResult::ComplexInfinity
203                } else if level < 0 {
204                    CalculationResult::ApproximateDigits(false, (digits.clone() - 1) * 2 + 1)
205                } else {
206                    CalculationResult::ApproximateDigitsTower(
207                        false,
208                        false,
209                        1,
210                        math::length(digits, prec) + digits,
211                    )
212                });
213            }
214            CalculationResult::ApproximateDigitsTower(was_neg, neg, depth, exponent) => {
215                return Some(if *neg {
216                    CalculationResult::Float(Float::new(prec).into())
217                } else if *was_neg {
218                    CalculationResult::ComplexInfinity
219                } else if level < 0 {
220                    CalculationResult::ApproximateDigitsTower(
221                        false,
222                        false,
223                        *depth,
224                        exponent.clone(),
225                    )
226                } else {
227                    CalculationResult::ApproximateDigitsTower(
228                        false,
229                        false,
230                        depth + 1,
231                        exponent.clone(),
232                    )
233                });
234            }
235            CalculationResult::ComplexInfinity => return Some(CalculationResult::ComplexInfinity),
236            Number::Float(num) => match level {
237                ..-1 => {
238                    // We don't support multitermials of decimals
239                    return None;
240                }
241                -1 => {
242                    let res: Float = math::fractional_termial(num.as_float().clone())
243                        * if negative % 2 != 0 { -1 } else { 1 };
244                    if res.is_finite() {
245                        return Some(CalculationResult::Float(res.into()));
246                    } else {
247                        num.as_float().to_integer()?
248                    }
249                }
250                0 => {
251                    // We don't support subfactorials of deciamals
252                    return None;
253                }
254                1 => {
255                    let res: Float = math::fractional_factorial(num.as_float().clone())
256                        * if negative % 2 != 0 { -1 } else { 1 };
257                    if res.is_finite() {
258                        return Some(CalculationResult::Float(res.into()));
259                    } else {
260                        num.as_float().to_integer()?
261                    }
262                }
263                2.. => {
264                    let res: Float =
265                        math::fractional_multifactorial(num.as_float().clone(), level as u32)
266                            * if negative % 2 != 0 { -1 } else { 1 };
267                    if res.is_finite() {
268                        return Some(CalculationResult::Float(res.into()));
269                    } else {
270                        num.as_float().to_integer()?
271                    }
272                }
273            },
274            Number::Exact(num) => num.clone(),
275        };
276        if level > 0 {
277            Some(if calc_num < 0 && level == 1 {
278                CalculationResult::ComplexInfinity
279            } else if calc_num < 0 {
280                let factor = math::negative_multifacorial_factor(calc_num.clone(), level);
281                match (factor, -level - 1 > calc_num) {
282                    (Some(factor), true) => {
283                        let mut res = Self::calculate_appropriate_factorial(
284                            Number::Exact(-calc_num.clone() - level),
285                            level,
286                            negative,
287                        )?;
288                        res = match res {
289                            CalculationResult::Exact(n) => {
290                                let n = Float::with_val(prec, n);
291                                CalculationResult::Float((factor / n).into())
292                            }
293                            CalculationResult::Approximate(b, e) => {
294                                let (b, e) =
295                                    math::adjust_approximate((factor / Float::from(b), -e));
296                                CalculationResult::Approximate(b.into(), e)
297                            }
298                            CalculationResult::ApproximateDigits(wn, n) => {
299                                CalculationResult::ApproximateDigits(wn, -n)
300                            }
301                            CalculationResult::ApproximateDigitsTower(
302                                wn,
303                                negative,
304                                depth,
305                                base,
306                            ) => CalculationResult::ApproximateDigitsTower(
307                                wn, !negative, depth, base,
308                            ),
309                            CalculationResult::ComplexInfinity => {
310                                CalculationResult::Exact(0.into())
311                            }
312                            CalculationResult::Float(f) => {
313                                CalculationResult::Float((factor / Float::from(f)).into())
314                            }
315                        };
316
317                        res
318                    }
319                    (factor, _) => factor
320                        .map(CalculationResult::Exact)
321                        .unwrap_or(CalculationResult::ComplexInfinity),
322                }
323                // Check if we can approximate the number of digits
324            } else if calc_num
325                > *UPPER_APPROXIMATION_LIMIT
326                    .get()
327                    .expect("Limit uninitialized, use init")
328            {
329                let factorial =
330                    math::approximate_multifactorial_digits(calc_num.clone(), level as u32, prec);
331                CalculationResult::ApproximateDigits(negative % 2 != 0, factorial)
332            // Check if the number is within a reasonable range to compute
333            } else if calc_num
334                > *UPPER_CALCULATION_LIMIT
335                    .get()
336                    .expect("Limit uninitialized, use init")
337            {
338                let factorial = if level == 0 {
339                    math::approximate_factorial(calc_num.clone(), prec)
340                } else {
341                    math::approximate_multifactorial(calc_num.clone(), level as u32, prec)
342                };
343                CalculationResult::Approximate(
344                    ((factorial.0 * if negative % 2 != 0 { -1 } else { 1 }) as Float).into(),
345                    factorial.1,
346                )
347            } else {
348                let calc_num = calc_num.to_u64().expect("Failed to convert BigInt to u64");
349                let factorial = math::factorial(calc_num, level as u32)
350                    * if negative % 2 != 0 { -1 } else { 1 };
351                CalculationResult::Exact(factorial)
352            })
353        } else if level == 0 {
354            Some(if calc_num < 0 {
355                CalculationResult::ComplexInfinity
356            } else if calc_num
357                > *UPPER_APPROXIMATION_LIMIT
358                    .get()
359                    .expect("Limit uninitialized, use init")
360            {
361                let factorial = math::approximate_multifactorial_digits(calc_num.clone(), 1, prec);
362                CalculationResult::ApproximateDigits(negative % 2 != 0, factorial)
363            } else if calc_num
364                > *UPPER_SUBFACTORIAL_LIMIT
365                    .get()
366                    .expect("Limit uninitialized, use init")
367            {
368                let factorial = math::approximate_subfactorial(calc_num.clone(), prec);
369                CalculationResult::Approximate(
370                    ((factorial.0 * if negative % 2 != 0 { -1 } else { 1 }) as Float).into(),
371                    factorial.1,
372                )
373            } else {
374                let calc_num = calc_num.to_u64().expect("Failed to convert BigInt to u64");
375                let factorial =
376                    math::subfactorial(calc_num) * if negative % 2 != 0 { -1 } else { 1 };
377                CalculationResult::Exact(factorial)
378            })
379        } else if level < 0 {
380            Some(
381                if calc_num.significant_bits()
382                    > *UPPER_TERMIAL_APPROXIMATION_LIMIT
383                        .get()
384                        .expect("Limit uninitialized, use init")
385                {
386                    let termial = math::approximate_termial_digits(calc_num, -level as u32, prec);
387                    CalculationResult::ApproximateDigits(negative % 2 != 0, termial)
388                } else if calc_num
389                    > *UPPER_TERMIAL_LIMIT
390                        .get()
391                        .expect("Limit uninitialized, use init")
392                {
393                    let termial = math::approximate_termial(calc_num, -level as u32, prec);
394                    CalculationResult::Approximate(
395                        ((termial.0 * if negative % 2 != 0 { -1 } else { 1 }) as Float).into(),
396                        termial.1,
397                    )
398                } else {
399                    let termial = if level < -1 {
400                        math::multitermial(calc_num, -level as u32)
401                    } else {
402                        math::termial(calc_num)
403                    };
404                    let termial = termial * if negative % 2 != 0 { -1 } else { 1 };
405                    CalculationResult::Exact(termial)
406                },
407            )
408        } else {
409            unreachable!()
410        }
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use factorion_math::recommended::FLOAT_PRECISION;
418
419    #[test]
420    fn test_unsupported_calcs() {
421        // Subfactorial
422        let job = CalculationJob {
423            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
424            level: 0,
425            negative: 0,
426        };
427        assert_eq!(job.execute(false), vec![None]);
428        // Multitermial
429        let job = CalculationJob {
430            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
431            level: -2,
432            negative: 0,
433        };
434        assert_eq!(job.execute(false), vec![None]);
435        let job = CalculationJob {
436            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
437            level: -51,
438            negative: 0,
439        };
440        assert_eq!(job.execute(false), vec![None]);
441    }
442}