Added glwe rank switch before glwe -> lwe

This commit is contained in:
Pro7ech
2025-11-19 10:15:30 +01:00
parent c23cf7803b
commit 43fc5119f4
10 changed files with 181 additions and 60 deletions

View File

@@ -52,8 +52,8 @@ where
{
let (mut pt_have, scratch_1) = scratch.take_glwe_plaintext(res);
self.glwe_decrypt(res, &mut pt_have, sk_prepared, scratch_1);
//println!("pt_have: {pt_have}");
//println!("pt_want: {}", pt_want.to_ref());
// println!("pt_have: {pt_have}");
// println!("pt_want: {}", pt_want.to_ref());
self.glwe_sub_inplace(&mut pt_have, pt_want);
self.glwe_normalize_inplace(&mut pt_have, scratch_1);
pt_have.data.stats(pt_have.base2k().into(), 0)

View File

@@ -1,9 +1,10 @@
use poulpy_core::{
GLWEAdd, GLWECopy, GLWEDecrypt, GLWEEncryptSk, GLWENoise, GLWEPacking, GLWERotate, GLWESub, GLWETrace, LWEFromGLWE,
ScratchTakeCore,
GLWEAdd, GLWECopy, GLWEDecrypt, GLWEEncryptSk, GLWEKeyswitch, GLWENoise, GLWEPacking, GLWERotate, GLWESub, GLWETrace,
LWEFromGLWE, ScratchTakeCore,
layouts::{
Base2K, Degree, GGLWEInfos, GGLWEPreparedToRef, GLWE, GLWEAutomorphismKeyHelper, GLWEInfos, GLWEPlaintextLayout,
GLWESecretPreparedToRef, GLWEToMut, GLWEToRef, GetGaloisElement, LWEInfos, LWEToMut, Rank, TorusPrecision,
Base2K, Degree, GGLWEInfos, GGLWEPreparedToRef, GLWE, GLWEAutomorphismKeyHelper, GLWEInfos, GLWELayout,
GLWEPlaintextLayout, GLWESecretPreparedToRef, GLWEToMut, GLWEToRef, GetGaloisElement, LWEInfos, LWEToMut, Rank,
TorusPrecision,
},
};
use poulpy_hal::{
@@ -323,16 +324,41 @@ where
impl<T: UnsignedInteger, BE: Backend> ScratchTakeBDD<T, BE> for Scratch<BE> where Self: ScratchTakeCore<BE> {}
impl<D: DataRef, T: UnsignedInteger> FheUint<D, T> {
pub fn get_bit_lwe<R, K, M, BE: Backend>(&self, module: &M, bit: usize, res: &mut R, ks: &K, scratch: &mut Scratch<BE>)
where
pub fn get_bit_lwe<R, KGLWE, KLWE, M, BE: Backend>(
&self,
module: &M,
bit: usize,
res: &mut R,
ks_glwe: Option<&KGLWE>,
ks_lwe: &KLWE,
scratch: &mut Scratch<BE>,
) where
R: LWEToMut,
K: GGLWEPreparedToRef<BE> + GGLWEInfos,
M: ModuleLogN + LWEFromGLWE<BE> + GLWERotate<BE>,
KGLWE: GGLWEPreparedToRef<BE> + GGLWEInfos,
KLWE: GGLWEPreparedToRef<BE> + GGLWEInfos,
M: ModuleLogN + LWEFromGLWE<BE> + GLWERotate<BE> + GLWEKeyswitch<BE>,
Scratch<BE>: ScratchTakeCore<BE>,
{
let log_gap: usize = module.log_n() - T::LOG_BITS as usize;
if let Some(ks_glwe) = ks_glwe {
let (mut res_tmp, scratch_1) = scratch.take_glwe(&GLWELayout {
n: self.n(),
base2k: ks_lwe.base2k(),
k: ks_lwe.k().min(self.k()),
rank: ks_lwe.rank_out(),
});
module.glwe_keyswitch(&mut res_tmp, self, ks_glwe, scratch_1);
res.to_mut().from_glwe(
module,
&res_tmp,
T::bit_index(bit) << log_gap,
ks_lwe,
scratch_1,
);
} else {
res.to_mut()
.from_glwe(module, self, T::bit_index(bit) << log_gap, ks, scratch);
.from_glwe(module, self, T::bit_index(bit) << log_gap, ks_lwe, scratch);
}
}
pub fn get_bit_glwe<R, K, M, H, BE: Backend>(&self, module: &M, bit: usize, res: &mut R, keys: &H, scratch: &mut Scratch<BE>)

View File

@@ -333,7 +333,7 @@ where
K: BDDKeyHelper<DK, BRA, BE> + BDDKeyInfos,
{
let bit_end = bit_start + bit_count;
let (cbt, ks) = key.get_cbt_key();
let (cbt, ks_glwe, ks_lwe) = key.get_cbt_key();
assert!(bit_end <= T::BITS as usize);
@@ -363,7 +363,14 @@ where
let (mut tmp_ggsw, scratch_1) = scratch_thread.take_ggsw(ggsw_infos);
let (mut tmp_lwe, scratch_2) = scratch_1.take_lwe(bits);
for (local_bit, dst) in res_bits_chunk.iter_mut().enumerate() {
bits.get_bit_lwe(self, start + local_bit, &mut tmp_lwe, ks, scratch_2);
bits.get_bit_lwe(
self,
start + local_bit,
&mut tmp_lwe,
ks_glwe,
ks_lwe,
scratch_2,
);
cbt.execute_to_constant(self, &mut tmp_ggsw, &tmp_lwe, 1, 1, scratch_2);
dst.prepare(self, &tmp_ggsw, scratch_2);
}

View File

@@ -126,7 +126,14 @@ where
let (_, scratch_1) = scratch.take_ggsw(res);
let (mut tmp_lwe, scratch_2) = scratch_1.take_lwe(bits);
for (bit, dst) in res.bits.iter_mut().enumerate() {
bits.get_bit_lwe(self, bit, &mut tmp_lwe, &key.ks, scratch_2);
bits.get_bit_lwe(
self,
bit,
&mut tmp_lwe,
key.ks_glwe.as_ref(),
&key.ks_lwe,
scratch_2,
);
key.cbt
.execute_to_constant(self, dst, &tmp_lwe, 1, 1, scratch_2);
}

View File

@@ -9,7 +9,11 @@ use crate::tfhe::{
},
};
use poulpy_core::layouts::{GGLWEInfos, GLWEAutomorphismKeyHelper, GLWEAutomorphismKeyPrepared};
use poulpy_core::GLWESwitchingKeyEncryptSk;
use poulpy_core::layouts::{
GGLWEInfos, GLWEAutomorphismKeyHelper, GLWEAutomorphismKeyPrepared, GLWESecret, GLWESwitchingKey, GLWESwitchingKeyLayout,
GLWESwitchingKeyPrepared,
};
use poulpy_core::{
GLWEToLWESwitchingKeyEncryptSk, GetDistribution, ScratchTakeCore,
layouts::{
@@ -24,13 +28,15 @@ use poulpy_hal::{
pub trait BDDKeyInfos {
fn cbt_infos(&self) -> CircuitBootstrappingKeyLayout;
fn ks_infos(&self) -> GLWEToLWEKeyLayout;
fn ks_lwe_infos(&self) -> GLWEToLWEKeyLayout;
fn ks_glwe_infos(&self) -> Option<GLWESwitchingKeyLayout>;
}
#[derive(Debug, Clone, Copy)]
pub struct BDDKeyLayout {
pub cbt: CircuitBootstrappingKeyLayout,
pub ks: GLWEToLWEKeyLayout,
pub ks_glwe: Option<GLWESwitchingKeyLayout>,
pub ks_lwe: GLWEToLWEKeyLayout,
}
impl BDDKeyInfos for BDDKeyLayout {
@@ -38,8 +44,12 @@ impl BDDKeyInfos for BDDKeyLayout {
self.cbt
}
fn ks_infos(&self) -> GLWEToLWEKeyLayout {
self.ks
fn ks_glwe_infos(&self) -> Option<GLWESwitchingKeyLayout> {
self.ks_glwe
}
fn ks_lwe_infos(&self) -> GLWEToLWEKeyLayout {
self.ks_lwe
}
}
@@ -49,7 +59,8 @@ where
BRA: BlindRotationAlgo,
{
pub(crate) cbt: CircuitBootstrappingKey<D, BRA>,
pub(crate) ks: GLWEToLWEKey<D>,
pub(crate) ks_glwe: Option<GLWESwitchingKey<D>>,
pub(crate) ks_lwe: GLWEToLWEKey<D>,
}
impl<BRA: BlindRotationAlgo> BDDKey<Vec<u8>, BRA>
@@ -60,9 +71,16 @@ where
where
A: BDDKeyInfos,
{
let ks_glwe: Option<GLWESwitchingKey<Vec<u8>>> = if let Some(ks_infos) = &infos.ks_glwe_infos() {
Some(GLWESwitchingKey::alloc_from_infos(ks_infos))
} else {
None
};
Self {
cbt: CircuitBootstrappingKey::alloc_from_infos(&infos.cbt_infos()),
ks: GLWEToLWEKey::alloc_from_infos(&infos.ks_infos()),
ks_glwe: ks_glwe,
ks_lwe: GLWEToLWEKey::alloc_from_infos(&infos.ks_lwe_infos()),
}
}
}
@@ -88,7 +106,7 @@ pub trait BDDKeyEncryptSk<BRA: BlindRotationAlgo, BE: Backend> {
impl<BE: Backend, BRA: BlindRotationAlgo> BDDKeyEncryptSk<BRA, BE> for Module<BE>
where
Self: CircuitBootstrappingKeyEncryptSk<BRA, BE> + GLWEToLWESwitchingKeyEncryptSk<BE>,
Self: CircuitBootstrappingKeyEncryptSk<BRA, BE> + GLWEToLWESwitchingKeyEncryptSk<BE> + GLWESwitchingKeyEncryptSk<BE>,
Scratch<BE>: ScratchTakeCore<BE>,
{
fn bdd_key_encrypt_sk_tmp_bytes<A>(&self, infos: &A) -> usize
@@ -96,7 +114,7 @@ where
A: BDDKeyInfos,
{
self.circuit_bootstrapping_key_encrypt_sk_tmp_bytes(&infos.cbt_infos())
.max(self.glwe_to_lwe_key_encrypt_sk_tmp_bytes(&infos.ks_infos()))
.max(self.glwe_to_lwe_key_encrypt_sk_tmp_bytes(&infos.ks_lwe_infos()))
}
fn bdd_key_encrypt_sk<D, S0, S1>(
@@ -112,8 +130,17 @@ where
S0: LWESecretToRef + GetDistribution + LWEInfos,
S1: GLWESecretToRef + GetDistribution + GLWEInfos,
{
res.ks
if let Some(key) = &mut res.ks_glwe {
let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(sk_glwe.n(), key.rank_out());
sk_out.fill_ternary_prob(0.5, source_xe);
key.encrypt_sk(self, sk_glwe, &sk_out, source_xa, source_xe, scratch);
res.ks_lwe
.encrypt_sk(self, sk_lwe, &sk_out, source_xa, source_xe, scratch);
} else {
res.ks_lwe
.encrypt_sk(self, sk_lwe, sk_glwe, source_xa, source_xe, scratch);
}
res.cbt
.encrypt_sk(self, sk_lwe, sk_glwe, source_xa, source_xe, scratch);
}
@@ -145,7 +172,8 @@ where
BE: Backend,
{
pub(crate) cbt: CircuitBootstrappingKeyPrepared<D, BRA, BE>,
pub(crate) ks: GLWEToLWEKeyPrepared<D, BE>,
pub(crate) ks_glwe: Option<GLWESwitchingKeyPrepared<D, BE>>,
pub(crate) ks_lwe: GLWEToLWEKeyPrepared<D, BE>,
}
impl<D: DataRef, BRA: BlindRotationAlgo, BE: Backend> BDDKeyInfos for BDDKeyPrepared<D, BRA, BE> {
@@ -156,13 +184,28 @@ impl<D: DataRef, BRA: BlindRotationAlgo, BE: Backend> BDDKeyInfos for BDDKeyPrep
layout_tsk: self.cbt.tsk_infos(),
}
}
fn ks_infos(&self) -> GLWEToLWEKeyLayout {
fn ks_glwe_infos(&self) -> Option<GLWESwitchingKeyLayout> {
if let Some(ks_glwe) = &self.ks_glwe {
Some(GLWESwitchingKeyLayout {
n: ks_glwe.n(),
base2k: ks_glwe.base2k(),
k: ks_glwe.k(),
rank_in: ks_glwe.rank_in(),
rank_out: ks_glwe.rank_out(),
dnum: ks_glwe.dnum(),
dsize: ks_glwe.dsize(),
})
} else {
None
}
}
fn ks_lwe_infos(&self) -> GLWEToLWEKeyLayout {
GLWEToLWEKeyLayout {
n: self.ks.n(),
base2k: self.ks.base2k(),
k: self.ks.k(),
rank_in: self.ks.rank_in(),
dnum: self.ks.dnum(),
n: self.ks_lwe.n(),
base2k: self.ks_lwe.base2k(),
k: self.ks_lwe.k(),
rank_in: self.ks_lwe.rank_in(),
dnum: self.ks_lwe.dnum(),
}
}
}
@@ -187,9 +230,19 @@ where
where
A: BDDKeyInfos,
{
let ks_glwe = if let Some(ks_glwe_infos) = &infos.ks_glwe_infos() {
Some(GLWESwitchingKeyPrepared::alloc_from_infos(
self,
ks_glwe_infos,
))
} else {
None
};
BDDKeyPrepared {
cbt: CircuitBootstrappingKeyPrepared::alloc_from_infos(self, &infos.cbt_infos()),
ks: GLWEToLWEKeyPrepared::alloc_from_infos(self, &infos.ks_infos()),
ks_glwe,
ks_lwe: GLWEToLWEKeyPrepared::alloc_from_infos(self, &infos.ks_lwe_infos()),
}
}
@@ -198,7 +251,7 @@ where
A: BDDKeyInfos,
{
self.circuit_bootstrapping_key_prepare_tmp_bytes(&infos.cbt_infos())
.max(self.prepare_glwe_to_lwe_key_tmp_bytes(&infos.ks_infos()))
.max(self.prepare_glwe_to_lwe_key_tmp_bytes(&infos.ks_lwe_infos()))
}
fn prepare_bdd_key<DM, DR>(&self, res: &mut BDDKeyPrepared<DM, BRA, BE>, other: &BDDKey<DR, BRA>, scratch: &mut Scratch<BE>)
@@ -208,7 +261,16 @@ where
Scratch<BE>: ScratchTakeCore<BE>,
{
res.cbt.prepare(self, &other.cbt, scratch);
res.ks.prepare(self, &other.ks, scratch);
if let Some(key_prep) = &mut res.ks_glwe {
if let Some(other) = &other.ks_glwe {
key_prep.prepare(self, other, scratch);
} else {
panic!("incompatible keys: res has Some(ks_glwe) but other has none")
}
}
res.ks_lwe.prepare(self, &other.ks_lwe, scratch);
}
}
impl<BRA: BlindRotationAlgo, BE: Backend> BDDKeyPreparedFactory<BRA, BE> for Module<BE> where
@@ -231,9 +293,10 @@ impl<D: DataRef, BRA: BlindRotationAlgo, BE: Backend> BDDKeyHelper<D, BRA, BE> f
&self,
) -> (
&CircuitBootstrappingKeyPrepared<D, BRA, BE>,
Option<&GLWESwitchingKeyPrepared<D, BE>>,
&GLWEToLWEKeyPrepared<D, BE>,
) {
(&self.cbt, &self.ks)
(&self.cbt, self.ks_glwe.as_ref(), &self.ks_lwe)
}
}
@@ -242,6 +305,7 @@ pub trait BDDKeyHelper<D: DataRef, BRA: BlindRotationAlgo, BE: Backend> {
&self,
) -> (
&CircuitBootstrappingKeyPrepared<D, BRA, BE>,
Option<&GLWESwitchingKeyPrepared<D, BE>>,
&GLWEToLWEKeyPrepared<D, BE>,
);
}

View File

@@ -15,7 +15,7 @@ use rand::RngCore;
use crate::tfhe::{
bdd_arithmetic::{
FheUintPrepared, GGSWBlindRotation,
tests::test_suite::{TEST_BASE2K, TEST_RANK, TestContext},
tests::test_suite::{TEST_FHEUINT_BASE2K, TEST_RANK, TestContext},
},
blind_rotation::BlindRotationAlgo,
};
@@ -37,7 +37,7 @@ where
let module: &Module<BE> = &test_context.module;
let sk_glwe_prep: &GLWESecretPrepared<Vec<u8>, BE> = &test_context.sk_glwe;
let base2k: Base2K = TEST_BASE2K.into();
let base2k: Base2K = TEST_FHEUINT_BASE2K.into();
let rank: Rank = TEST_RANK.into();
let k_ggsw_res: TorusPrecision = TorusPrecision(39);
let k_ggsw_apply: TorusPrecision = TorusPrecision(52);

View File

@@ -15,7 +15,7 @@ use rand::RngCore;
use crate::tfhe::{
bdd_arithmetic::{
FheUintPrepared, GLWEBlindRotation,
tests::test_suite::{TEST_BASE2K, TEST_RANK, TestContext},
tests::test_suite::{TEST_FHEUINT_BASE2K, TEST_RANK, TestContext},
},
blind_rotation::BlindRotationAlgo,
};
@@ -35,7 +35,7 @@ where
let module: &Module<BE> = &test_context.module;
let sk_glwe_prep: &GLWESecretPrepared<Vec<u8>, BE> = &test_context.sk_glwe;
let base2k: Base2K = TEST_BASE2K.into();
let base2k: Base2K = TEST_FHEUINT_BASE2K.into();
let rank: Rank = TEST_RANK.into();
let k_glwe: TorusPrecision = TorusPrecision(26);
let k_ggsw: TorusPrecision = TorusPrecision(39);

View File

@@ -17,7 +17,7 @@ use rand::RngCore;
use crate::tfhe::{
bdd_arithmetic::{
FheUintPrepared, GLWEBlinSelection,
tests::test_suite::{TEST_BASE2K, TEST_RANK, TestContext},
tests::test_suite::{TEST_FHEUINT_BASE2K, TEST_RANK, TestContext},
},
blind_rotation::BlindRotationAlgo,
};
@@ -37,7 +37,7 @@ where
let module: &Module<BE> = &test_context.module;
let sk_glwe_prep: &GLWESecretPrepared<Vec<u8>, BE> = &test_context.sk_glwe;
let base2k: Base2K = TEST_BASE2K.into();
let base2k: Base2K = TEST_FHEUINT_BASE2K.into();
let rank: Rank = TEST_RANK.into();
let k_glwe: TorusPrecision = TorusPrecision(26);
let k_ggsw: TorusPrecision = TorusPrecision(39);

View File

@@ -41,7 +41,8 @@ use poulpy_core::{
ScratchTakeCore,
layouts::{
Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GLWEAutomorphismKeyLayout, GLWELayout, GLWESecret,
GLWESecretPrepared, GLWESecretPreparedFactory, GLWEToLWEKeyLayout, LWESecret, Rank, TorusPrecision,
GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout, LWESecret, Rank,
TorusPrecision,
},
};
@@ -137,7 +138,11 @@ impl<BRA: BlindRotationAlgo, BE: Backend> TestContext<BRA, BE> {
pub(crate) const TEST_N_GLWE: u32 = 256;
pub(crate) const TEST_N_LWE: u32 = 77;
pub(crate) const TEST_BASE2K: u32 = 13;
pub(crate) const TEST_FHEUINT_BASE2K: u32 = 13;
pub(crate) const TEST_BRK_BASE2K: u32 = 12;
pub(crate) const TEST_ATK_BASE2K: u32 = 11;
pub(crate) const TEST_TSK_BASE2K: u32 = 10;
pub(crate) const TEST_LWE_BASE2K: u32 = 4;
pub(crate) const TEST_K_GLWE: u32 = 26;
pub(crate) const TEST_K_GGSW: u32 = 39;
pub(crate) const TEST_BLOCK_SIZE: u32 = 7;
@@ -145,14 +150,14 @@ pub(crate) const TEST_RANK: u32 = 2;
pub(crate) static TEST_GLWE_INFOS: GLWELayout = GLWELayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_BASE2K),
base2k: Base2K(TEST_FHEUINT_BASE2K),
k: TorusPrecision(TEST_K_GLWE),
rank: Rank(TEST_RANK),
};
pub(crate) static TEST_GGSW_INFOS: GGSWLayout = GGSWLayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_BASE2K),
base2k: Base2K(TEST_FHEUINT_BASE2K),
k: TorusPrecision(TEST_K_GGSW),
rank: Rank(TEST_RANK),
dnum: Dnum(2),
@@ -164,33 +169,42 @@ pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout {
layout_brk: BlindRotationKeyLayout {
n_glwe: Degree(TEST_N_GLWE),
n_lwe: Degree(TEST_N_LWE),
base2k: Base2K(TEST_BASE2K),
base2k: Base2K(TEST_BRK_BASE2K),
k: TorusPrecision(52),
dnum: Dnum(3),
dnum: Dnum(4),
rank: Rank(TEST_RANK),
},
layout_atk: GLWEAutomorphismKeyLayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_BASE2K),
base2k: Base2K(TEST_ATK_BASE2K),
k: TorusPrecision(52),
rank: Rank(TEST_RANK),
dnum: Dnum(3),
dnum: Dnum(4),
dsize: Dsize(1),
},
layout_tsk: GGLWEToGGSWKeyLayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_BASE2K),
base2k: Base2K(TEST_TSK_BASE2K),
k: TorusPrecision(52),
rank: Rank(TEST_RANK),
dnum: Dnum(3),
dnum: Dnum(4),
dsize: Dsize(1),
},
},
ks: GLWEToLWEKeyLayout {
ks_glwe: Some(GLWESwitchingKeyLayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_BASE2K),
k: TorusPrecision(39),
base2k: Base2K(TEST_LWE_BASE2K),
k: TorusPrecision(20),
rank_in: Rank(TEST_RANK),
dnum: Dnum(2),
rank_out: Rank(1),
dnum: Dnum(3),
dsize: Dsize(1),
}),
ks_lwe: GLWEToLWEKeyLayout {
n: Degree(TEST_N_GLWE),
base2k: Base2K(TEST_LWE_BASE2K),
k: TorusPrecision(16),
rank_in: Rank(1),
dnum: Dnum(3),
},
};

