use structs instead instead of separate parameters

This commit is contained in:
Mayeul@Zama
2022-04-06 17:30:45 +02:00
committed by rudy
parent b88fa7e9d9
commit 58876119c9
9 changed files with 443 additions and 394 deletions

View File

@@ -2,20 +2,14 @@ use super::super::complexity::Complexity;
use super::keyswitch_lwe::KeySwitchLWEComplexity;
use super::pbs::PbsComplexity;
use super::{keyswitch_lwe, pbs};
use crate::parameters::AtomicPatternParameters;
#[allow(clippy::too_many_arguments)]
pub trait AtomicPatternComplexity {
fn complexity(
&self,
sum_size: u64,
input_lwe_dimension: u64, //n_big
internal_ks_output_lwe_dimension: u64, //n_small
ks_decomposition_level_count: u64, //l(BS)
ks_decomposition_base_log: u64, //b(BS)
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(KS)
br_decomposition_base_log: u64, //b(ks)
params: AtomicPatternParameters,
ciphertext_modulus_log: u64,
) -> Complexity;
}
@@ -33,34 +27,16 @@ where
fn complexity(
&self,
sum_size: u64,
input_lwe_dimension: u64, //n_big
internal_ks_output_lwe_dimension: u64, //n_small
ks_decomposition_level_count: u64, //l(KS)
ks_decomposition_base_log: u64, //b(KS) // not used
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(BR) // not used
br_decomposition_base_log: u64, //b(BR)
params: AtomicPatternParameters,
ciphertext_modulus_log: u64,
) -> Complexity {
let multisum_complexity = (sum_size * input_lwe_dimension) as f64;
let ks_complexity = {
self.ks_lwe.complexity(
input_lwe_dimension,
internal_ks_output_lwe_dimension,
ks_decomposition_level_count,
ks_decomposition_base_log,
ciphertext_modulus_log,
)
};
let pbs_complexity = self.pbs.complexity(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
ciphertext_modulus_log,
);
let multisum_complexity = (sum_size * params.input_lwe_dimension.0) as f64;
let ks_complexity = self
.ks_lwe
.complexity(params.ks_parameters(), ciphertext_modulus_log);
let pbs_complexity = self
.pbs
.complexity(params.pbs_parameters(), ciphertext_modulus_log);
multisum_complexity + ks_complexity + pbs_complexity
}
}

View File

