poulpy/backend/src/vec_znx.rs
backend/src/vec_znx.rsRaw
1use itertools::izip;
2
3use crate::DataView;
4use crate::DataViewMut;
5use crate::ScalarZnx;
6use crate::Scratch;
7use crate::ZnxSliceSize;
8use crate::ZnxZero;
9use crate::alloc_aligned;
10use crate::assert_alignement;
11use crate::cast_mut;
12use crate::ffi::znx;
13use crate::znx_base::{ZnxInfos, ZnxView, ZnxViewMut};
14use std::{cmp::min, fmt};
15
16/// [VecZnx] represents collection of contiguously stacked vector of small norm polynomials of
17/// Zn\[X\] with [i64] coefficients.
18/// A [VecZnx] is composed of multiple Zn\[X\] polynomials stored in a single contiguous array
19/// in the memory.
20///
21/// # Example
22///
23/// Given 3 polynomials (a, b, c) of Zn\[X\], each with 4 columns, then the memory
24/// layout is: `[a0, b0, c0, a1, b1, c1, a2, b2, c2, a3, b3, c3]`, where ai, bi, ci
25/// are small polynomials of Zn\[X\].
26#[derive(PartialEq, Eq)]
27pub struct VecZnx<D> {
28 pub data: D,
29 pub n: usize,
30 pub cols: usize,
31 pub size: usize,
32}
33
34impl<D> fmt::Debug for VecZnx<D>
35where
36 D: AsRef<[u8]>,
37{
38 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39 write!(f, "{}", self)
40 }
41}
42
43impl<D> ZnxInfos for VecZnx<D> {
44 fn cols(&self) -> usize {
45 self.cols
46 }
47
48 fn rows(&self) -> usize {
49 1
50 }
51
52 fn n(&self) -> usize {
53 self.n
54 }
55
56 fn size(&self) -> usize {
57 self.size
58 }
59}
60
61impl<D> ZnxSliceSize for VecZnx<D> {
62 fn sl(&self) -> usize {
63 self.n() * self.cols()
64 }
65}
66
67impl<D> DataView for VecZnx<D> {
68 type D = D;
69 fn data(&self) -> &Self::D {
70 &self.data
71 }
72}
73
74impl<D> DataViewMut for VecZnx<D> {
75 fn data_mut(&mut self) -> &mut Self::D {
76 &mut self.data
77 }
78}
79
80impl<D: AsRef<[u8]>> ZnxView for VecZnx<D> {
81 type Scalar = i64;
82}
83
84impl VecZnx<Vec<u8>> {
85 pub fn rsh_scratch_space(n: usize) -> usize {
86 n * std::mem::size_of::<i64>()
87 }
88}
89
90impl<D: AsMut<[u8]> + AsRef<[u8]>> VecZnx<D> {
91 /// Truncates the precision of the [VecZnx] by k bits.
92 ///
93 /// # Arguments
94 ///
95 /// * `basek`: the base two logarithm of the coefficients decomposition.
96 /// * `k`: the number of bits of precision to drop.
97 pub fn trunc_pow2(&mut self, basek: usize, k: usize, col: usize) {
98 if k == 0 {
99 return;
100 }
101
102 self.size -= k / basek;
103
104 let k_rem: usize = k % basek;
105
106 if k_rem != 0 {
107 let mask: i64 = ((1 << (basek - k_rem - 1)) - 1) << k_rem;
108 self.at_mut(col, self.size() - 1)
109 .iter_mut()
110 .for_each(|x: &mut i64| *x &= mask)
111 }
112 }
113
114 pub fn rotate(&mut self, k: i64) {
115 unsafe {
116 (0..self.cols()).for_each(|i| {
117 (0..self.size()).for_each(|j| {
118 znx::znx_rotate_inplace_i64(self.n() as u64, k, self.at_mut_ptr(i, j));
119 });
120 })
121 }
122 }
123
124 pub fn rsh(&mut self, basek: usize, k: usize, scratch: &mut Scratch) {
125 let n: usize = self.n();
126 let cols: usize = self.cols();
127 let size: usize = self.size();
128 let steps: usize = k / basek;
129
130 self.raw_mut().rotate_right(n * steps * cols);
131 (0..cols).for_each(|i| {
132 (0..steps).for_each(|j| {
133 self.zero_at(i, j);
134 })
135 });
136
137 let k_rem: usize = k % basek;
138
139 if k_rem != 0 {
140 let (carry, _) = scratch.tmp_slice::<i64>(n);
141 let shift = i64::BITS as usize - k_rem;
142 (0..cols).for_each(|i| {
143 carry.fill(0);
144 (steps..size).for_each(|j| {
145 izip!(carry.iter_mut(), self.at_mut(i, j).iter_mut()).for_each(|(ci, xi)| {
146 *xi += *ci << basek;
147 *ci = (*xi << shift) >> shift;
148 *xi = (*xi - *ci) >> k_rem;
149 });
150 });
151 })
152 }
153 }
154
155 pub fn lsh(&mut self, basek: usize, k: usize, scratch: &mut Scratch) {
156 let n: usize = self.n();
157 let cols: usize = self.cols();
158 let size: usize = self.size();
159 let steps: usize = k / basek;
160
161 self.raw_mut().rotate_left(n * steps * cols);
162 (0..cols).for_each(|i| {
163 (size - steps..size).for_each(|j| {
164 self.zero_at(i, j);
165 })
166 });
167
168 let k_rem: usize = k % basek;
169
170 if k_rem != 0 {
171 let shift: usize = i64::BITS as usize - k_rem;
172 let (tmp_bytes, _) = scratch.tmp_slice::<u8>(n * size_of::<i64>());
173 (0..cols).for_each(|i| {
174 (0..steps).for_each(|j| {
175 self.at_mut(i, j).iter_mut().for_each(|xi| {
176 *xi <<= shift;
177 });
178 });
179 normalize(basek, self, i, tmp_bytes);
180 });
181 }
182 }
183}
184
185impl<D: From<Vec<u8>>> VecZnx<D> {
186 pub(crate) fn bytes_of<Scalar: Sized>(n: usize, cols: usize, size: usize) -> usize {
187 n * cols * size * size_of::<Scalar>()
188 }
189
190 pub fn new<Scalar: Sized>(n: usize, cols: usize, size: usize) -> Self {
191 let data = alloc_aligned::<u8>(Self::bytes_of::<Scalar>(n, cols, size));
192 Self {
193 data: data.into(),
194 n,
195 cols,
196 size,
197 }
198 }
199
200 pub(crate) fn new_from_bytes<Scalar: Sized>(n: usize, cols: usize, size: usize, bytes: impl Into<Vec<u8>>) -> Self {
201 let data: Vec<u8> = bytes.into();
202 assert!(data.len() == Self::bytes_of::<Scalar>(n, cols, size));
203 Self {
204 data: data.into(),
205 n,
206 cols,
207 size,
208 }
209 }
210}
211
212impl<D> VecZnx<D> {
213 pub(crate) fn from_data(data: D, n: usize, cols: usize, size: usize) -> Self {
214 Self {
215 data,
216 n,
217 cols,
218 size,
219 }
220 }
221
222 pub fn to_scalar_znx(self) -> ScalarZnx<D> {
223 debug_assert_eq!(
224 self.size, 1,
225 "cannot convert VecZnx to ScalarZnx if cols: {} != 1",
226 self.cols
227 );
228 ScalarZnx {
229 data: self.data,
230 n: self.n,
231 cols: self.cols,
232 }
233 }
234}
235
236/// Copies the coefficients of `a` on the receiver.
237/// Copy is done with the minimum size matching both backing arrays.
238/// Panics if the cols do not match.
239pub fn copy_vec_znx_from<DataMut, Data>(b: &mut VecZnx<DataMut>, a: &VecZnx<Data>)
240where
241 DataMut: AsMut<[u8]> + AsRef<[u8]>,
242 Data: AsRef<[u8]>,
243{
244 assert_eq!(b.cols(), a.cols());
245 let data_a: &[i64] = a.raw();
246 let data_b: &mut [i64] = b.raw_mut();
247 let size = min(data_b.len(), data_a.len());
248 data_b[..size].copy_from_slice(&data_a[..size])
249}
250
251#[allow(dead_code)]
252fn normalize_tmp_bytes(n: usize) -> usize {
253 n * std::mem::size_of::<i64>()
254}
255
256impl<D: AsRef<[u8]> + AsMut<[u8]>> VecZnx<D> {
257 pub fn normalize(&mut self, basek: usize, a_col: usize, tmp_bytes: &mut [u8]) {
258 normalize(basek, self, a_col, tmp_bytes);
259 }
260}
261
262fn normalize<D: AsMut<[u8]> + AsRef<[u8]>>(basek: usize, a: &mut VecZnx<D>, a_col: usize, tmp_bytes: &mut [u8]) {
263 let n: usize = a.n();
264
265 debug_assert!(
266 tmp_bytes.len() >= normalize_tmp_bytes(n),
267 "invalid tmp_bytes: tmp_bytes.len()={} < normalize_tmp_bytes({})",
268 tmp_bytes.len(),
269 n,
270 );
271
272 #[cfg(debug_assertions)]
273 {
274 assert_alignement(tmp_bytes.as_ptr())
275 }
276
277 let carry_i64: &mut [i64] = cast_mut(tmp_bytes);
278
279 unsafe {
280 znx::znx_zero_i64_ref(n as u64, carry_i64.as_mut_ptr());
281 (0..a.size()).rev().for_each(|i| {
282 znx::znx_normalize(
283 n as u64,
284 basek as u64,
285 a.at_mut_ptr(a_col, i),
286 carry_i64.as_mut_ptr(),
287 a.at_mut_ptr(a_col, i),
288 carry_i64.as_mut_ptr(),
289 )
290 });
291 }
292}
293
294impl<D: AsMut<[u8]> + AsRef<[u8]>> VecZnx<D>
295where
296 VecZnx<D>: VecZnxToMut + ZnxInfos,
297{
298 /// Extracts the a_col-th column of 'a' and stores it on the self_col-th column [Self].
299 pub fn extract_column<R>(&mut self, self_col: usize, a: &VecZnx<R>, a_col: usize)
300 where
301 R: AsRef<[u8]>,
302 VecZnx<R>: VecZnxToRef + ZnxInfos,
303 {
304 #[cfg(debug_assertions)]
305 {
306 assert!(self_col < self.cols());
307 assert!(a_col < a.cols());
308 }
309
310 let min_size: usize = self.size.min(a.size());
311 let max_size: usize = self.size;
312
313 let mut self_mut: VecZnx<&mut [u8]> = self.to_mut();
314 let a_ref: VecZnx<&[u8]> = a.to_ref();
315
316 (0..min_size).for_each(|i: usize| {
317 self_mut
318 .at_mut(self_col, i)
319 .copy_from_slice(a_ref.at(a_col, i));
320 });
321
322 (min_size..max_size).for_each(|i| {
323 self_mut.zero_at(self_col, i);
324 });
325 }
326}
327
328impl<D: AsRef<[u8]>> fmt::Display for VecZnx<D> {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 writeln!(
331 f,
332 "VecZnx(n={}, cols={}, size={})",
333 self.n, self.cols, self.size
334 )?;
335
336 for col in 0..self.cols {
337 writeln!(f, "Column {}:", col)?;
338 for size in 0..self.size {
339 let coeffs = self.at(col, size);
340 write!(f, " Size {}: [", size)?;
341
342 let max_show = 100;
343 let show_count = coeffs.len().min(max_show);
344
345 for (i, &coeff) in coeffs.iter().take(show_count).enumerate() {
346 if i > 0 {
347 write!(f, ", ")?;
348 }
349 write!(f, "{}", coeff)?;
350 }
351
352 if coeffs.len() > max_show {
353 write!(f, ", ... ({} more)", coeffs.len() - max_show)?;
354 }
355
356 writeln!(f, "]")?;
357 }
358 }
359 Ok(())
360 }
361}
362
363pub type VecZnxOwned = VecZnx<Vec<u8>>;
364pub type VecZnxMut<'a> = VecZnx<&'a mut [u8]>;
365pub type VecZnxRef<'a> = VecZnx<&'a [u8]>;
366
367pub trait VecZnxToRef {
368 fn to_ref(&self) -> VecZnx<&[u8]>;
369}
370
371impl<D> VecZnxToRef for VecZnx<D>
372where
373 D: AsRef<[u8]>,
374{
375 fn to_ref(&self) -> VecZnx<&[u8]> {
376 VecZnx {
377 data: self.data.as_ref(),
378 n: self.n,
379 cols: self.cols,
380 size: self.size,
381 }
382 }
383}
384
385pub trait VecZnxToMut {
386 fn to_mut(&mut self) -> VecZnx<&mut [u8]>;
387}
388
389impl<D> VecZnxToMut for VecZnx<D>
390where
391 D: AsRef<[u8]> + AsMut<[u8]>,
392{
393 fn to_mut(&mut self) -> VecZnx<&mut [u8]> {
394 VecZnx {
395 data: self.data.as_mut(),
396 n: self.n,
397 cols: self.cols,
398 size: self.size,
399 }
400 }
401}
402
403impl<DataSelf: AsRef<[u8]>> VecZnx<DataSelf> {
404 pub fn clone(&self) -> VecZnx<Vec<u8>> {
405 let self_ref: VecZnx<&[u8]> = self.to_ref();
406 VecZnx {
407 data: self_ref.data.to_vec(),
408 n: self_ref.n,
409 cols: self_ref.cols,
410 size: self_ref.size,
411 }
412 }
413}