arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/base2k/src/sampling.rs
base2k/src/sampling.rsRaw
  1use crate::znx_base::ZnxViewMut;
  2use crate::{Backend, Module, VecZnx};
  3use rand_distr::{Distribution, Normal};
  4use sampling::source::Source;
  5
  6pub trait Sampling {
  7    /// Fills the first `size` size with uniform values in \[-2^{log_base2k-1}, 2^{log_base2k-1}\]
  8    fn fill_uniform<DataMut: AsMut<[u8]> + AsRef<[u8]>>(
  9        &self,
 10        log_base2k: usize,
 11        a: &mut VecZnx<DataMut>,
 12        col_i: usize,
 13        size: usize,
 14        source: &mut Source,
 15    );
 16
 17    /// Adds vector sampled according to the provided distribution, scaled by 2^{-log_k} and bounded to \[-bound, bound\].
 18    fn add_dist_f64<DataMut: AsMut<[u8]> + AsRef<[u8]>, D: Distribution<f64>>(
 19        &self,
 20        log_base2k: usize,
 21        a: &mut VecZnx<DataMut>,
 22        col_i: usize,
 23        log_k: usize,
 24        source: &mut Source,
 25        dist: D,
 26        bound: f64,
 27    );
 28
 29    /// Adds a discrete normal vector scaled by 2^{-log_k} with the provided standard deviation and bounded to \[-bound, bound\].
 30    fn add_normal<DataMut: AsMut<[u8]> + AsRef<[u8]>>(
 31        &self,
 32        log_base2k: usize,
 33        a: &mut VecZnx<DataMut>,
 34        col_i: usize,
 35        log_k: usize,
 36        source: &mut Source,
 37        sigma: f64,
 38        bound: f64,
 39    );
 40}
 41
 42impl<B: Backend> Sampling for Module<B> {
 43    fn fill_uniform<DataMut: AsMut<[u8]> + AsRef<[u8]>>(
 44        &self,
 45        log_base2k: usize,
 46        a: &mut VecZnx<DataMut>,
 47        col_i: usize,
 48        size: usize,
 49        source: &mut Source,
 50    ) {
 51        let base2k: u64 = 1 << log_base2k;
 52        let mask: u64 = base2k - 1;
 53        let base2k_half: i64 = (base2k >> 1) as i64;
 54        (0..size).for_each(|j| {
 55            a.at_mut(col_i, j)
 56                .iter_mut()
 57                .for_each(|x| *x = (source.next_u64n(base2k, mask) as i64) - base2k_half);
 58        })
 59    }
 60
 61    fn add_dist_f64<DataMut: AsMut<[u8]> + AsRef<[u8]>, D: Distribution<f64>>(
 62        &self,
 63        log_base2k: usize,
 64        a: &mut VecZnx<DataMut>,
 65        col_i: usize,
 66        log_k: usize,
 67        source: &mut Source,
 68        dist: D,
 69        bound: f64,
 70    ) {
 71        assert!(
 72            (bound.log2().ceil() as i64) < 64,
 73            "invalid bound: ceil(log2(bound))={} > 63",
 74            (bound.log2().ceil() as i64)
 75        );
 76
 77        let limb: usize = (log_k + log_base2k - 1) / log_base2k - 1;
 78        let log_base2k_rem: usize = log_k % log_base2k;
 79
 80        if log_base2k_rem != 0 {
 81            a.at_mut(col_i, limb).iter_mut().for_each(|a| {
 82                let mut dist_f64: f64 = dist.sample(source);
 83                while dist_f64.abs() > bound {
 84                    dist_f64 = dist.sample(source)
 85                }
 86                *a += (dist_f64.round() as i64) << log_base2k_rem;
 87            });
 88        } else {
 89            a.at_mut(col_i, limb).iter_mut().for_each(|a| {
 90                let mut dist_f64: f64 = dist.sample(source);
 91                while dist_f64.abs() > bound {
 92                    dist_f64 = dist.sample(source)
 93                }
 94                *a += dist_f64.round() as i64
 95            });
 96        }
 97    }
 98
 99    fn add_normal<DataMut: AsMut<[u8]> + AsRef<[u8]>>(
100        &self,
101        log_base2k: usize,
102        a: &mut VecZnx<DataMut>,
103        col_i: usize,
104        log_k: usize,
105        source: &mut Source,
106        sigma: f64,
107        bound: f64,
108    ) {
109        self.add_dist_f64(
110            log_base2k,
111            a,
112            col_i,
113            log_k,
114            source,
115            Normal::new(0.0, sigma).unwrap(),
116            bound,
117        );
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::Sampling;
124    use crate::vec_znx_ops::*;
125    use crate::znx_base::*;
126    use crate::{FFT64, Module, Stats, VecZnx};
127    use sampling::source::Source;
128
129    #[test]
130    fn fill_uniform() {
131        let n: usize = 4096;
132        let module: Module<FFT64> = Module::<FFT64>::new(n);
133        let log_base2k: usize = 17;
134        let size: usize = 5;
135        let mut source: Source = Source::new([0u8; 32]);
136        let cols: usize = 2;
137        let zero: Vec<i64> = vec![0; n];
138        let one_12_sqrt: f64 = 0.28867513459481287;
139        (0..cols).for_each(|col_i| {
140            let mut a: VecZnx<_> = module.new_vec_znx(cols, size);
141            module.fill_uniform(log_base2k, &mut a, col_i, size, &mut source);
142            (0..cols).for_each(|col_j| {
143                if col_j != col_i {
144                    (0..size).for_each(|limb_i| {
145                        assert_eq!(a.at(col_j, limb_i), zero);
146                    })
147                } else {
148                    let std: f64 = a.std(col_i, log_base2k);
149                    assert!(
150                        (std - one_12_sqrt).abs() < 0.01,
151                        "std={} ~!= {}",
152                        std,
153                        one_12_sqrt
154                    );
155                }
156            })
157        });
158    }
159
160    #[test]
161    fn add_normal() {
162        let n: usize = 4096;
163        let module: Module<FFT64> = Module::<FFT64>::new(n);
164        let log_base2k: usize = 17;
165        let log_k: usize = 2 * 17;
166        let size: usize = 5;
167        let sigma: f64 = 3.2;
168        let bound: f64 = 6.0 * sigma;
169        let mut source: Source = Source::new([0u8; 32]);
170        let cols: usize = 2;
171        let zero: Vec<i64> = vec![0; n];
172        let k_f64: f64 = (1u64 << log_k as u64) as f64;
173        (0..cols).for_each(|col_i| {
174            let mut a: VecZnx<_> = module.new_vec_znx(cols, size);
175            module.add_normal(log_base2k, &mut a, col_i, log_k, &mut source, sigma, bound);
176            (0..cols).for_each(|col_j| {
177                if col_j != col_i {
178                    (0..size).for_each(|limb_i| {
179                        assert_eq!(a.at(col_j, limb_i), zero);
180                    })
181                } else {
182                    let std: f64 = a.std(col_i, log_base2k) * k_f64;
183                    assert!((std - sigma).abs() < 0.1, "std={} ~!= {}", std, sigma);
184                }
185            })
186        });
187    }
188}