mvpoly/
utils.rs

1//! This module contains functions to work with prime numbers and to compute
2//! dimension of multivariate spaces
3
4use std::collections::HashMap;
5
6/// Naive implementation checking if n is prime
7/// You can also use the structure PrimeNumberGenerator to check if a number is
8/// prime using
9/// ```rust
10/// use mvpoly::utils::PrimeNumberGenerator;
11/// let n = 5;
12/// let mut prime_gen = PrimeNumberGenerator::new();
13/// prime_gen.is_prime(n);
14/// ```
15pub fn is_prime(n: usize) -> bool {
16    if n == 2 {
17        return true;
18    }
19    if n < 2 || n % 2 == 0 {
20        return false;
21    }
22    let mut i = 3;
23    while i * i <= n {
24        if n % i == 0 {
25            return false;
26        }
27        i += 2;
28    }
29    true
30}
31
32/// Given a number n, return the list of prime factors of n, with their
33/// multiplicity
34/// The first argument is the number to factorize, the second argument is the
35/// list of prime numbers to use to factorize the number
36/// The output is a list of tuples, where the first element is the prime number
37/// and the second element is the multiplicity of the prime number in the
38/// factorization of n.
39// IMPROVEME: native algorithm, could be optimized. Use a cache to store
40// the prime factors of the previous numbers
41pub fn naive_prime_factors(n: usize, prime_gen: &mut PrimeNumberGenerator) -> Vec<(usize, usize)> {
42    assert!(n > 0);
43    let mut hash_factors = HashMap::new();
44    let mut n = n;
45    if prime_gen.is_prime(n) {
46        vec![(n, 1)]
47    } else {
48        let mut i = 1;
49        let mut p = prime_gen.get_nth_prime(i);
50        while n != 1 {
51            if n % p == 0 {
52                hash_factors.entry(p).and_modify(|e| *e += 1).or_insert(1);
53                n /= p;
54            } else {
55                i += 1;
56                p = prime_gen.get_nth_prime(i);
57            }
58        }
59        let mut factors = vec![];
60        hash_factors.into_iter().for_each(|(k, v)| {
61            factors.push((k, v));
62        });
63        // sort by the prime number
64        factors.sort();
65        factors
66    }
67}
68
69pub struct PrimeNumberGenerator {
70    primes: Vec<usize>,
71}
72
73impl PrimeNumberGenerator {
74    pub fn new() -> Self {
75        PrimeNumberGenerator { primes: vec![] }
76    }
77
78    /// Generate the nth prime number
79    pub fn get_nth_prime(&mut self, n: usize) -> usize {
80        assert!(n > 0);
81        if n <= self.primes.len() {
82            self.primes[n - 1]
83        } else {
84            while self.primes.len() < n {
85                let mut i = {
86                    if self.primes.is_empty() {
87                        2
88                    } else if self.primes.len() == 1 {
89                        3
90                    } else {
91                        self.primes[self.primes.len() - 1] + 2
92                    }
93                };
94                while !is_prime(i) {
95                    i += 2;
96                }
97                self.primes.push(i);
98            }
99            self.primes[n - 1]
100        }
101    }
102
103    /// Check if a number is prime using the list of prime numbers
104    /// It is different than the is_prime function because it uses the list
105    /// of prime numbers to check if a number is prime instead of checking
106    /// all the numbers up to the square root of n by step of 2.
107    /// This method can be more efficient if the list of prime numbers is
108    /// already computed.
109    pub fn is_prime(&mut self, n: usize) -> bool {
110        if n == 0 || n == 1 {
111            false
112        } else {
113            let mut i = 1;
114            let mut p = self.get_nth_prime(i);
115            while p * p <= n {
116                if n % p == 0 {
117                    return false;
118                }
119                i += 1;
120                p = self.get_nth_prime(i);
121            }
122            true
123        }
124    }
125
126    /// Get the next prime number
127    pub fn get_next_prime(&mut self) -> usize {
128        let n = self.primes.len();
129        self.get_nth_prime(n + 1)
130    }
131
132    pub fn get_first_nth_primes(&mut self, n: usize) -> Vec<usize> {
133        let _ = self.get_nth_prime(n);
134        self.primes.clone()
135    }
136}
137
138impl Iterator for PrimeNumberGenerator {
139    type Item = usize;
140
141    fn next(&mut self) -> Option<Self::Item> {
142        let n = self.primes.len();
143        Some(self.get_nth_prime(n + 1))
144    }
145}
146
147impl Default for PrimeNumberGenerator {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153/// Build mapping from 1..N to the first N prime numbers. It will be used to
154/// encode variables as prime numbers
155/// For instance, if N = 3, i.e. we have the variable $x_1, x_2, x_3$, the
156/// mapping will be [1, 2, 3, 2, 3, 5]
157/// The idea is to encode products of variables as products of prime numbers
158/// and then use the factorization of the products to know which variables must
159/// be fetched while computing a product of variables
160pub fn get_mapping_with_primes<const N: usize>() -> Vec<usize> {
161    let mut primes = PrimeNumberGenerator::new();
162    let mut mapping = vec![0; 2 * N];
163    for (i, v) in mapping.iter_mut().enumerate().take(N) {
164        *v = i + 1;
165    }
166    for (i, v) in mapping.iter_mut().enumerate().skip(N) {
167        *v = primes.get_nth_prime(i - N + 1);
168    }
169    mapping
170}
171
172/// Compute all the possible two factors decomposition of a number n.
173/// It uses a cache where previous values have been computed.
174/// For instance, if n = 6, the function will return [(1, 6), (2, 3), (3, 2), (6, 1)].
175/// The cache might be used to store the results of previous computations.
176/// The cache is a hashmap where the key is the number and the value is the
177/// list of all the possible two factors decomposition.
178/// The hashmap is updated in place.
179/// The third parameter is a precomputed list of prime numbers. It is updated in
180/// place in case new prime numbers are generated.
181pub fn compute_all_two_factors_decomposition(
182    n: usize,
183    cache: &mut HashMap<usize, Vec<(usize, usize)>>,
184    prime_numbers: &mut PrimeNumberGenerator,
185) -> Vec<(usize, usize)> {
186    if cache.contains_key(&n) {
187        cache[&n].clone()
188    } else {
189        let mut factors = vec![];
190        if n == 1 {
191            factors.push((1, 1));
192        } else if prime_numbers.is_prime(n) {
193            factors.push((1, n));
194            factors.push((n, 1));
195        } else {
196            let mut i = 1;
197            let mut p = prime_numbers.get_nth_prime(i);
198            while p * p <= n {
199                if n % p == 0 {
200                    let res = n / p;
201                    let res_factors =
202                        compute_all_two_factors_decomposition(res, cache, prime_numbers);
203                    for (a, b) in res_factors {
204                        let x = (p * a, b);
205                        if !factors.contains(&x) {
206                            factors.push(x);
207                        }
208                        let x = (a, p * b);
209                        if !factors.contains(&x) {
210                            factors.push(x);
211                        }
212                    }
213                }
214                i += 1;
215                p = prime_numbers.get_nth_prime(i);
216            }
217        }
218        cache.insert(n, factors.clone());
219        factors
220    }
221}
222
223/// Compute the list of indices to perform N nested loops of different size
224/// each, whose sum is less than or equal to an optional upper bound.
225/// In other words, if we have to perform the 3 nested loops:
226/// ```rust
227/// let n1 = 3;
228/// let n2 = 3;
229/// let n3 = 5;
230/// for i in 0..n1 {
231///   for j in 0..n2 {
232///     for k in 0..n3 {
233///     }
234///   }
235/// }
236/// ```
237/// the output will be all the possible values of `i`, `j`, and `k`.
238/// The algorithm is as follows:
239/// ```rust
240/// let n1 = 3;
241/// let n2 = 3;
242/// let n3 = 5;
243/// (0..(n1 * n2 * n3)).map(|l| {
244///   let i = l               % n1;
245///   let j = (l / n1)        % n2;
246///   let k = (l / (n1 * n2)) % n3;
247///   (i, j, k)
248/// });
249/// ```
250/// For N nested loops, the algorithm is the same, with the division increasing
251/// by the factor `N_k` for the index `i_(k + 1)`
252///
253/// In the case of an empty list, the function will return a list containing a
254/// single element which is the empty list.
255///
256/// In the case of an empty loop (i.e. one value in the input list is 0), the
257/// expected output is the empty list.
258pub fn compute_indices_nested_loop(
259    nested_loop_sizes: Vec<usize>,
260    upper_bound: Option<usize>,
261) -> Vec<Vec<usize>> {
262    let n = nested_loop_sizes.iter().product();
263    (0..n)
264        .filter_map(|i| {
265            let mut div = 1;
266            // Compute indices for the loop, step i
267            let indices: Vec<usize> = nested_loop_sizes
268                .iter()
269                .map(|n_i| {
270                    let k = (i / div) % n_i;
271                    div *= n_i;
272                    k
273                })
274                .collect();
275            if let Some(upper_bound) = upper_bound {
276                if indices.iter().sum::<usize>() <= upper_bound {
277                    Some(indices)
278                } else {
279                    None
280                }
281            } else {
282                Some(indices)
283            }
284        })
285        .collect()
286}