Support for bivariate convolution & normalization with offset (#126)

* Add bivariate-convolution
* Add pair-wise convolution + tests + benches
* Add take_cnv_pvec_[left/right] to Scratch & updated CHANGELOG.md
* cross-base2k normalization with positive offset
* clippy & fix CI doctest avx compile error
* more streamlined bounds derivation for normalization
* Working cross-base2k normalization with pos/neg offset
* Update normalization API & tests
* Add glwe tensoring test
* Add relinearization + preliminary test
* Fix GGLWEToGGSW key infos
* Add (X,Y) convolution by const (1, Y) poly
* Faster normalization test + add bench for cnv_by_const
* Update changelog
This commit is contained in:
Jean-Philippe Bossuat
2025-12-21 16:56:42 +01:00
committed by GitHub
parent 76424d0ab5
commit 4e90e08a71
219 changed files with 6571 additions and 5041 deletions

View File

@@ -0,0 +1,237 @@
use std::marker::PhantomData;
use crate::{
alloc_aligned,
layouts::{Backend, Data, DataMut, DataRef, DataView, DataViewMut, ZnxInfos, ZnxView},
oep::CnvPVecBytesOfImpl,
};
pub struct CnvPVecR<D: Data, BE: Backend> {
data: D,
n: usize,
size: usize,
cols: usize,
_phantom: PhantomData<BE>,
}
impl<D: Data, BE: Backend> ZnxInfos for CnvPVecR<D, BE> {
fn cols(&self) -> usize {
self.cols
}
fn n(&self) -> usize {
self.n
}
fn rows(&self) -> usize {
1
}
fn size(&self) -> usize {
self.size
}
}
impl<D: Data, BE: Backend> DataView for CnvPVecR<D, BE> {
type D = D;
fn data(&self) -> &Self::D {
&self.data
}
}
impl<D: Data, B: Backend> DataViewMut for CnvPVecR<D, B> {
fn data_mut(&mut self) -> &mut Self::D {
&mut self.data
}
}
impl<D: DataRef, BE: Backend> ZnxView for CnvPVecR<D, BE> {
type Scalar = BE::ScalarPrep;
}
impl<D: DataRef + From<Vec<u8>>, B: Backend> CnvPVecR<D, B>
where
B: CnvPVecBytesOfImpl,
{
pub fn alloc(n: usize, cols: usize, size: usize) -> Self {
let data: Vec<u8> = alloc_aligned::<u8>(B::bytes_of_cnv_pvec_right_impl(n, cols, size));
Self {
data: data.into(),
n,
size,
cols,
_phantom: PhantomData,
}
}
pub fn from_bytes(n: usize, cols: usize, size: usize, bytes: impl Into<Vec<u8>>) -> Self {
let data: Vec<u8> = bytes.into();
assert!(data.len() == B::bytes_of_cnv_pvec_right_impl(n, cols, size));
Self {
data: data.into(),
n,
size,
cols,
_phantom: PhantomData,
}
}
}
impl<D: Data, B: Backend> CnvPVecR<D, B> {
pub fn from_data(data: D, n: usize, cols: usize, size: usize) -> Self {
Self {
data,
n,
cols,
size,
_phantom: PhantomData,
}
}
}
pub struct CnvPVecL<D: Data, BE: Backend> {
data: D,
n: usize,
size: usize,
cols: usize,
_phantom: PhantomData<BE>,
}
impl<D: Data, BE: Backend> ZnxInfos for CnvPVecL<D, BE> {
fn cols(&self) -> usize {
self.cols
}
fn n(&self) -> usize {
self.n
}
fn rows(&self) -> usize {
1
}
fn size(&self) -> usize {
self.size
}
}
impl<D: Data, BE: Backend> DataView for CnvPVecL<D, BE> {
type D = D;
fn data(&self) -> &Self::D {
&self.data
}
}
impl<D: Data, B: Backend> DataViewMut for CnvPVecL<D, B> {
fn data_mut(&mut self) -> &mut Self::D {
&mut self.data
}
}
impl<D: DataRef, BE: Backend> ZnxView for CnvPVecL<D, BE> {
type Scalar = BE::ScalarPrep;
}
impl<D: DataRef + From<Vec<u8>>, B: Backend> CnvPVecL<D, B>
where
B: CnvPVecBytesOfImpl,
{
pub fn alloc(n: usize, cols: usize, size: usize) -> Self {
let data: Vec<u8> = alloc_aligned::<u8>(B::bytes_of_cnv_pvec_left_impl(n, cols, size));
Self {
data: data.into(),
n,
size,
cols,
_phantom: PhantomData,
}
}
pub fn from_bytes(n: usize, cols: usize, size: usize, bytes: impl Into<Vec<u8>>) -> Self {
let data: Vec<u8> = bytes.into();
assert!(data.len() == B::bytes_of_cnv_pvec_left_impl(n, cols, size));
Self {
data: data.into(),
n,
size,
cols,
_phantom: PhantomData,
}
}
}
impl<D: Data, B: Backend> CnvPVecL<D, B> {
pub fn from_data(data: D, n: usize, cols: usize, size: usize) -> Self {
Self {
data,
n,
cols,
size,
_phantom: PhantomData,
}
}
}
pub trait CnvPVecRToRef<BE: Backend> {
fn to_ref(&self) -> CnvPVecR<&[u8], BE>;
}
impl<D: DataRef, BE: Backend> CnvPVecRToRef<BE> for CnvPVecR<D, BE> {
fn to_ref(&self) -> CnvPVecR<&[u8], BE> {
CnvPVecR {
data: self.data.as_ref(),
n: self.n,
size: self.size,
cols: self.cols,
_phantom: self._phantom,
}
}
}
pub trait CnvPVecRToMut<BE: Backend> {
fn to_mut(&mut self) -> CnvPVecR<&mut [u8], BE>;
}
impl<D: DataMut, BE: Backend> CnvPVecRToMut<BE> for CnvPVecR<D, BE> {
fn to_mut(&mut self) -> CnvPVecR<&mut [u8], BE> {
CnvPVecR {
data: self.data.as_mut(),
n: self.n,
size: self.size,
cols: self.cols,
_phantom: self._phantom,
}
}
}
pub trait CnvPVecLToRef<BE: Backend> {
fn to_ref(&self) -> CnvPVecL<&[u8], BE>;
}
impl<D: DataRef, BE: Backend> CnvPVecLToRef<BE> for CnvPVecL<D, BE> {
fn to_ref(&self) -> CnvPVecL<&[u8], BE> {
CnvPVecL {
data: self.data.as_ref(),
n: self.n,
size: self.size,
cols: self.cols,
_phantom: self._phantom,
}
}
}
pub trait CnvPVecLToMut<BE: Backend> {
fn to_mut(&mut self) -> CnvPVecL<&mut [u8], BE>;
}
impl<D: DataMut, BE: Backend> CnvPVecLToMut<BE> for CnvPVecL<D, BE> {
fn to_mut(&mut self) -> CnvPVecL<&mut [u8], BE> {
CnvPVecL {
data: self.data.as_mut(),
n: self.n,
size: self.size,
cols: self.cols,
_phantom: self._phantom,
}
}
}

View File

@@ -223,22 +223,22 @@ impl<D: DataRef> VecZnx<D> {
let a: VecZnx<&[u8]> = self.to_ref();
let size: usize = a.size();
let prec: u32 = (base2k * size) as u32;
let prec: u32 = data[0].prec();
// 2^{base2k}
let base: Float = Float::with_val(prec, (1u64 << base2k) as f64);
let scale: Float = Float::with_val(prec, Float::u_pow_u(2, base2k as u32));
// y[i] = sum x[j][i] * 2^{-base2k*j}
(0..size).for_each(|i| {
if i == 0 {
izip!(a.at(col, size - i - 1).iter(), data.iter_mut()).for_each(|(x, y)| {
y.assign(*x);
*y /= &base;
*y /= &scale;
});
} else {
izip!(a.at(col, size - i - 1).iter(), data.iter_mut()).for_each(|(x, y)| {
*y += Float::with_val(prec, *x);
*y /= &base;
*y /= &scale;
});
}
});

View File

@@ -1,3 +1,4 @@
mod convolution;
mod encoding;
mod mat_znx;
mod module;
@@ -12,6 +13,7 @@ mod vec_znx_dft;
mod vmp_pmat;
mod znx_base;
pub use convolution::*;
pub use mat_znx::*;
pub use module::*;
pub use scalar_znx::*;

View File

@@ -123,10 +123,8 @@ where
panic!("cannot invert 0")
}
let g_exp: u64 = mod_exp_u64(
gal_el.unsigned_abs(),
(self.cyclotomic_order() - 1) as usize,
) & (self.cyclotomic_order() - 1) as u64;
let g_exp: u64 =
mod_exp_u64(gal_el.unsigned_abs(), (self.cyclotomic_order() - 1) as usize) & (self.cyclotomic_order() - 1) as u64;
g_exp as i64 * gal_el.signum()
}
}