View File

@@ -13,7 +13,7 @@ use crate::tfhe::{
bdd_arithmetic::{
BDDKeyEncryptSk, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint, FheUintPrepare,
FheUintPrepareDebug, FheUintPreparedDebug, FheUintPreparedEncryptSk, FheUintPreparedFactory,
tests::test_suite::{TEST_BASE2K, TEST_GGSW_INFOS, TEST_GLWE_INFOS, TestContext},
tests::test_suite::{TEST_GGSW_INFOS, TEST_GLWE_INFOS, TestContext},
},
blind_rotation::{BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory},
};
@@ -73,7 +73,7 @@ where
c_enc_prep_debug.prepare(module, &c_enc, bdd_key_prepared, scratch_2.borrow());
let max_noise = |col_i: usize| {
let mut noise: f64 = -(ggsw_infos.size() as f64 * TEST_BASE2K as f64) + SIGMA.log2() + 2.0;
let mut noise: f64 = -(ggsw_infos.size() as f64 * ggsw_infos.base2k().as_usize() as f64) + SIGMA.log2() + 2.0;
noise += 0.5 * ggsw_infos.log_n() as f64;
if col_i != 0 {
noise += 0.5 * ggsw_infos.log_n() as f64
@@ -87,7 +87,10 @@ where
for (i, stat) in stats.iter().enumerate() {
let noise_have: f64 = stat.std().log2();
let noise_max: f64 = max_noise(col);
assert!(noise_have <= noise_max, "bit: {i} noise_have: {noise_have} > noise_max: {noise_max}")
assert!(
noise_have <= noise_max,
"bit: {i} noise_have: {noise_have} > noise_max: {noise_max}"
)
}
}
}