bulk_gcd/
lib.rs

1//! # bulk-gcd
2//!
3//! This crate computes GCD of each integer in moduli list with the product of
4//! all integers in the same list using [fast algorithm][bernstein] by
5//! D. Bernstein.
6//!
7//! See: [this paper][that paper] for more details and motivation.
8//!
9//! Usage example:
10//! ```rust
11//! extern crate bulk_gcd;
12//! extern crate rug;
13//!
14//! use rug::Integer;
15//!
16//! let moduli = [
17//!     Integer::from(15),
18//!     Integer::from(35),
19//!     Integer::from(23),
20//! ];
21//!
22//! let result = bulk_gcd::compute(&moduli).unwrap();
23//!
24//! assert_eq!(
25//!     result,
26//!     vec![
27//!         Some(Integer::from(5)),
28//!         Some(Integer::from(5)),
29//!         None,
30//!     ]
31//! );
32//! ```
33//!
34//! [bernstein]: https://cr.yp.to/factorization/smoothparts-20040510.pdf
35//! [that paper]: https://factorable.net/weakkeys12.conference.pdf
36extern crate rayon;
37extern crate rug;
38
39#[macro_use]
40extern crate log;
41extern crate env_logger;
42
43mod utils;
44
45use rayon::prelude::*;
46use rug::Integer;
47use std::error::Error;
48use std::fmt;
49use std::fmt::Display;
50use utils::*;
51
52/// Possible computation errors
53#[derive(Debug, PartialEq)]
54pub enum ComputeError {
55    /// Returned when `compute()` is called with 0 or 1 moduli
56    /// Minimum 2 moduli are required for meaningful operation of the function.
57    NotEnoughModuli,
58}
59
60impl Display for ComputeError {
61    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62        write!(f, "ComputeError: {}", self.description())
63    }
64}
65
66impl Error for ComputeError {
67    fn description(&self) -> &str {
68        match self {
69            ComputeError::NotEnoughModuli => "Not enough moduli",
70        }
71    }
72}
73
74pub type ComputeResult = Result<Vec<Option<Integer>>, ComputeError>;
75
76struct ProductTree {
77    levels: Vec<Vec<Integer>>,
78}
79
80fn compute_product_tree(moduli: Vec<Integer>) -> ProductTree {
81    // Root
82    if moduli.len() == 1 {
83        return ProductTree {
84            levels: vec![moduli],
85        };
86    }
87
88    // Node
89    let level = (0..(moduli.len() / 2))
90        .into_par_iter()
91        .map(|i| Integer::from(&moduli[i * 2] * &moduli[i * 2 + 1]))
92        .collect();
93
94    let mut res = compute_product_tree(level);
95    res.levels.push(moduli);
96    res
97}
98
99fn compute_remainders(tree: ProductTree) -> Option<Vec<Integer>> {
100    let level_count = tree.levels.len() - 1;
101    trace!("computing remainders for {} levels", level_count);
102
103    tree.levels
104        .into_iter()
105        .enumerate()
106        .fold(None, |maybe_parent, (level, current)| {
107            let parent = match maybe_parent {
108                None => {
109                    return Some(current);
110                }
111                Some(parent) => parent,
112            };
113
114            trace!("computing remainder level {}/{}", level, level_count);
115            let remainders = current
116                .into_par_iter()
117                .enumerate()
118                .map(|(i, mut value)| {
119                    // value = parent[i / 2] % (value ** 2)
120                    value.square_mut();
121
122                    &parent[i / 2] % value
123                })
124                .collect();
125
126            Some(remainders)
127        })
128}
129
130fn compute_gcds(remainders: &[Integer], moduli: &[Integer]) -> Vec<Integer> {
131    trace!("computing quotients and gcd");
132    remainders
133        .par_iter()
134        .zip(moduli.par_iter())
135        .map(|(remainder, modulo)| {
136            let quotient = Integer::from(remainder / modulo);
137            quotient.gcd(modulo)
138        })
139        .collect()
140}
141
142/// Compute GCD of each integer in the `moduli` with all other integers in it.
143///
144/// Usage example:
145/// ```rust
146/// extern crate bulk_gcd;
147/// extern crate rug;
148///
149/// use rug::Integer;
150///
151/// let moduli = [
152///     Integer::from(15),
153///     Integer::from(35),
154///     Integer::from(23),
155///     Integer::from(49),
156/// ];
157///
158/// let result = bulk_gcd::compute(&moduli).unwrap();
159///
160/// assert_eq!(
161///     result,
162///     vec![
163///         Some(Integer::from(5)),
164///         Some(Integer::from(35)),
165///         None,
166///         Some(Integer::from(7)),
167///     ]
168/// );
169/// ```
170///
171/// NOTE: Minimum 2 `moduli` are required for running the algorithm, otherwise
172/// `NotEnoughModuli` error is returned:
173///
174/// ```rust
175/// extern crate bulk_gcd;
176/// extern crate rug;
177///
178/// use rug::Integer;
179///
180/// assert_eq!(
181///     bulk_gcd::compute(&[]).unwrap_err(),
182///     bulk_gcd::ComputeError::NotEnoughModuli
183/// );
184/// ```
185///
186pub fn compute(moduli: &[Integer]) -> ComputeResult {
187    if moduli.len() < 2 {
188        return Err(ComputeError::NotEnoughModuli);
189    }
190
191    // Pad to the power-of-two len
192    let (padded_moduli, pad_size) = pad_ints(moduli.to_vec());
193    trace!("added {} padding to moduli", pad_size);
194
195    trace!("computing product tree");
196    let tree = compute_product_tree(padded_moduli);
197    let remainders = compute_remainders(tree);
198
199    let gcds = compute_gcds(&unpad_ints(remainders.unwrap(), pad_size), moduli);
200
201    Ok(gcds
202        .into_iter()
203        .map(|gcd| if gcd == 1 { None } else { Some(gcd) })
204        .collect())
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn it_should_fail_on_zero_moduli() {
213        assert!(compute(&[]).is_err());
214    }
215
216    #[test]
217    fn it_should_fail_on_single_moduli() {
218        assert!(compute(&[Integer::new()]).is_err());
219    }
220
221    #[test]
222    fn it_should_return_gcd_of_two_moduli() {
223        let moduli = [Integer::from(6), Integer::from(15)];
224
225        let result = compute(&moduli).unwrap();
226        assert_eq!(
227            result,
228            vec![Some(Integer::from(3)), Some(Integer::from(3)),]
229        );
230    }
231
232    #[test]
233    fn it_should_find_gcd_for_many_moduli() {
234        let moduli = vec![
235            Integer::from(31 * 41),
236            Integer::from(41),
237            Integer::from(61),
238            Integer::from(71 * 31),
239            Integer::from(101 * 131),
240            Integer::from(131 * 151),
241        ];
242
243        let result = compute(&moduli).unwrap();
244
245        assert_eq!(
246            result,
247            vec![
248                Some(Integer::from(31 * 41)),
249                Some(Integer::from(41)),
250                None,
251                Some(Integer::from(31)),
252                Some(Integer::from(131)),
253                Some(Integer::from(131)),
254            ]
255        );
256    }
257}