View File

@@ -187,11 +187,7 @@ impl<D: Data> VecZnx<D> {
impl<D: DataRef> fmt::Display for VecZnx<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"VecZnx(n={}, cols={}, size={})",
self.n, self.cols, self.size
)?;
writeln!(f, "VecZnx(n={}, cols={}, size={})", self.n, self.cols, self.size)?;
for col in 0..self.cols {
writeln!(f, "Column {col}:")?;

View File

@@ -93,7 +93,7 @@ where
impl<D: DataRef + From<Vec<u8>>, B: Backend> VecZnxBig<D, B>
where
B: VecZnxBigAllocBytesImpl<B>,
B: VecZnxBigAllocBytesImpl,
{
pub fn alloc(n: usize, cols: usize, size: usize) -> Self {
let data = alloc_aligned::<u8>(B::vec_znx_big_bytes_of_impl(n, cols, size));
@@ -172,11 +172,7 @@ impl<D: DataMut, B: Backend> VecZnxBigToMut<B> for VecZnxBig<D, B> {
impl<D: DataRef, B: Backend> fmt::Display for VecZnxBig<D, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"VecZnxBig(n={}, cols={}, size={})",
self.n, self.cols, self.size
)?;
writeln!(f, "VecZnxBig(n={}, cols={}, size={})", self.n, self.cols, self.size)?;
for col in 0..self.cols {
writeln!(f, "Column {col}:")?;

View File

@@ -192,11 +192,7 @@ impl<D: DataMut, B: Backend> VecZnxDftToMut<B> for VecZnxDft<D, B> {
impl<D: DataRef, B: Backend> fmt::Display for VecZnxDft<D, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"VecZnxDft(n={}, cols={}, size={})",
self.n, self.cols, self.size
)?;
writeln!(f, "VecZnxDft(n={}, cols={}, size={})", self.n, self.cols, self.size)?;
for col in 0..self.cols {
writeln!(f, "Column {col}:")?;

View File

@@ -65,11 +65,8 @@ pub trait ZnxView: ZnxInfos + DataView<D: DataRef> {
/// Returns a non-mutable pointer starting at the j-th small polynomial of the i-th column.
fn at_ptr(&self, i: usize, j: usize) -> *const Self::Scalar {
#[cfg(debug_assertions)]
{
assert!(i < self.cols(), "cols: {} >= {}", i, self.cols());
assert!(j < self.size(), "size: {} >= {}", j, self.size());
}
assert!(i < self.cols(), "cols: {} >= self.cols(): {}", i, self.cols());
assert!(j < self.size(), "size: {} >= self.size(): {}", j, self.size());
let offset: usize = self.n() * (j * self.cols() + i);
unsafe { self.as_ptr().add(offset) }
}
@@ -93,11 +90,8 @@ pub trait ZnxViewMut: ZnxView + DataViewMut<D: DataMut> {
/// Returns a mutable pointer starting at the j-th small polynomial of the i-th column.
fn at_mut_ptr(&mut self, i: usize, j: usize) -> *mut Self::Scalar {
#[cfg(debug_assertions)]
{
assert!(i < self.cols(), "cols: {} >= {}", i, self.cols());
assert!(j < self.size(), "size: {} >= {}", j, self.size());
}
assert!(i < self.cols(), "cols: {} >= self.cols(): {}", i, self.cols());
assert!(j < self.size(), "size: {} >= self.size(): {}", j, self.size());
let offset: usize = self.n() * (j * self.cols() + i);
unsafe { self.as_mut_ptr().add(offset) }
}