diff --git a/poulpy-core/examples/encryption.rs b/poulpy-core/examples/encryption.rs index 5ee8c8e..e933fde 100644 --- a/poulpy-core/examples/encryption.rs +++ b/poulpy-core/examples/encryption.rs @@ -85,7 +85,7 @@ fn main() { module.glwe_sub_inplace(&mut pt_want, &pt_have); // Ideal vs. actual noise - let noise_have: f64 = pt_want.data.std(base2k.into(), 0) * (ct.k().as_u32() as f64).exp2(); + let noise_have: f64 = pt_want.data.stats(base2k.into(), 0).std() * (ct.k().as_u32() as f64).exp2(); let noise_want: f64 = SIGMA; // Check diff --git a/poulpy-core/src/noise/gglwe.rs b/poulpy-core/src/noise/gglwe.rs index c6dd278..8831517 100644 --- a/poulpy-core/src/noise/gglwe.rs +++ b/poulpy-core/src/noise/gglwe.rs @@ -60,7 +60,7 @@ where self.vec_znx_sub_scalar_inplace(&mut pt.data, 0, (dsize - 1) + row_i * dsize, pt_want, col_i); - let noise_have: f64 = pt.data.std(base2k, 0).log2(); + let noise_have: f64 = pt.data.stats(base2k, 0).std().log2(); println!("noise_have: {noise_have}"); diff --git a/poulpy-core/src/noise/ggsw.rs b/poulpy-core/src/noise/ggsw.rs index 616d7c2..0c0173f 100644 --- a/poulpy-core/src/noise/ggsw.rs +++ b/poulpy-core/src/noise/ggsw.rs @@ -107,7 +107,7 @@ where self.vec_znx_sub_inplace(&mut pt_have.data, 0, &pt.data, 0); - let std_pt: f64 = pt_have.data.std(base2k, 0).log2(); + let std_pt: f64 = pt_have.data.stats(base2k, 0).std().log2(); let noise: f64 = max_noise(col_j); assert!(std_pt <= noise, "{std_pt} > {noise}"); @@ -165,7 +165,7 @@ where self.vec_znx_sub_inplace(&mut pt_have.data, 0, &pt.data, 0); - let std_pt: f64 = pt_have.data.std(base2k, 0).log2(); + let std_pt: f64 = pt_have.data.stats(base2k, 0).std().log2(); println!("col: {col_j} row: {row_i}: {std_pt}"); pt.data.zero(); } diff --git a/poulpy-core/src/noise/glwe.rs b/poulpy-core/src/noise/glwe.rs index 7c6979e..dbdeea9 100644 --- a/poulpy-core/src/noise/glwe.rs +++ b/poulpy-core/src/noise/glwe.rs @@ -1,6 +1,6 @@ use poulpy_hal::{ api::{ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxNormalizeInplace, VecZnxSubInplace}, - layouts::{Backend, DataRef, Module, Scratch, ScratchOwned}, + layouts::{Backend, DataRef, Module, Scratch, ScratchOwned, Stats}, }; use crate::{ @@ -10,7 +10,7 @@ use crate::{ }; impl GLWE { - pub fn noise(&self, module: &M, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> f64 + pub fn noise(&self, module: &M, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> Stats where M: GLWENoise, S: GLWESecretPreparedToRef, @@ -30,7 +30,7 @@ impl GLWE { } pub trait GLWENoise { - fn glwe_noise(&self, res: &R, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> f64 + fn glwe_noise(&self, res: &R, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> Stats where R: GLWEToRef, S: GLWESecretPreparedToRef, @@ -49,7 +49,7 @@ where ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, Scratch: ScratchTakeCore, { - fn glwe_noise(&self, res: &R, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> f64 + fn glwe_noise(&self, res: &R, sk_prepared: &S, pt_want: &P, scratch: &mut Scratch) -> Stats where R: GLWEToRef, S: GLWESecretPreparedToRef, @@ -63,7 +63,7 @@ where self.glwe_decrypt(res, &mut pt_have, sk_prepared, scratch); self.vec_znx_sub_inplace(&mut pt_have.data, 0, &pt_want.data, 0); self.vec_znx_normalize_inplace(res_ref.base2k().into(), &mut pt_have.data, 0, scratch); - pt_have.data.std(res_ref.base2k().into(), 0).log2() + pt_have.data.stats(res_ref.base2k().into(), 0) } fn glwe_assert_noise(&self, res: &R, sk_prepared: &S, pt_want: &P, max_noise: f64) @@ -74,7 +74,10 @@ where { let res: &GLWE<&[u8]> = &res.to_ref(); let mut scratch: ScratchOwned = ScratchOwned::alloc(self.glwe_decrypt_tmp_bytes(res)); - let noise_have: f64 = self.glwe_noise(res, sk_prepared, pt_want, scratch.borrow()); + let noise_have: f64 = self + .glwe_noise(res, sk_prepared, pt_want, scratch.borrow()) + .std() + .log2(); assert!(noise_have <= max_noise, "{noise_have} {max_noise}"); } } diff --git a/poulpy-core/src/tests/test_suite/automorphism/gglwe_atk.rs b/poulpy-core/src/tests/test_suite/automorphism/gglwe_atk.rs index 3f2b94a..14aab05 100644 --- a/poulpy-core/src/tests/test_suite/automorphism/gglwe_atk.rs +++ b/poulpy-core/src/tests/test_suite/automorphism/gglwe_atk.rs @@ -159,7 +159,7 @@ where col_i, ); - let noise_have: f64 = pt.data.std(base2k, 0).log2(); + let noise_have: f64 = pt.data.stats(base2k, 0).std().log2(); let noise_want: f64 = log2_std_noise_gglwe_product( n as f64, base2k * di, @@ -306,7 +306,7 @@ where col_i, ); - let noise_have: f64 = pt.data.std(base2k, 0).log2(); + let noise_have: f64 = pt.data.stats(base2k, 0).std().log2(); let noise_want: f64 = log2_std_noise_gglwe_product( n as f64, base2k * di, diff --git a/poulpy-core/src/tests/test_suite/encryption/glwe_ct.rs b/poulpy-core/src/tests/test_suite/encryption/glwe_ct.rs index 514d28d..7d4fcff 100644 --- a/poulpy-core/src/tests/test_suite/encryption/glwe_ct.rs +++ b/poulpy-core/src/tests/test_suite/encryption/glwe_ct.rs @@ -74,7 +74,7 @@ where module.glwe_sub_inplace(&mut pt_want, &pt_have); - let noise_have: f64 = pt_want.data.std(base2k, 0) * (ct.k().as_u32() as f64).exp2(); + let noise_have: f64 = pt_want.data.stats(base2k, 0).std() * (ct.k().as_u32() as f64).exp2(); let noise_want: f64 = SIGMA; assert!(noise_have <= noise_want + 0.2); @@ -147,7 +147,7 @@ where module.glwe_sub_inplace(&mut pt_want, &pt_have); - let noise_have: f64 = pt_want.data.std(base2k, 0) * (ct.k().as_u32() as f64).exp2(); + let noise_have: f64 = pt_want.data.stats(base2k, 0).std() * (ct.k().as_u32() as f64).exp2(); let noise_want: f64 = SIGMA; assert!( @@ -203,7 +203,7 @@ where ); ct.decrypt(module, &mut pt, &sk_prepared, scratch.borrow()); - assert!((SIGMA - pt.data.std(base2k, 0) * (k_ct as f64).exp2()) <= 0.2); + assert!((SIGMA - pt.data.stats(base2k, 0).std() * (k_ct as f64).exp2()) <= 0.2); } } @@ -271,7 +271,7 @@ where module.glwe_sub_inplace(&mut pt_want, &pt_have); - let noise_have: f64 = pt_want.data.std(base2k, 0).log2(); + let noise_have: f64 = pt_want.data.stats(base2k, 0).std().log2(); let noise_want: f64 = ((((rank as f64) + 1.0) * n as f64 * 0.5 * SIGMA * SIGMA).sqrt()).log2() - (k_ct as f64); assert!( diff --git a/poulpy-core/src/tests/test_suite/glwe_packer.rs b/poulpy-core/src/tests/test_suite/glwe_packer.rs index 7dd5ea2..04b08ab 100644 --- a/poulpy-core/src/tests/test_suite/glwe_packer.rs +++ b/poulpy-core/src/tests/test_suite/glwe_packer.rs @@ -150,7 +150,7 @@ where module.glwe_sub_inplace(&mut pt, &pt_want); - let noise_have: f64 = pt.std().log2(); + let noise_have: f64 = pt.stats().std().log2(); assert!( noise_have < -((k_ct - base2k) as f64), diff --git a/poulpy-core/src/tests/test_suite/trace.rs b/poulpy-core/src/tests/test_suite/trace.rs index 7c3c653..c4db7e4 100644 --- a/poulpy-core/src/tests/test_suite/trace.rs +++ b/poulpy-core/src/tests/test_suite/trace.rs @@ -123,7 +123,7 @@ where module.vec_znx_sub_inplace(&mut pt_want.data, 0, &pt_have.data, 0); module.vec_znx_normalize_inplace(base2k, &mut pt_want.data, 0, scratch.borrow()); - let noise_have: f64 = pt_want.std().log2(); + let noise_have: f64 = pt_want.stats().std().log2(); let mut noise_want: f64 = var_noise_gglwe_product( n as f64, diff --git a/poulpy-core/src/utils.rs b/poulpy-core/src/utils.rs index 9f9e4a0..858cd5d 100644 --- a/poulpy-core/src/utils.rs +++ b/poulpy-core/src/utils.rs @@ -1,5 +1,5 @@ use crate::layouts::{GLWEPlaintext, LWEInfos, LWEPlaintext, TorusPrecision}; -use poulpy_hal::layouts::{DataMut, DataRef}; +use poulpy_hal::layouts::{DataMut, DataRef, Stats}; use rug::Float; impl GLWEPlaintext { @@ -29,8 +29,8 @@ impl GLWEPlaintext { self.data.decode_vec_float(self.base2k().into(), 0, data); } - pub fn std(&self) -> f64 { - self.data.std(self.base2k().into(), 0) + pub fn stats(&self) -> Stats { + self.data.stats(self.base2k().into(), 0) } } diff --git a/poulpy-hal/src/layouts/mod.rs b/poulpy-hal/src/layouts/mod.rs index cce4df4..0553665 100644 --- a/poulpy-hal/src/layouts/mod.rs +++ b/poulpy-hal/src/layouts/mod.rs @@ -18,6 +18,7 @@ pub use module::*; pub use scalar_znx::*; pub use scratch::*; pub use serialization::*; +pub use stats::*; pub use svp_ppol::*; pub use vec_znx::*; pub use vec_znx_big::*; diff --git a/poulpy-hal/src/layouts/stats.rs b/poulpy-hal/src/layouts/stats.rs index d40ffa5..bb4f9d2 100644 --- a/poulpy-hal/src/layouts/stats.rs +++ b/poulpy-hal/src/layouts/stats.rs @@ -6,15 +6,33 @@ use rug::{ use crate::layouts::{Backend, DataRef, VecZnx, VecZnxBig, VecZnxBigToRef, ZnxInfos}; +pub struct Stats { + max: f64, + std: f64, +} + +impl Stats { + pub fn max(&self) -> f64 { + self.max + } + + pub fn std(&self) -> f64 { + self.std + } +} + impl VecZnx { - pub fn std(&self, base2k: usize, col: usize) -> f64 { + pub fn stats(&self, base2k: usize, col: usize) -> Stats { let prec: u32 = (self.size() * base2k) as u32; let mut data: Vec = (0..self.n()).map(|_| Float::with_val(prec, 0)).collect(); self.decode_vec_float(base2k, col, &mut data); // std = sqrt(sum((xi - avg)^2) / n) let mut avg: Float = Float::with_val(prec, 0); + let mut max: Float = Float::with_val(prec, 0); + data.iter().for_each(|x| { avg.add_assign_round(x, Round::Nearest); + max.max_mut(&Float::with_val(53, x.abs_ref())); }); avg.div_assign_round(Float::with_val(prec, data.len()), Round::Nearest); data.iter_mut().for_each(|x| { @@ -24,12 +42,15 @@ impl VecZnx { data.iter().for_each(|x| std += x * x); std.div_assign_round(Float::with_val(prec, data.len()), Round::Nearest); std = std.sqrt(); - std.to_f64() + Stats { + std: std.to_f64(), + max: max.to_f64(), + } } } impl> VecZnxBig { - pub fn std(&self, base2k: usize, col: usize) -> f64 { + pub fn stats(&self, base2k: usize, col: usize) -> Stats { let self_ref: VecZnxBig<&[u8], B> = self.to_ref(); let znx: VecZnx<&[u8]> = VecZnx { data: self_ref.data, @@ -38,6 +59,6 @@ impl> VecZnxBig { size: self_ref.size, max_size: self_ref.max_size, }; - znx.std(base2k, col) + znx.stats(base2k, col) } } diff --git a/poulpy-hal/src/reference/fft64/vec_znx_big.rs b/poulpy-hal/src/reference/fft64/vec_znx_big.rs index 2295ee7..233eb5c 100644 --- a/poulpy-hal/src/reference/fft64/vec_znx_big.rs +++ b/poulpy-hal/src/reference/fft64/vec_znx_big.rs @@ -324,7 +324,7 @@ where assert_eq!(a.at(col_j, limb_i), zero); }) } else { - let std: f64 = a.std(base2k, col_i) * k_f64; + let std: f64 = a.stats(base2k, col_i).std() * k_f64; assert!( (std - sigma * sqrt2).abs() < 0.1, "std={} ~!= {}", diff --git a/poulpy-hal/src/reference/vec_znx/shift.rs b/poulpy-hal/src/reference/vec_znx/shift.rs index a13b982..a7366f8 100644 --- a/poulpy-hal/src/reference/vec_znx/shift.rs +++ b/poulpy-hal/src/reference/vec_znx/shift.rs @@ -662,8 +662,8 @@ mod tests { vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_ref, j, &mut carry); vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_test, j, &mut carry); - assert!(res_ref.std(base2k, j).log2() - (k as f64) <= (k * base2k) as f64); - assert!(res_test.std(base2k, j).log2() - (k as f64) <= (k * base2k) as f64); + assert!(res_ref.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64); + assert!(res_test.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64); } } } diff --git a/poulpy-hal/src/test_suite/vec_znx.rs b/poulpy-hal/src/test_suite/vec_znx.rs index f1c4e86..058d0ec 100644 --- a/poulpy-hal/src/test_suite/vec_znx.rs +++ b/poulpy-hal/src/test_suite/vec_znx.rs @@ -717,7 +717,7 @@ where assert_eq!(a.at(col_j, limb_i), zero); }) } else { - let std: f64 = a.std(base2k, col_i); + let std: f64 = a.stats(base2k, col_i).std(); assert!( (std - one_12_sqrt).abs() < 0.01, "std={std} ~!= {one_12_sqrt}", @@ -750,7 +750,7 @@ where assert_eq!(a.at(col_j, limb_i), zero); }) } else { - let std: f64 = a.std(base2k, col_i) * k_f64; + let std: f64 = a.stats(base2k, col_i).std() * k_f64; assert!((std - sigma).abs() < 0.1, "std={std} ~!= {sigma}"); } }) @@ -782,7 +782,7 @@ where assert_eq!(a.at(col_j, limb_i), zero); }) } else { - let std: f64 = a.std(base2k, col_i) * k_f64; + let std: f64 = a.stats(base2k, col_i).std() * k_f64; assert!( (std - sigma * sqrt2).abs() < 0.1, "std={std} ~!= {}", diff --git a/poulpy-schemes/src/tfhe/bdd_arithmetic/ciphertexts/fhe_uint.rs b/poulpy-schemes/src/tfhe/bdd_arithmetic/ciphertexts/fhe_uint.rs index 19629f6..1d60027 100644 --- a/poulpy-schemes/src/tfhe/bdd_arithmetic/ciphertexts/fhe_uint.rs +++ b/poulpy-schemes/src/tfhe/bdd_arithmetic/ciphertexts/fhe_uint.rs @@ -8,7 +8,7 @@ use poulpy_core::{ }; use poulpy_hal::{ api::ModuleLogN, - layouts::{Backend, Data, DataMut, DataRef, Scratch}, + layouts::{Backend, Data, DataMut, DataRef, Scratch, Stats}, source::Source, }; use std::{collections::HashMap, marker::PhantomData}; @@ -114,7 +114,7 @@ impl FheUint { } impl FheUint { - pub fn noise(&self, module: &M, want: u32, sk: &S, scratch: &mut Scratch) -> f64 + pub fn noise(&self, module: &M, want: u32, sk: &S, scratch: &mut Scratch) -> Stats where S: GLWESecretPreparedToRef + GLWEInfos, M: ModuleLogN + GLWEDecrypt + GLWENoise,