use std::collections::HashMap;
use num_bigint::BigInt;
use crate::{utils::util::bigint_to_fe, CircuitField};
pub(crate) fn decompose_in_variable_limbsizes<InF: CircuitField, OutF: CircuitField>(
x: &InF,
limb_sizes: &[usize],
) -> Vec<OutF> {
let x: BigInt = x.to_biguint().into();
let mut limbs: Vec<OutF> = Vec::with_capacity(limb_sizes.len());
let mut shift = 0;
for limb_size in limb_sizes {
let mask_bits: u64 = (1 << limb_size) - 1;
let mask = BigInt::from(mask_bits);
let limb_int = (x.clone() >> shift) & mask;
let limb = bigint_to_fe(&limb_int);
limbs.push(limb);
shift += limb_size;
}
#[cfg(not(test))]
debug_assert_eq!(
x.clone() >> shift,
0.into(),
"Decomposition Chip: the integer cannot be represented with the given limb_sizes"
);
limbs
}
pub(super) fn variable_limbsize_coefficients<F: CircuitField>(limb_sizes: &[usize]) -> Vec<F> {
let mut coefficients = Vec::with_capacity(limb_sizes.len());
let mut shift = 0;
for limb_size in limb_sizes {
if *limb_size == 0 {
coefficients.push(BigInt::from(0));
} else {
coefficients.push(BigInt::from(1) << shift);
}
shift += limb_size;
}
coefficients.iter().map(|x| bigint_to_fe(x)).collect()
}
pub(super) fn process_limb_sizes(max_parallel_lookups: usize, limbs: &mut Vec<usize>) {
while !limbs.len().is_multiple_of(max_parallel_lookups) {
limbs.push(0)
}
}
pub(crate) fn compute_optimal_limb_sizes(
solutions: &mut HashMap<i32, Vec<Vec<usize>>>,
max_parallel_lookups: usize,
max_bit_length: usize,
bound: i32,
) -> Vec<Vec<usize>> {
#[allow(clippy::map_entry)]
if solutions.contains_key(&bound) {
solutions.get(&bound).unwrap().to_vec()
}
else if bound == 0 {
solutions.insert(bound, vec![]);
vec![]
} else {
let (mut opt_solution, mut opt_value) = (Vec::new(), usize::MAX);
for parallel_lookups in 1..=max_parallel_lookups {
for bit_length in (1..=max_bit_length).rev() {
let next_bound = bound - (bit_length * parallel_lookups) as i32;
if next_bound >= 0 {
let mut sol = compute_optimal_limb_sizes(
solutions,
max_parallel_lookups,
max_bit_length,
next_bound,
);
sol.push(vec![bit_length; parallel_lookups]);
if sol.len() < opt_value {
opt_value = sol.len();
opt_solution = sol;
}
}
}
}
solutions.insert(bound, opt_solution.clone());
opt_solution
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use itertools::Itertools;
use super::compute_optimal_limb_sizes;
fn limb_sizes_is_optimal(
bound: usize,
max_parallel_lookups: usize,
max_bit_length: usize,
optimal_value: usize,
) -> bool {
let mut possible_rows = (1..=max_bit_length)
.cartesian_product(1..=max_parallel_lookups)
.map(|(x, how_many)| vec![x; how_many])
.collect::<Vec<_>>();
possible_rows.push(vec![0]);
possible_rows
.into_iter()
.combinations(optimal_value - 1)
.map(|solution| solution.iter().flatten().sum::<usize>())
.all(|x| x != bound)
}
#[test]
fn test_limb_size_decomposition_optimality() {
let mut opt_limbs = HashMap::new();
let max_parallel_lookups = 4usize;
let max_bit_length = 8usize;
for bound in 1..=128 {
let opt = compute_optimal_limb_sizes(
&mut opt_limbs,
max_parallel_lookups,
max_bit_length,
bound as i32,
)
.len();
assert!(limb_sizes_is_optimal(
bound,
max_parallel_lookups,
max_bit_length,
opt
));
}
}
}