use num_bigint::BigInt;
use num_traits::Zero;
use crate::basis::Basis;
use crate::primes::lcm;
use crate::rational::RnsRational;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DispatchPlan {
pub natural_base: u64,
pub required_bits: u64,
pub provisioned_bits: u64,
}
impl DispatchPlan {
pub fn fits(&self) -> bool {
self.provisioned_bits >= self.required_bits
}
pub fn tightness(&self) -> f64 {
if self.provisioned_bits == 0 {
f64::INFINITY
} else {
self.required_bits as f64 / self.provisioned_bits as f64
}
}
}
pub struct Dispatcher {
basis: Basis,
}
impl Dispatcher {
pub fn new(basis: Basis) -> Self {
Dispatcher { basis }
}
pub fn basis(&self) -> &Basis {
&self.basis
}
fn reconstruct_bits(p: &BigInt, q: &BigInt) -> u64 {
let max_bits = p.bits().max(q.bits());
2 * max_bits + 2
}
fn plan(&self, base: u64, p: &BigInt, q: &BigInt) -> DispatchPlan {
DispatchPlan {
natural_base: base,
required_bits: Self::reconstruct_bits(p, q),
provisioned_bits: self.basis.capacity_bits(),
}
}
pub fn plan_add(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
let (p1, q1) = a.to_pair();
let (p2, q2) = b.to_pair();
let p = &p1 * &q2 + &p2 * &q1;
let q = &q1 * &q2;
self.plan(lcm(a.natural_base(), b.natural_base()), &p, &q)
}
pub fn plan_mul(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
let (p1, q1) = a.to_pair();
let (p2, q2) = b.to_pair();
let p = &p1 * &p2;
let q = &q1 * &q2;
self.plan(lcm(a.natural_base(), b.natural_base()), &p, &q)
}
pub fn provision(&mut self, plan: &DispatchPlan) {
if !plan.fits() {
self.basis = self.basis.extend_to_bits(plan.required_bits + 2);
}
}
pub fn execute_add(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
a.add(b)
}
pub fn execute_mul(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
a.mul(b)
}
pub fn invalid_channels(&self, denom: &BigInt) -> Vec<usize> {
(0..self.basis.len())
.filter(|&c| {
let m = BigInt::from(self.basis.modulus(c));
(denom % &m).is_zero()
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn b() -> Basis {
Basis::standard()
}
#[test]
fn natural_base_is_reported() {
let d = Dispatcher::new(b());
let sixth = RnsRational::from_fraction(1, 6, b());
let quarter = RnsRational::from_fraction(1, 4, b());
let plan = d.plan_add(&sixth, &quarter);
assert_eq!(plan.natural_base, 6);
}
#[test]
fn integers_need_minimal_range() {
let d = Dispatcher::new(b());
let a = RnsRational::from_int(3, b());
let c = RnsRational::from_int(5, b());
let plan = d.plan_add(&a, &c);
assert_eq!(plan.natural_base, 1);
assert!(plan.fits()); assert!(plan.tightness() < 1.0);
}
#[test]
fn small_basis_triggers_provisioning() {
let mut d = Dispatcher::new(Basis::with_bits(40));
let big: BigInt = BigInt::from(1u64) << 100;
let a = RnsRational::new(big.clone(), BigInt::from(1), Basis::with_bits(40));
let c = RnsRational::from_int(1, Basis::with_bits(40));
let plan = d.plan_add(&a, &c);
assert!(!plan.fits());
d.provision(&plan);
assert!(d.basis().capacity_bits() >= plan.required_bits);
}
#[test]
fn execution_is_exact() {
let d = Dispatcher::new(b());
let sixth = RnsRational::from_fraction(1, 6, b());
let quarter = RnsRational::from_fraction(1, 4, b());
assert_eq!(
d.execute_add(&sixth, &quarter),
RnsRational::from_fraction(5, 12, b())
);
}
}