1use num_bigint::BigInt;
23use num_traits::Zero;
24
25use crate::basis::Basis;
26use crate::primes::lcm;
27use crate::rational::RnsRational;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct DispatchPlan {
32 pub natural_base: u64,
34 pub required_bits: u64,
36 pub provisioned_bits: u64,
38}
39
40impl DispatchPlan {
41 pub fn fits(&self) -> bool {
43 self.provisioned_bits >= self.required_bits
44 }
45
46 pub fn tightness(&self) -> f64 {
49 if self.provisioned_bits == 0 {
50 f64::INFINITY
51 } else {
52 self.required_bits as f64 / self.provisioned_bits as f64
53 }
54 }
55}
56
57pub struct Dispatcher {
59 basis: Basis,
60}
61
62impl Dispatcher {
63 pub fn new(basis: Basis) -> Self {
64 Dispatcher { basis }
65 }
66
67 pub fn basis(&self) -> &Basis {
69 &self.basis
70 }
71
72 fn reconstruct_bits(p: &BigInt, q: &BigInt) -> u64 {
74 let max_bits = p.bits().max(q.bits());
75 2 * max_bits + 2
76 }
77
78 fn plan(&self, base: u64, p: &BigInt, q: &BigInt) -> DispatchPlan {
79 DispatchPlan {
80 natural_base: base,
81 required_bits: Self::reconstruct_bits(p, q),
82 provisioned_bits: self.basis.capacity_bits(),
83 }
84 }
85
86 pub fn plan_add(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
88 let (p1, q1) = a.to_pair();
89 let (p2, q2) = b.to_pair();
90 let p = &p1 * &q2 + &p2 * &q1;
91 let q = &q1 * &q2;
92 self.plan(lcm(a.natural_base(), b.natural_base()), &p, &q)
93 }
94
95 pub fn plan_mul(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
97 let (p1, q1) = a.to_pair();
98 let (p2, q2) = b.to_pair();
99 let p = &p1 * &p2;
100 let q = &q1 * &q2;
101 self.plan(lcm(a.natural_base(), b.natural_base()), &p, &q)
102 }
103
104 pub fn provision(&mut self, plan: &DispatchPlan) {
106 if !plan.fits() {
107 self.basis = self.basis.extend_to_bits(plan.required_bits + 2);
108 }
109 }
110
111 pub fn execute_add(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
113 a.add(b)
114 }
115
116 pub fn execute_mul(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
118 a.mul(b)
119 }
120
121 pub fn invalid_channels(&self, denom: &BigInt) -> Vec<usize> {
124 (0..self.basis.len())
125 .filter(|&c| {
126 let m = BigInt::from(self.basis.modulus(c));
127 (denom % &m).is_zero()
128 })
129 .collect()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn b() -> Basis {
138 Basis::standard()
139 }
140
141 #[test]
142 fn natural_base_is_reported() {
143 let d = Dispatcher::new(b());
144 let sixth = RnsRational::from_fraction(1, 6, b());
145 let quarter = RnsRational::from_fraction(1, 4, b());
146 let plan = d.plan_add(&sixth, &quarter);
147 assert_eq!(plan.natural_base, 6);
149 }
150
151 #[test]
152 fn integers_need_minimal_range() {
153 let d = Dispatcher::new(b());
154 let a = RnsRational::from_int(3, b());
155 let c = RnsRational::from_int(5, b());
156 let plan = d.plan_add(&a, &c);
157 assert_eq!(plan.natural_base, 1);
158 assert!(plan.fits()); assert!(plan.tightness() < 1.0);
160 }
161
162 #[test]
163 fn small_basis_triggers_provisioning() {
164 let mut d = Dispatcher::new(Basis::with_bits(40));
165 let big: BigInt = BigInt::from(1u64) << 100;
167 let a = RnsRational::new(big.clone(), BigInt::from(1), Basis::with_bits(40));
168 let c = RnsRational::from_int(1, Basis::with_bits(40));
169 let plan = d.plan_add(&a, &c);
170 assert!(!plan.fits());
171 d.provision(&plan);
172 assert!(d.basis().capacity_bits() >= plan.required_bits);
173 }
174
175 #[test]
176 fn execution_is_exact() {
177 let d = Dispatcher::new(b());
178 let sixth = RnsRational::from_fraction(1, 6, b());
179 let quarter = RnsRational::from_fraction(1, 4, b());
180 assert_eq!(
181 d.execute_add(&sixth, &quarter),
182 RnsRational::from_fraction(5, 12, b())
183 );
184 }
185}