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}