I have a piece of code that attempts to reduce the work and span in making a Fibonacci sequence. My approach is to parallelize the code as much as possible, along with doing matrix multiplication and exponentiation. Here is my Rust code:
use num_bigint::BigUint;
use rayon::iter::*;
use rayon::prelude::ParallelSlice;
use rayon::join;
fn mat_ops(base: (BigUint, BigUint, BigUint, BigUint), mut exponent: u32) -> (BigUint, BigUint, BigUint, BigUint) {
if exponent == 0 {
return (BigUint::from(1u32), BigUint::from(0u32), BigUint::from(0u32), BigUint::from(1u32)); // Identity matrix
}
let mut result = (BigUint::from(1u32), BigUint::from(0u32), BigUint::from(0u32), BigUint::from(1u32));
let mut current_base = base;
while exponent > 0 {
if exponent % 2 == 1 {
result = (
&result.0 * ¤t_base.0 + &result.1 * ¤t_base.2,
&result.0 * ¤t_base.1 + &result.1 * ¤t_base.3,
&result.2 * ¤t_base.0 + &result.3 * ¤t_base.2,
&result.2 * ¤t_base.1 + &result.3 * ¤t_base.3,
);
}
current_base = (
¤t_base.0 * ¤t_base.0 + ¤t_base.1 * ¤t_base.2,
¤t_base.0 * ¤t_base.1 + ¤t_base.1 * ¤t_base.3,
¤t_base.2 * ¤t_base.0 + ¤t_base.3 * ¤t_base.2,
¤t_base.2 * ¤t_base.1 + ¤t_base.3 * ¤t_base.3,
);
exponent /= 2;
}
result
}
pub fn par_fib_seq(n: u32) -> Vec<BigUint> {
if n < 1 {
return Vec::new();
}
let fib_matrix = (BigUint::from(1u32), BigUint::from(1u32), BigUint::from(1u32), BigUint::from(0u32));
let numbers: Vec<u32> = (1..=n).collect();
let results: Vec<BigUint> = numbers
.into_par_iter()
.flat_map(|i| {
vec![mat_ops(fib_matrix.clone(), i).1]
})
.collect();
results
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigUint;
#[test]
fn test_fib_sequences() {
let result = par_fib_seq(5);
let expected = vec![
BigUint::from(1u32),
BigUint::from(1u32),
BigUint::from(2u32),
BigUint::from(3u32),
BigUint::from(5u32),
];
assert_eq!(result, expected);
let result = par_fib_seq(10);
let expected = vec![
BigUint::from(1u32),
BigUint::from(1u32),
BigUint::from(2u32),
BigUint::from(3u32),
BigUint::from(5u32),
BigUint::from(8u32),
BigUint::from(13u32),
BigUint::from(21u32),
BigUint::from(34u32),
BigUint::from(55u32),
];
assert_eq!(result, expected);
let result = par_fib_seq(20);
let expected = vec![
BigUint::from(1u32),
BigUint::from(1u32),
BigUint::from(2u32),
BigUint::from(3u32),
BigUint::from(5u32),
BigUint::from(8u32),
BigUint::from(13u32),
BigUint::from(21u32),
BigUint::from(34u32),
BigUint::from(55u32),
BigUint::from(89u32),
BigUint::from(144u32),
BigUint::from(233u32),
BigUint::from(377u32),
BigUint::from(610u32),
BigUint::from(987u32),
BigUint::from(1597u32),
BigUint::from(2584u32),
BigUint::from(4181u32),
BigUint::from(6765u32),
];
assert_eq!(result, expected);
let result = par_fib_seq(0);
let expected: Vec<BigUint> = vec![];
assert_eq!(result, expected);
let result = par_fib_seq(1);
let expected = vec![BigUint::from(1u32)];
assert_eq!(result, expected);
}
}
Even though I understand that parallelizing the snippet above more than what I currently have can be tricky due to Fibonacci's sequential nature (each number depends on the previous two), I am hoping to get ideas on reducing its time complexity to \$O(n)\$ and span to \$O(\log^2 n)\$.