@@ -1,17 +1,12 @@
use crate::parameters::CmuxParameters;
use super::super::complexity::Complexity;
use super::super::fft;
use fft::FftComplexity;
pub trait CmuxComplexity {
#[allow(non_snake_case)]
fn complexity(
&self,
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(BR)
br_decomposition_base_log: u64, //b(BR)
ciphertext_modulus_log: u64, //log2_q
) -> Complexity;
fn complexity(&self, params: CmuxParameters, ciphertext_modulus_log: u64) -> Complexity;
}
#[allow(non_snake_case)]
@@ -33,17 +28,12 @@ fn final_additional_linear_fft_factor(factor: Option<f64>, integer_size: u64) ->
impl<FFT: FftComplexity> CmuxComplexity for SimpleWithFactors<FFT> {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L145
#[allow(non_snake_case)]
fn complexity(
&self,
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(BR)
_br_decomposition_base_log: u64, //b(BR)
ciphertext_modulus_log: u64, //log2_q
) -> Complexity {
fn complexity(&self, params: CmuxParameters, ciphertext_modulus_log: u64) -> Complexity {
let glwe_polynomial_size = 1 << params.output_glwe_params.log2_polynomial_size;
let f_glwe_polynomial_size = glwe_polynomial_size as f64;
let f_glwe_size = (glwe_dimension + 1) as f64;
let br_decomposition_level_count = br_decomposition_level_count as f64;
let f_glwe_size = (params.output_glwe_params.glwe_dimension + 1) as f64;
let br_decomposition_level_count = params.br_decomposition_parameter.level as f64;
let f_square_glwe_size = f_glwe_size * f_glwe_size;
let additional_linear_fft_factor =
@@ -79,6 +69,8 @@ pub const DEFAULT: Default = SimpleWithFactors {
#[cfg(test)]
pub mod tests {
use crate::parameters::{BrDecompositionParameters, GlweParameters};
use super::*;
pub const COST_AWS: Default = SimpleWithFactors {
@@ -94,23 +86,57 @@ pub mod tests {
fn golden_python_prototype() {
let ignored = 0;
let golden = 8.0;
let actual = DEFAULT.complexity(1, 1, 1, ignored, 0);
let cmux_param1 = CmuxParameters {
br_decomposition_parameter: BrDecompositionParameters {
level: 1,
log2_base: ignored,
},
output_glwe_params: GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: 1,
},
};
let cmux_param2 = CmuxParameters {
br_decomposition_parameter: BrDecompositionParameters {
level: 300,
log2_base: ignored,
},
output_glwe_params: GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: 20,
},
};
let cmux_param3 = CmuxParameters {
br_decomposition_parameter: BrDecompositionParameters {
level: 56,
log2_base: ignored,
},
output_glwe_params: GlweParameters {
log2_polynomial_size: 10,
glwe_dimension: 10,
},
};
let actual = DEFAULT.complexity(cmux_param1, 0);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 138621.0;
let actual = DEFAULT.complexity(1, 20, 300, ignored, 64);
let actual = DEFAULT.complexity(cmux_param2, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 927.1215428435396;
let actual = COST_AWS.complexity(1, 1, 1, ignored, 0);
let actual = COST_AWS.complexity(cmux_param1, 0);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 117019.72048983313;
let actual = COST_AWS.complexity(1, 20, 300, ignored, 64);
let actual = COST_AWS.complexity(cmux_param2, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 7651844.24194206;
let actual = COST_AWS.complexity(1024, 10, 56, ignored, 64);
let actual = COST_AWS.complexity(cmux_param3, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
}
}

View File

@@ -1,32 +1,24 @@
use crate::parameters::KeyswitchParameters;
use super::super::complexity::Complexity;
pub trait KeySwitchLWEComplexity {
fn complexity(
&self,
input_lwe_dimension: u64, //n_big
output_lwe_dimension: u64, //n_small
decomposition_level_count: u64, //l(BS)
decomposition_base_log: u64, //b(BS)
ciphertext_modulus_log: u64, //log2_q
) -> Complexity;
fn complexity(&self, params: KeyswitchParameters, ciphertext_modulus_log: u64) -> Complexity;
}
pub struct Default;
impl KeySwitchLWEComplexity for Default {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L91
fn complexity(
&self,
input_lwe_dimension: u64, //n_big
output_lwe_dimension: u64, //n_small
decomposition_level_count: u64, //l(KS)
_decomposition_base_log: u64, //b(KS)
_ciphertext_modulus_log: u64, //log2_q
) -> Complexity {
fn complexity(&self, params: KeyswitchParameters, _ciphertext_modulus_log: u64) -> Complexity {
let input_lwe_dimension = params.input_lwe_dimension.0;
let output_lwe_dimension = params.output_lwe_dimension.0;
let level = params.ks_decomposition_parameter.level;
let output_lwe_size = output_lwe_dimension + 1;
let count_decomposition = input_lwe_dimension * decomposition_level_count;
let count_mul = input_lwe_dimension * decomposition_level_count * output_lwe_size;
let count_add = (input_lwe_dimension * decomposition_level_count - 1) * output_lwe_size + 1;
let count_decomposition = input_lwe_dimension * level;
let count_mul = input_lwe_dimension * level * output_lwe_size;
let count_add = (input_lwe_dimension * level - 1) * output_lwe_size + 1;
(count_decomposition + count_mul + count_add) as Complexity
}
}
@@ -37,17 +29,10 @@ pub struct SimpleProductWithFactor {
impl KeySwitchLWEComplexity for SimpleProductWithFactor {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L100
fn complexity(
&self,
input_lwe_dimension: u64, //n_big
output_lwe_dimension: u64, //n_small
decomposition_level_count: u64, //l(BS)
_decomposition_base_log: u64, //b(BS)
ciphertext_modulus_log: u64, //log2_q
) -> Complexity {
let product = input_lwe_dimension
* output_lwe_dimension
* decomposition_level_count
fn complexity(&self, params: KeyswitchParameters, ciphertext_modulus_log: u64) -> Complexity {
let product = params.input_lwe_dimension.0
* params.output_lwe_dimension.0
* params.ks_decomposition_parameter.level
* ciphertext_modulus_log;
self.factor * (product as f64)
}
@@ -57,6 +42,8 @@ pub const DEFAULT: Default = Default;
#[cfg(test)]
mod tests {
use crate::parameters::{KsDecompositionParameters, LweDimension};
use super::*;
pub const COST_AWS: SimpleProductWithFactor = SimpleProductWithFactor {
factor: 0.12547239853890443,
@@ -66,11 +53,21 @@ mod tests {
fn golden_python_prototype() {
let ignored = 0;
let golden = 134313984.0;
let actual = DEFAULT.complexity(1024, 2048, 32, ignored, 64);
let ks_params = KeyswitchParameters {
input_lwe_dimension: LweDimension(1024),
output_lwe_dimension: LweDimension(2048),
ks_decomposition_parameter: KsDecompositionParameters {
level: 32,
log2_base: ignored,
},
};
let actual = DEFAULT.complexity(ks_params, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 538899848.2752727;
let actual = COST_AWS.complexity(1024, 2048, 32, ignored, 64);
let actual = COST_AWS.complexity(ks_params, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
}
}

View File

@@ -1,15 +1,12 @@
use super::super::complexity::Complexity;
use super::cmux;
use crate::parameters::PbsParameters;
pub trait PbsComplexity {
fn complexity(
&self,
lwe_dimension: u64, //n
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(BR)
br_decomposition_base_log: u64, //b(BR)
ciphertext_modulus_log: u64, // log2_q
params: PbsParameters,
ciphertext_modulus_log: u64, // log2_q
) -> Complexity;
}
@@ -18,24 +15,12 @@ pub struct CmuxProportional<CMUX: cmux::CmuxComplexity> {
}
impl<CMUX: cmux::CmuxComplexity> PbsComplexity for CmuxProportional<CMUX> {
fn complexity(
&self,
lwe_dimension: u64, //n
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(BR)
br_decomposition_base_log: u64, //b(BR)
ciphertext_modulus_log: u64, //log2_q
) -> Complexity {
fn complexity(&self, params: PbsParameters, ciphertext_modulus_log: u64) -> Complexity {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L163
let cmux_cost = self.cmux.complexity(
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
ciphertext_modulus_log,
);
(lwe_dimension as f64) * cmux_cost
let cmux_cost = self
.cmux
.complexity(params.cmux_parameters(), ciphertext_modulus_log);
(params.internal_lwe_dimension.0 as f64) * cmux_cost
}
}
@@ -47,6 +32,10 @@ pub const DEFAULT: Default = CmuxProportional {
#[cfg(test)]
pub mod tests {
use crate::computing_cost::operators::pbs::PbsParameters;
use crate::parameters::{BrDecompositionParameters, GlweParameters, LweDimension};
use super::super::cmux;
use super::{CmuxProportional, Default, PbsComplexity, DEFAULT};
@@ -58,15 +47,40 @@ pub mod tests {
fn golden_python_prototype() {
let ignored = 0;
let golden = 8.0;
let actual = DEFAULT.complexity(1, 1, 1, 1, ignored, 32);
let pbs_param1 = PbsParameters {
internal_lwe_dimension: LweDimension(1),
br_decomposition_parameter: BrDecompositionParameters {
level: 1,
log2_base: ignored,
},
output_glwe_params: GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: 1,
},
};
let pbs_param2 = PbsParameters {
internal_lwe_dimension: LweDimension(1024),
br_decomposition_parameter: BrDecompositionParameters {
level: 56,
log2_base: ignored,
},
output_glwe_params: GlweParameters {
log2_polynomial_size: 12,
glwe_dimension: 1024,
},
};
let actual = DEFAULT.complexity(pbs_param1, 32);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 249957554585600.0;
let actual = DEFAULT.complexity(1024, 4096, 1024, 56, ignored, 64);
let actual = DEFAULT.complexity(pbs_param2, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
let golden = 208532086206064.16;
let actual = COST_AWS.complexity(1024, 4096, 1024, 56, ignored, 64);
let actual = COST_AWS.complexity(pbs_param2, 64);
approx::assert_relative_eq!(golden, actual, epsilon = f64::EPSILON);
}
}

View File

@@ -1,3 +1,8 @@
use crate::parameters::{
AtomicPatternParameters, BrDecompositionParameters, GlweParameters, KeyswitchParameters,
PbsParameters,
};
use crate::security;
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_commons::key_kinds::BinaryKeyKind;
use concrete_commons::numeric::UnsignedInteger;
@@ -5,13 +10,9 @@ use concrete_commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
};
use crate::security;
/// Additional noise generated by the keyswitch step.
pub fn variance_keyswitch<W: UnsignedInteger>(
input_lwe_dimension: u64, //n_big
ks_decomposition_level_count: u64, //l(BS)
ks_decomposition_base_log: u64, //b(BS)
param: KeyswitchParameters,
ciphertext_modulus_log: u64,
variance_ksk: Variance,
) -> Variance {
@@ -22,11 +23,11 @@ pub fn variance_keyswitch<W: UnsignedInteger>(
Variance,
BinaryKeyKind,
>(
LweDimension(input_lwe_dimension as usize),
LweDimension(param.input_lwe_dimension.0 as usize),
Variance(0.0),
variance_ksk,
DecompositionBaseLog(ks_decomposition_base_log as usize),
DecompositionLevelCount(ks_decomposition_level_count as usize),
DecompositionBaseLog(param.ks_decomposition_parameter.log2_base as usize),
DecompositionLevelCount(param.ks_decomposition_parameter.level as usize),
)
}
@@ -36,30 +37,25 @@ pub fn variance_ksk(
ciphertext_modulus_log: u64,
security_level: u64,
) -> Variance {
let glwe_polynomial_size = 1;
let glwe_dimension = internal_ks_output_lwe_dimension;
let glwe_params = GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: internal_ks_output_lwe_dimension,
};
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/keyswitch.py#L13
security::glwe::minimal_variance(
glwe_polynomial_size,
glwe_dimension,
ciphertext_modulus_log,
security_level,
)
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level)
}
/// Additional noise generated by fft computation
pub fn fft_noise<W: UnsignedInteger>(
internal_ks_output_lwe_dimension: u64, //n_small
glwe_polynomial_size: u64, //N
_glwe_dimension: u64, //k, unused
br_decomposition_level_count: u64,
br_decomposition_base_log: u64,
glwe_params: GlweParameters,
br_decomposition_parameter: BrDecompositionParameters,
) -> Variance {
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L25
let n = internal_ks_output_lwe_dimension as f64;
let b = 2_f64.powi(br_decomposition_base_log as i32);
let l = br_decomposition_level_count as f64;
let big_n = glwe_polynomial_size as f64;
let b = 2_f64.powi(br_decomposition_parameter.log2_base as i32);
let l = br_decomposition_parameter.level as f64;
let big_n = (1 << glwe_params.log2_polynomial_size) as f64;
// 22 = 2 x 11, 11 = 64 -53
let scale_margin = (1_u64 << 22) as f64;
let res = n * 0.1 * scale_margin * l * b * b * big_n.powf(2.0);
@@ -69,29 +65,24 @@ pub fn fft_noise<W: UnsignedInteger>(
/// Final reduced noise generated by the final bootstrap step.
/// Note that it does not depends from input noise, assuming the bootstrap is successful
pub fn variance_bootstrap<W: UnsignedInteger>(
internal_ks_output_lwe_dimension: u64, //n_small
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(KS)
br_decomposition_base_log: u64, //b(ks)
param: PbsParameters,
ciphertext_modulus_log: u64,
variance_bsk: Variance,
) -> Variance {
assert!(ciphertext_modulus_log == W::BITS as u64);
let out_variance_pbs = concrete_npe::estimate_pbs_noise::<W, Variance, BinaryKeyKind>(
LweDimension(internal_ks_output_lwe_dimension as usize),
PolynomialSize(glwe_polynomial_size as usize),
GlweDimension(glwe_dimension as usize),
DecompositionBaseLog(br_decomposition_base_log as usize),
DecompositionLevelCount(br_decomposition_level_count as usize),
LweDimension(param.internal_lwe_dimension.0 as usize),
PolynomialSize(1 << param.output_glwe_params.log2_polynomial_size as usize),
GlweDimension(param.output_glwe_params.glwe_dimension as usize),
DecompositionBaseLog(param.br_decomposition_parameter.log2_base as usize),
DecompositionLevelCount(param.br_decomposition_parameter.level as usize),
variance_bsk,
);
let additional_fft_noise = fft_noise::<W>(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
param.internal_lwe_dimension.0,
param.output_glwe_params,
param.br_decomposition_parameter,
);
Variance(out_variance_pbs.get_variance() + additional_fft_noise.get_variance())
}
@@ -114,12 +105,10 @@ where
pub fn maximal_noise<D, W>(
input_variance: Variance,
input_lwe_dimension: u64, //n_big
internal_ks_output_lwe_dimension: u64, //n_small
ks_decomposition_level_count: u64, //l(BS)
ks_decomposition_base_log: u64, //b(BS)
glwe_polynomial_size: u64, //N
ciphertext_modulus_log: u64, //log(q)
param: AtomicPatternParameters,
ciphertext_modulus_log: u64, //log(q)
security_level: u64,
) -> Variance
where
@@ -128,19 +117,17 @@ where
{
assert!(ciphertext_modulus_log == W::BITS as u64);
let v_keyswitch = variance_keyswitch::<W>(
input_lwe_dimension,
ks_decomposition_level_count,
ks_decomposition_base_log,
param.ks_parameters(),
ciphertext_modulus_log,
variance_ksk(
internal_ks_output_lwe_dimension,
param.internal_lwe_dimension.0,
ciphertext_modulus_log,
security_level,
),
);
let v_modulus_switch = estimate_modulus_switching_noise_with_binary_key::<W>(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
param.internal_lwe_dimension.0,
1 << param.output_glwe_params.log2_polynomial_size,
);
Variance(
input_variance.get_variance()
@@ -153,15 +140,9 @@ where
pub fn maximal_noise_multi_sum<D, W, Ignored>(
dispersions: &[D],
weights_tuples: &[(W, Ignored)],
input_lwe_dimension: u64, //n_big
internal_ks_output_lwe_dimension: u64, //n_small
ks_decomposition_level_count: u64, //l(BS)
ks_decomposition_base_log: u64, //b(BS)
glwe_polynomial_size: u64, //N
_glwe_dimension: u64, //k
_br_decomposition_level_count: u64, //l(KS)
_br_decomposition_base_log: u64, //b(ks)
ciphertext_modulus_log: u64, //log(q)
param: AtomicPatternParameters,
ciphertext_modulus_log: u64,
security_level: u64,
) -> Variance
where
@@ -180,11 +161,7 @@ where
};
maximal_noise::<D, W>(
v_out_multi_sum,
input_lwe_dimension,
internal_ks_output_lwe_dimension,
ks_decomposition_level_count,
ks_decomposition_base_log,
glwe_polynomial_size,
param,
ciphertext_modulus_log,
security_level,
)
@@ -192,14 +169,7 @@ where
/// The output noise is the variance boostrap.
pub fn output_noise<D, W>(
_input_lwe_dimension: u64, //n_big
internal_ks_output_lwe_dimension: u64, //n_small
_ks_decomposition_level_count: u64, //l(BS)
_ks_decomposition_base_log: u64, //b(BS)
glwe_polynomial_size: u64, //N
glwe_dimension: u64, //k
br_decomposition_level_count: u64, //l(KS)
br_decomposition_base_log: u64, //b(ks)
param: AtomicPatternParameters,
ciphertext_modulus_log: u64,
security_level: u64,
) -> Variance
@@ -209,39 +179,39 @@ where
{
// https://github.com/zama-ai/concrete-optimizer/blob/prototype/python/optimizer/noise_formulas/bootstrap.py#L66
let variance_bsk = security::glwe::minimal_variance(
glwe_polynomial_size,
glwe_dimension,
param.output_glwe_params,
ciphertext_modulus_log,
security_level,
);
variance_bootstrap::<W>(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
ciphertext_modulus_log,
variance_bsk,
)
variance_bootstrap::<W>(param.pbs_parameters(), ciphertext_modulus_log, variance_bsk)
}
#[cfg(test)]
mod tests {
use crate::parameters::{
BrDecompositionParameters, GlweParameters, KsDecompositionParameters, LweDimension,
};
use super::*;
#[test]
fn golden_python_prototype_security_variance_keyswitch_1() {
let golden_modular_variance = 3.260_702_274_017_557e68;
let input_lwe_dimension = 4096;
let internal_ks_output_lwe_dimension = 1024;
let ks_decomposition_level_count = 9;
let ks_decomposition_base_log = 5;
let ciphertext_modulus_log = 128;
let security = 128;
let param = KeyswitchParameters {
input_lwe_dimension: LweDimension(4096),
output_lwe_dimension: LweDimension(internal_ks_output_lwe_dimension),
ks_decomposition_parameter: KsDecompositionParameters {
level: 9,
log2_base: 5,
},
};
let actual = variance_keyswitch::<u128>(
input_lwe_dimension,
ks_decomposition_level_count,
ks_decomposition_base_log,
param,
ciphertext_modulus_log,
variance_ksk(
internal_ks_output_lwe_dimension,
@@ -258,16 +228,21 @@ mod tests {
// let golden_modular_variance = 8.580795457940938e+66;
// the full npe implements a part of the full estimation
let golden_modular_variance = 3.941_898_681_369_209e48; // full estimation
let input_lwe_dimension = 2048;
let internal_ks_output_lwe_dimension = 512;
let ks_decomposition_level_count = 2;
let ks_decomposition_base_log = 24;
let ciphertext_modulus_log = 64;
let security = 128;
let param = KeyswitchParameters {
input_lwe_dimension: LweDimension(2048),
output_lwe_dimension: LweDimension(internal_ks_output_lwe_dimension),
ks_decomposition_parameter: KsDecompositionParameters {
level: 2,
log2_base: 24,
},
};
let actual = variance_keyswitch::<u64>(
input_lwe_dimension,
ks_decomposition_level_count,
ks_decomposition_base_log,
param,
ciphertext_modulus_log,
variance_ksk(
internal_ks_output_lwe_dimension,
@@ -282,29 +257,26 @@ mod tests {
#[test]
fn security_variance_bootstrap_1() {
let ref_modular_variance = 8.112_963_910_722_068e30;
let internal_ks_output_lwe_dimension = 2048;
let glwe_polynomial_size = 4096;
let glwe_dimension = 10;
let br_decomposition_level_count = 2;
let br_decomposition_base_log = 24;
let glwe_params = GlweParameters {
log2_polynomial_size: 12,
glwe_dimension: 10,
};
let ciphertext_modulus_log = 64;
let security = 128;
let variance_bsk = security::glwe::minimal_variance(
glwe_polynomial_size,
glwe_dimension,
ciphertext_modulus_log,
security,
);
let actual = variance_bootstrap::<u64>(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
ciphertext_modulus_log,
variance_bsk,
)
.get_modular_variance::<u64>();
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security);
let param = PbsParameters {
internal_lwe_dimension: LweDimension(2048),
br_decomposition_parameter: BrDecompositionParameters {
level: 2,
log2_base: 24,
},
output_glwe_params: glwe_params,
};
let actual = variance_bootstrap::<u64>(param, ciphertext_modulus_log, variance_bsk)
.get_modular_variance::<u64>();
approx::assert_relative_eq!(actual, ref_modular_variance, max_relative = 1e-8);
}
@@ -312,29 +284,26 @@ mod tests {
fn golden_python_prototype_security_variance_bootstrap_2() {
// golden value include fft correction
let golden_modular_variance = 1.307_769_436_943_601_9e56;
let internal_ks_output_lwe_dimension = 1024;
let glwe_polynomial_size = 4096;
let glwe_dimension = 16;
let br_decomposition_level_count = 9;
let br_decomposition_base_log = 5;
let glwe_params = GlweParameters {
log2_polynomial_size: 12,
glwe_dimension: 16,
};
let ciphertext_modulus_log = 128;
let security = 128;
let variance_bsk = security::glwe::minimal_variance(
glwe_polynomial_size,
glwe_dimension,
ciphertext_modulus_log,
security,
);
let actual = variance_bootstrap::<u128>(
internal_ks_output_lwe_dimension,
glwe_polynomial_size,
glwe_dimension,
br_decomposition_level_count,
br_decomposition_base_log,
ciphertext_modulus_log,
variance_bsk,
)
.get_modular_variance::<u128>();
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security);
let param = PbsParameters {
internal_lwe_dimension: LweDimension(1024),
br_decomposition_parameter: BrDecompositionParameters {
level: 9,
log2_base: 5,
},
output_glwe_params: glwe_params,
};
let actual = variance_bootstrap::<u128>(param, ciphertext_modulus_log, variance_bsk)
.get_modular_variance::<u128>();
approx::assert_relative_eq!(actual, golden_modular_variance, max_relative = 1e-8);
}
}

View File

@@ -1,17 +1,18 @@
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_commons::numeric::UnsignedInteger;
use crate::computing_cost::operators::atomic_pattern as complexity_atomic_pattern;
use complexity_atomic_pattern::AtomicPatternComplexity;
use crate::computing_cost::operators::keyswitch_lwe::KeySwitchLWEComplexity;
use crate::computing_cost::operators::pbs::PbsComplexity;
use crate::noise_estimator::error::{
error_probability_of_sigma_scale, sigma_scale_of_error_probability,
};
use crate::noise_estimator::operators::atomic_pattern as noise_atomic_pattern;
use crate::parameters::{
AtomicPatternParameters, BrDecompositionParameters, GlweParameters, KeyswitchParameters,
KsDecompositionParameters, LweDimension, PbsParameters,
};
use crate::security;
use complexity_atomic_pattern::AtomicPatternComplexity;
use concrete_commons::dispersion::{DispersionParameter, Variance};
use concrete_commons::numeric::UnsignedInteger;
#[rustfmt::skip]
const BR_BL: &[(u64, u64); 35] = &[
@@ -65,8 +66,8 @@ struct OptimizationDecompositionsConsts {
security_level: u64,
noise_factor: f64,
ciphertext_modulus_log: u64,
keyswitch_decompositions: Vec<(u64, u64)>,
blind_rotate_decompositions: Vec<(u64, u64)>,
keyswitch_decompositions: Vec<KsDecompositionParameters>,
blind_rotate_decompositions: Vec<BrDecompositionParameters>,
variance_max: f64,
}
@@ -88,8 +89,7 @@ impl ComplexityNoise {
fn blind_rotate_quantities<W: UnsignedInteger>(
consts: &OptimizationDecompositionsConsts,
internal_dim: u64,
glwe_poly_size: u64,
glwe_dim: u64,
glwe_params: GlweParameters,
cut_complexity: f64,
cut_noise: f64,
) -> Vec<ComplexityNoise> {
@@ -98,36 +98,32 @@ fn blind_rotate_quantities<W: UnsignedInteger>(
let ciphertext_modulus_log = consts.ciphertext_modulus_log;
let security_level = consts.security_level;
let variance_bsk = security::glwe::minimal_variance(
glwe_poly_size,
glwe_dim,
ciphertext_modulus_log,
security_level,
);
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level);
let mut increasing_complexity = 0.0;
let mut decreasing_variance = f64::INFINITY;
let mut size = 0;
for (i_br, &(br_b, br_l)) in consts.blind_rotate_decompositions.iter().enumerate() {
let complexity_pbs = complexity_atomic_pattern::DEFAULT.pbs.complexity(
internal_dim,
glwe_poly_size,
glwe_dim,
br_l,
br_b,
consts.ciphertext_modulus_log,
);
for (i_br, &br_decomposition_parameter) in consts.blind_rotate_decompositions.iter().enumerate()
{
let pbs_parameters = PbsParameters {
internal_lwe_dimension: LweDimension(internal_dim),
br_decomposition_parameter,
output_glwe_params: glwe_params,
};
let complexity_pbs = complexity_atomic_pattern::DEFAULT
.pbs
.complexity(pbs_parameters, ciphertext_modulus_log);
if cut_complexity < complexity_pbs && CUTS {
break; // complexity is increasing
}
let base_noise = noise_atomic_pattern::variance_bootstrap::<W>(
internal_dim,
glwe_poly_size,
glwe_dim,
br_l,
br_b,
consts.ciphertext_modulus_log,
pbs_parameters,
ciphertext_modulus_log,
variance_bsk,
);
let noise_in = base_noise.get_variance() * square(consts.noise_factor);
if cut_noise < noise_in && CUTS {
continue; // noise is decreasing
@@ -177,21 +173,21 @@ fn keyswitch_quantities<W: UnsignedInteger>(
let mut increasing_complexity = 0.0;
let mut decreasing_variance = f64::INFINITY;
let mut size = 0;
for (i_ks, &(ks_b, ks_l)) in consts.keyswitch_decompositions.iter().enumerate() {
let complexity_keyswitch = complexity_atomic_pattern::DEFAULT.ks_lwe.complexity(
in_dim,
internal_dim,
ks_l,
ks_b,
ciphertext_modulus_log,
);
for (i_ks, &ks_decomposition_parameter) in consts.keyswitch_decompositions.iter().enumerate() {
let keyswitch_parameter = KeyswitchParameters {
input_lwe_dimension: LweDimension(in_dim),
output_lwe_dimension: LweDimension(internal_dim),
ks_decomposition_parameter,
};
let complexity_keyswitch = complexity_atomic_pattern::DEFAULT
.ks_lwe
.complexity(keyswitch_parameter, ciphertext_modulus_log);
if cut_complexity < complexity_keyswitch && CUTS {
break;
}
let noise_keyswitch = noise_atomic_pattern::variance_keyswitch::<W>(
in_dim,
ks_l,
ks_b,
keyswitch_parameter,
ciphertext_modulus_log,
variance_ksk,
)
@@ -237,10 +233,10 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
state: &mut OptimizationState,
consts: &OptimizationDecompositionsConsts,
internal_dim: u64,
glwe_poly_size: u64,
glwe_dim: u64,
glwe_params: GlweParameters,
) {
let input_lwe_dimension = glwe_dim * glwe_poly_size;
let glwe_poly_size = 1 << glwe_params.log2_polynomial_size;
let input_lwe_dimension = glwe_params.glwe_dimension * glwe_poly_size;
let noise_modulus_switching =
noise_atomic_pattern::estimate_modulus_switching_noise_with_binary_key::<W>(
internal_dim,
@@ -258,14 +254,8 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
let complexity_multisum = (consts.sum_size * input_lwe_dimension) as f64;
let mut cut_complexity = best_complexity - complexity_multisum;
let mut cut_noise = variance_max - noise_modulus_switching;
let br_quantities = blind_rotate_quantities::<W>(
consts,
internal_dim,
glwe_poly_size,
glwe_dim,
cut_complexity,
cut_noise,
);
let br_quantities =
blind_rotate_quantities::<W>(consts, internal_dim, glwe_params, cut_complexity, cut_noise);
if br_quantities.is_empty() {
return;
}
@@ -315,8 +305,7 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
assert_checks::<W>(
consts,
internal_dim,
glwe_poly_size,
glwe_dim,
glwe_params,
input_lwe_dimension,
ks_quantity,
br_quantity,
@@ -350,8 +339,14 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
let i_br = br_quantity.index;
let i_ks = ks_quantity.index;
let (br_b, br_l) = consts.blind_rotate_decompositions[i_br];
let (ks_b, ks_l) = consts.keyswitch_decompositions[i_ks];
let BrDecompositionParameters {
level: br_l,
log2_base: br_b,
} = consts.blind_rotate_decompositions[i_br];
let KsDecompositionParameters {
level: ks_l,
log2_base: ks_b,
} = consts.keyswitch_decompositions[i_ks];
best_complexity = complexity;
best_variance = noise_max;
@@ -360,8 +355,8 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
internal_ks_output_lwe_dimension: internal_dim,
ks_decomposition_level_count: ks_l,
ks_decomposition_base_log: ks_b,
glwe_polynomial_size: glwe_poly_size,
glwe_dimension: glwe_dim,
glwe_polynomial_size: 1 << glwe_params.log2_polynomial_size,
glwe_dimension: glwe_params.glwe_dimension,
br_decomposition_level_count: br_l,
br_decomposition_base_log: br_b,
noise_max,
@@ -375,11 +370,11 @@ fn update_state_with_best_decompositions<W: UnsignedInteger>(
// This function provides reference values with unoptimised code, until we have non regeression tests
#[allow(clippy::float_cmp)]
#[allow(clippy::too_many_lines)]
fn assert_checks<W: UnsignedInteger>(
consts: &OptimizationDecompositionsConsts,
internal_dim: u64,
glwe_poly_size: u64,
glwe_dim: u64,
glwe_params: GlweParameters,
input_lwe_dimension: u64,
ks_c_n: ComplexityNoise,
br_c_n: ComplexityNoise,
@@ -395,61 +390,63 @@ fn assert_checks<W: UnsignedInteger>(
let complexity_pbs = br_c_n.complexity;
let ciphertext_modulus_log = consts.ciphertext_modulus_log;
let security_level = consts.security_level;
let (br_b, br_l) = consts.blind_rotate_decompositions[i_br];
let (ks_b, ks_l) = consts.keyswitch_decompositions[i_ks];
let variance_bsk = security::glwe::minimal_variance(
glwe_poly_size,
glwe_dim,
ciphertext_modulus_log,
security_level,
);
let br_decomposition_parameter = consts.blind_rotate_decompositions[i_br];
let ks_decomposition_parameter = consts.keyswitch_decompositions[i_ks];
let variance_bsk =
security::glwe::minimal_variance(glwe_params, ciphertext_modulus_log, security_level);
let pbs_parameters = PbsParameters {
internal_lwe_dimension: LweDimension(internal_dim),
br_decomposition_parameter,
output_glwe_params: glwe_params,
};
let base_noise_ = noise_atomic_pattern::variance_bootstrap::<W>(
internal_dim,
glwe_poly_size,
glwe_dim,
br_l,
br_b,
pbs_parameters,
ciphertext_modulus_log,
variance_bsk,
);
let noise_in_ = base_noise_.get_variance() * square(consts.noise_factor);
let complexity_pbs_ = complexity_atomic_pattern::DEFAULT.pbs.complexity(
internal_dim,
glwe_poly_size,
glwe_dim,
br_l,
br_b,
ciphertext_modulus_log,
);
let complexity_pbs_ = complexity_atomic_pattern::DEFAULT
.pbs
.complexity(pbs_parameters, ciphertext_modulus_log);
assert!(complexity_pbs == complexity_pbs_);
assert!(noise_in == noise_in_);
let variance_ksk =
noise_atomic_pattern::variance_ksk(internal_dim, ciphertext_modulus_log, security_level);
let keyswitch_parameters = KeyswitchParameters {
input_lwe_dimension: LweDimension(input_lwe_dimension),
output_lwe_dimension: LweDimension(internal_dim),
ks_decomposition_parameter,
};
let noise_keyswitch_ = noise_atomic_pattern::variance_keyswitch::<W>(
input_lwe_dimension,
ks_l,
ks_b,
keyswitch_parameters,
ciphertext_modulus_log,
variance_ksk,
)
.get_variance();
let complexity_keyswitch_ = complexity_atomic_pattern::DEFAULT.ks_lwe.complexity(
input_lwe_dimension,
internal_dim,
ks_l,
ks_b,
ciphertext_modulus_log,
);
let complexity_keyswitch_ = complexity_atomic_pattern::DEFAULT
.ks_lwe
.complexity(keyswitch_parameters, ciphertext_modulus_log);
assert!(complexity_keyswitch == complexity_keyswitch_);
assert!(noise_keyswitch == noise_keyswitch_);
let atomic_pattern_parameters = AtomicPatternParameters {
input_lwe_dimension: LweDimension(input_lwe_dimension),
ks_decomposition_parameter,
internal_lwe_dimension: LweDimension(internal_dim),
br_decomposition_parameter,
output_glwe_params: glwe_params,
};
let check_max_noise = noise_atomic_pattern::maximal_noise::<Variance, W>(
Variance(noise_in),
input_lwe_dimension,
internal_dim,
ks_l,
ks_b,
glwe_poly_size,
atomic_pattern_parameters,
ciphertext_modulus_log,
security_level,
)
@@ -457,14 +454,7 @@ fn assert_checks<W: UnsignedInteger>(
assert!(f64::abs(noise_max - check_max_noise) / check_max_noise < 0.00000000001);
let check_complexity = complexity_atomic_pattern::DEFAULT.complexity(
consts.sum_size,
input_lwe_dimension,
internal_dim,
ks_l,
ks_b,
glwe_poly_size,
glwe_dim,
br_l,
br_b,
atomic_pattern_parameters,
ciphertext_modulus_log,
);
@@ -521,8 +511,12 @@ pub fn optimise_one<W: UnsignedInteger>(
security_level,
noise_factor,
ciphertext_modulus_log,
keyswitch_decompositions: KS_BL.to_vec(),
blind_rotate_decompositions: BR_BL.to_vec(),
keyswitch_decompositions: KS_BL
.map(|(log2_base, level)| KsDecompositionParameters { level, log2_base })
.to_vec(),
blind_rotate_decompositions: BR_BL
.map(|(log2_base, level)| BrDecompositionParameters { level, log2_base })
.to_vec(),
variance_max: variance_max.get_variance(),
};
@@ -570,14 +564,18 @@ pub fn optimise_one<W: UnsignedInteger>(
continue;
}
let glwe_params = GlweParameters {
log2_polynomial_size: glwe_log_poly_size,
glwe_dimension: glwe_dim,
};
for &internal_dim in internal_lwe_dimensions {
assert!(256 < internal_dim);
update_state_with_best_decompositions::<W>(
&mut state,
&consts,
internal_dim,
glwe_poly_size,
glwe_dim,
glwe_params,
);
}
}

View File

@@ -23,13 +23,11 @@ mod individual {
}
#[derive(Clone, Copy)]
pub struct LweDimension {
pub lwe_dimension: u32,
}
pub struct LweDimension(pub u64);
#[derive(Copy, Clone)]
pub struct InputParameter {
pub lwe_dimension: u32,
pub lwe_dimension: u64,
}
#[derive(Copy, Clone)]
@@ -40,6 +38,53 @@ mod individual {
pub br_decomposition_parameter: BrDecompositionParameters,
pub output_glwe_params: GlweParameters,
}
impl AtomicPatternParameters {
pub fn pbs_parameters(self) -> PbsParameters {
PbsParameters {
internal_lwe_dimension: self.internal_lwe_dimension,
br_decomposition_parameter: self.br_decomposition_parameter,
output_glwe_params: self.output_glwe_params,
}
}
pub fn ks_parameters(self) -> KeyswitchParameters {
KeyswitchParameters {
input_lwe_dimension: self.input_lwe_dimension,
output_lwe_dimension: self.internal_lwe_dimension,
ks_decomposition_parameter: self.ks_decomposition_parameter,
}
}
}
#[derive(Clone, Copy)]
pub struct PbsParameters {
pub internal_lwe_dimension: LweDimension,
pub br_decomposition_parameter: BrDecompositionParameters,
pub output_glwe_params: GlweParameters,
}
impl PbsParameters {
pub fn cmux_parameters(self) -> CmuxParameters {
CmuxParameters {
br_decomposition_parameter: self.br_decomposition_parameter,
output_glwe_params: self.output_glwe_params,
}
}
}
#[derive(Clone, Copy)]
pub struct CmuxParameters {
pub br_decomposition_parameter: BrDecompositionParameters,
pub output_glwe_params: GlweParameters,
}
#[derive(Clone, Copy)]
pub struct KeyswitchParameters {
pub input_lwe_dimension: LweDimension, //n_big
pub output_lwe_dimension: LweDimension, //n_small
pub ks_decomposition_parameter: KsDecompositionParameters,
}
}
mod range {

View File

@@ -1,6 +1,7 @@
use crate::noise_estimator::operators::atomic_pattern::{variance_bootstrap, variance_keyswitch};
use crate::parameters::{
BrDecompositionParameters, GlweParameterRanges, KsDecompositionParameters,
BrDecompositionParameters, GlweParameterRanges, GlweParameters, KeyswitchParameters,
KsDecompositionParameters, LweDimension, PbsParameters,
};
use crate::security::glwe::minimal_variance;
use concrete_commons::dispersion::Variance;
@@ -16,14 +17,13 @@ pub fn extract_br_pareto(
for glwe_dimension in &output_glwe_range.glwe_dimension {
for log2_polynomial_size in &output_glwe_range.log2_polynomial_size {
let polynomial_size = 1 << log2_polynomial_size;
let variance_bsk = minimal_variance(
polynomial_size,
let glwe_params = GlweParameters {
log2_polynomial_size,
glwe_dimension,
ciphertext_modulus_log,
security_level,
);
};
let variance_bsk =
minimal_variance(glwe_params, ciphertext_modulus_log, security_level);
for input_lwe_dimension in &input_lwe_range.lwe_dimension {
let mut min_variance = Variance(f64::INFINITY);
@@ -37,12 +37,20 @@ pub fn extract_br_pareto(
let mut log_base_arg_min = None;
for log2_base in 1..=(MAX_DECOMPOSITION_DEPTH / level) {
let pbs_parameters = PbsParameters {
internal_lwe_dimension: LweDimension(input_lwe_dimension),
br_decomposition_parameter: BrDecompositionParameters {
level,
log2_base,
},
output_glwe_params: GlweParameters {
log2_polynomial_size,
glwe_dimension,
},
};
let variance = variance_bootstrap::<u64>(
input_lwe_dimension,
polynomial_size,
glwe_dimension,
level,
log2_base,
pbs_parameters,
ciphertext_modulus_log,
variance_bsk,
);
@@ -85,8 +93,10 @@ pub fn extract_ks_pareto(
for output_lwe_dimension in &output_lwe_range.lwe_dimension {
let variance_ksk = minimal_variance(
1,
output_lwe_dimension,
GlweParameters {
log2_polynomial_size: 0,
glwe_dimension: output_lwe_dimension,
},
ciphertext_modulus_log,
security_level,
);
@@ -103,10 +113,17 @@ pub fn extract_ks_pareto(
let mut log2_base_arg_min = None;
for log2_base in 1..=(ciphertext_modulus_log / level) {
let keyswitch_parameters = KeyswitchParameters {
input_lwe_dimension: LweDimension(input_lwe_dimension),
output_lwe_dimension: LweDimension(output_lwe_dimension),
ks_decomposition_parameter: KsDecompositionParameters {
level,
log2_base,
},
};
let variance = variance_keyswitch::<u64>(
input_lwe_dimension,
level,
log2_base,
keyswitch_parameters,
ciphertext_modulus_log,
variance_ksk,
);

View File

@@ -1,10 +1,11 @@
use concrete_commons::dispersion::Variance;
use crate::parameters::GlweParameters;
/// Noise ensuring security
// It was 128 bits of security on the 30th August 2021 with https://bitbucket.org/malb/lwe-estimator/commits/fb7deba98e599df10b665eeb6a26332e43fb5004
pub fn minimal_variance(
glwe_polynomial_size: u64,
glwe_dimension: u64,
glwe_params: GlweParameters,
ciphertext_modulus_log: u64,
security_level: u64,
) -> Variance {
@@ -16,7 +17,8 @@ pub fn minimal_variance(
);
let espsilon_log2_std_modular = 2.0;
let espsilon_log2_std = espsilon_log2_std_modular - (ciphertext_modulus_log as f64);
let equiv_lwe_dimension = (glwe_dimension * glwe_polynomial_size) as f64;
let equiv_lwe_dimension =
(glwe_params.glwe_dimension * (1 << glwe_params.log2_polynomial_size)) as f64;
let secure_log2_std = -0.026374888765705498 * equiv_lwe_dimension + 2.012143923330495;
// TODO: could be added instead
let log2_std = f64::max(secure_log2_std, espsilon_log2_std);
@@ -32,12 +34,14 @@ mod tests {
#[test]
fn golden_python_prototype_security_security_glwe_variance_low() {
// python securityFunc(10,14,64)= 0.3120089883926036
let log_poly_size = 14;
let glwe_dimension = 10;
let integer_size = 64;
let golden_std_dev = 0.312_008_988_392_603_6;
let golden_std_dev = 2.168404344971009e-19;
let security_level = 128;
let actual = minimal_variance(log_poly_size, glwe_dimension, integer_size, security_level);
let glwe_params = GlweParameters {
log2_polynomial_size: 14,
glwe_dimension: 10,
};
let actual = minimal_variance(glwe_params, integer_size, security_level);
approx::assert_relative_eq!(
golden_std_dev,
actual.get_standard_dev(),
@@ -48,12 +52,15 @@ mod tests {
#[test]
fn golden_python_prototype_security_security_glwe_variance_high() {
// python securityFunc(3,8,32)= 2.6011445832514504
let log_poly_size = 8;
let glwe_dimension = 3;
let integer_size = 32;
let golden_std_dev = 2.6011445832514504;
let golden_std_dev = 3.2216458741669603e-6;
let security_level = 128;
let actual = minimal_variance(log_poly_size, glwe_dimension, integer_size, security_level);
let glwe_params = GlweParameters {
log2_polynomial_size: 8,
glwe_dimension: 3,
};
let actual = minimal_variance(glwe_params, integer_size, security_level);
approx::assert_relative_eq!(
golden_std_dev,
actual.get_standard_dev(),