use poulpy_hal::{
api::{
CnvPVecBytesOf, Convolution, ModuleN, ScratchArenaTakeBasic, VecZnxBigAddAssign, VecZnxBigAddSmallAssign, VecZnxBigAlloc,
VecZnxBigAutomorphismAssign, VecZnxBigAutomorphismAssignTmpBytes, VecZnxBigBytesOf, VecZnxBigFromSmallBackend,
VecZnxBigNormalize, VecZnxCopyBackend, VecZnxDftAddAssign, VecZnxDftApply, VecZnxDftAutomorphism, VecZnxDftBytesOf,
VecZnxDftCopy, VecZnxDftZero, VecZnxIdftApply, VecZnxIdftApplyTmpA, VecZnxIdftApplyTmpBytes,
VecZnxNormalizeAssignBackend, VecZnxNormalizeTmpBytes,
},
layouts::{
Backend, GaloisElement, ScratchArena, VecZnxBigToBackendMut, VecZnxBigToBackendRef, VecZnxDftBackendMut,
VecZnxDftToBackendMut, VecZnxDftToBackendRef,
},
};
use crate::{
GLWEAdd, GLWEAutomorphism, GLWECopy, GLWEMulPlain, LinearTransformation, LinearTransformationGiantStep,
default::{
keyswitching::{GGLWEProductDefault, GLWEKeyswitchInternal},
linear_transformation::{
inner_product::{glwe_accumulate_prepared_baby_steps_dft, glwe_accumulate_unprepared_baby_steps_dft},
lazy::{
glwe_dft_add_dft_assign, glwe_dft_copy_dft, glwe_idft_dft_into_big, glwe_lazy_giant_automorphism_from_dft,
glwe_normalize_big_into,
},
},
operations::cnv_offset_to_limb_offset,
},
layouts::{
GLWE, GLWEInfos, GLWEToBackendMut, GLWEToBackendRef, GetAutomorphismKey, LWEInfos, ModuleCoreAlloc,
prepared::{GLWEAutomorphismKeyPreparedBackendRef, PreparedDiagonal},
},
};
use super::LinearTransformationBabySteps;
pub trait DiagonalProd<BE: Backend>: LWEInfos + Sized {
fn accumulate_giant_prod<M>(
module: &M,
cnv_offset_hi: usize,
prod_dft: &mut VecZnxDftBackendMut<'_, BE>,
lhs: &LinearTransformationBabySteps<BE>,
gs: &LinearTransformationGiantStep<Self>,
scratch: &mut ScratchArena<'_, BE>,
) where
M: CnvPVecBytesOf + Convolution<BE> + ModuleN;
}
impl<BE: Backend> DiagonalProd<BE> for PreparedDiagonal<BE::OwnedBuf, BE> {
fn accumulate_giant_prod<M>(
module: &M,
cnv_offset_hi: usize,
prod_dft: &mut VecZnxDftBackendMut<'_, BE>,
lhs: &LinearTransformationBabySteps<BE>,
gs: &LinearTransformationGiantStep<Self>,
scratch: &mut ScratchArena<'_, BE>,
) where
M: CnvPVecBytesOf + Convolution<BE> + ModuleN,
{
glwe_accumulate_prepared_baby_steps_dft(module, cnv_offset_hi, prod_dft, lhs, gs, scratch);
}
}
pub fn glwe_accumulate_streamed_baby_steps_dft<BE, M, P>(
module: &M,
cnv_offset_hi: usize,
prod_dft: &mut VecZnxDftBackendMut<'_, BE>,
lhs: &LinearTransformationBabySteps<BE>,
gs: &LinearTransformationGiantStep<P>,
scratch: &mut ScratchArena<'_, BE>,
) where
BE: Backend,
M: CnvPVecBytesOf + Convolution<BE> + ModuleN,
P: GLWEToBackendRef<BE> + crate::layouts::IntPolyInfos + GLWEInfos,
{
glwe_accumulate_unprepared_baby_steps_dft(module, cnv_offset_hi, prod_dft, lhs, gs, scratch);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn glwe_eval_giant_steps<BE, M, R, P, H>(
module: &M,
cnv_offset: usize,
res: &mut R,
lhs: &LinearTransformationBabySteps<BE>,
rhs: &LinearTransformation<P>,
keys: &H,
scratch: &mut ScratchArena<'_, BE>,
) where
BE: Backend,
M: GLWEAutomorphism<BE>
+ GaloisElement
+ GLWEAdd<BE>
+ GLWECopy<BE>
+ ModuleCoreAlloc<OwnedBuf = BE::OwnedBuf, ZnxWord = BE::ZnxWord>
+ CnvPVecBytesOf
+ Convolution<BE>
+ ModuleN
+ GGLWEProductDefault<BE>
+ GLWEKeyswitchInternal<BE>
+ VecZnxBigAddAssign<BE>
+ VecZnxBigAddSmallAssign<BE>
+ VecZnxBigAlloc<BE>
+ VecZnxBigAutomorphismAssign<BE>
+ VecZnxBigAutomorphismAssignTmpBytes
+ VecZnxBigBytesOf
+ VecZnxBigFromSmallBackend<BE>
+ VecZnxBigNormalize<BE>
+ VecZnxCopyBackend<BE>
+ VecZnxDftAddAssign<BE>
+ VecZnxDftApply<BE>
+ VecZnxDftAutomorphism<BE>
+ VecZnxDftBytesOf
+ VecZnxDftCopy<BE>
+ VecZnxDftZero<BE>
+ VecZnxIdftApply<BE>
+ VecZnxIdftApplyTmpA<BE>
+ VecZnxIdftApplyTmpBytes
+ VecZnxNormalizeAssignBackend<BE>
+ VecZnxNormalizeTmpBytes
+ GLWEMulPlain<BE>,
R: GLWEToBackendMut<BE> + GLWEInfos,
P: DiagonalProd<BE>,
H: GetAutomorphismKey<BE>,
{
let cols = res.rank().as_usize() + 1;
let res_base2k = res.base2k();
let res_k = res.k().as_usize();
let first_diagonal = rhs
.first_diagonal_plaintext()
.expect("linear transformation has no diagonals");
let prod_base2k = first_diagonal.base2k();
let baby_size = lhs.size();
let diagonal_size = first_diagonal.size();
let (cnv_offset_hi, cnv_offset_lo) = cnv_offset_to_limb_offset(cnv_offset, prod_base2k.as_usize());
let prod_size = baby_size + diagonal_size - cnv_offset_hi;
let nonzero_giant_rotations = rhs.giant_steps.iter().filter(|gs| gs.rot != 0).count();
let has_nonzero_giant_rotation = nonzero_giant_rotations != 0;
let giant_keys: Vec<Option<GLWEAutomorphismKeyPreparedBackendRef<'_, BE>>> = rhs
.giant_steps
.iter()
.map(|gs| {
(gs.rot != 0).then(|| {
keys.get_automorphism_key(module.galois_element(gs.rot), res.k())
.unwrap_or_else(|e| panic!("giant-step rotation {}: {e}", gs.rot))
})
})
.collect();
let use_lazy_giant_rotation = giant_keys
.iter()
.flatten()
.all(|key| res_base2k == key.base2k() && prod_base2k == key.base2k());
let key_output_size = giant_keys
.iter()
.flatten()
.map(|key| {
crate::default::keyswitching::gglwe_product_accumulation_output_size_with_tail(
res,
res,
key,
nonzero_giant_rotations,
prod_size.saturating_sub(res.size()),
)
})
.max()
.unwrap_or(res.size());
let use_final_lazy_accumulator = !has_nonzero_giant_rotation || use_lazy_giant_rotation;
let lazy_size = if use_lazy_giant_rotation {
key_output_size.max(prod_size)
} else {
res.size().max(prod_size)
};
let scratch = scratch.borrow();
if use_final_lazy_accumulator {
let (mut prod_dft, scratch_phase) = scratch.take_vec_znx_dft_scratch(module, cols, prod_size);
let (mut lazy_acc_dft, mut scratch_phase) = scratch_phase.take_vec_znx_dft_scratch(module, cols, lazy_size);
for col in 0..cols {
module.vec_znx_dft_zero(&mut lazy_acc_dft, col);
}
let mut res_initialized = false;
for (g, giant_key) in giant_keys.iter().enumerate() {
{
let mut prod_dft_backend = prod_dft.to_backend_mut();
P::accumulate_giant_prod(
module,
cnv_offset_hi,
&mut prod_dft_backend,
lhs,
&rhs.giant_steps[g],
&mut scratch_phase,
);
}
let rot = rhs.giant_steps[g].rot;
if rot == 0 {
let prod_dft_ref = prod_dft.to_backend_ref();
let mut lazy_acc_dft_backend = lazy_acc_dft.to_backend_mut();
if res_initialized {
glwe_dft_add_dft_assign(module, &mut lazy_acc_dft_backend, &prod_dft_ref);
} else {
glwe_dft_copy_dft(module, &mut lazy_acc_dft_backend, &prod_dft_ref);
}
} else {
let key = giant_key.as_ref().unwrap();
{
let mut lazy_acc_dft_backend = lazy_acc_dft.to_backend_mut();
let prod_dft_ref = prod_dft.to_backend_ref();
glwe_lazy_giant_automorphism_from_dft(
module,
&mut lazy_acc_dft_backend,
&prod_dft_ref,
prod_base2k.as_usize(),
module.galois_element(rot),
&key.key,
key_output_size,
nonzero_giant_rotations,
res_initialized,
&mut scratch_phase,
);
}
}
res_initialized = true;
}
assert!(res_initialized, "linear transformation has no giant steps");
let (mut lazy_acc_big, mut scratch_phase) = scratch_phase.take_vec_znx_big_scratch(module, cols, lazy_size);
{
let mut lazy_acc_dft_backend = lazy_acc_dft.to_backend_mut();
let mut lazy_acc_big_backend = lazy_acc_big.to_backend_mut();
glwe_idft_dft_into_big(module, &mut lazy_acc_big_backend, &mut lazy_acc_dft_backend);
}
let lazy_acc_ref = lazy_acc_big.to_backend_ref();
glwe_normalize_big_into(
module,
res,
&lazy_acc_ref,
prod_base2k.as_usize(),
cnv_offset_lo,
&mut scratch_phase,
);
return;
}
let (mut prod_dft, scratch_phase) = scratch.take_vec_znx_dft_scratch(module, cols, prod_size);
let (mut prod_col_big, mut scratch_phase) = scratch_phase.take_vec_znx_big_scratch(module, 1, prod_size);
let mut fallback_acc: GLWE<BE::OwnedBuf, BE::ZnxWord> = module.glwe_alloc_from_infos(res);
let mut res_initialized = false;
for (g, giant_key) in giant_keys.iter().enumerate() {
{
let mut prod_dft_backend = prod_dft.to_backend_mut();
P::accumulate_giant_prod(
module,
cnv_offset_hi,
&mut prod_dft_backend,
lhs,
&rhs.giant_steps[g],
&mut scratch_phase,
);
let mut acc_backend = <GLWE<BE::OwnedBuf, BE::ZnxWord> as GLWEToBackendMut<BE>>::to_backend_mut(&mut fallback_acc);
for col in 0..cols {
module.vec_znx_idft_apply_tmpa(&mut prod_col_big, 0, &mut prod_dft_backend, col);
let prod_col_big_ref = prod_col_big.to_backend_ref();
module.vec_znx_big_normalize(
&mut acc_backend.data,
res_base2k.as_usize(),
res_k,
cnv_offset_lo,
col,
&prod_col_big_ref,
prod_base2k.as_usize(),
0,
&mut scratch_phase.borrow(),
);
}
}
if let Some(key) = giant_key.as_ref() {
module.glwe_automorphism_assign(&mut fallback_acc, key, &mut scratch_phase);
}
if res_initialized {
module.glwe_add_assign(res, &fallback_acc);
} else {
module.glwe_copy(res, &fallback_acc);
res_initialized = true;
}
}
let mut res_backend = res.to_backend_mut();
for col in 0..cols {
module.vec_znx_normalize_assign_backend(
res_base2k.as_usize(),
res_k,
&mut res_backend.data,
col,
&mut scratch_phase.borrow(),
);
}
}