1use anyhow::{Result, bail, ensure};
2use poulpy_core::{
3 GLWENormalize, GLWETensoring,
4 layouts::{
5 GGLWEInfos, GLWE, GLWEInfos, GLWELayout, GLWETensor, GLWETensorKeyPrepared, GLWEToBackendMut, GLWEToBackendRef, LWEInfos,
6 TorusPrecision,
7 },
8};
9use poulpy_hal::layouts::{Backend, Data, Module, ScratchArena};
10
11use crate::{
12 CKKSCtBounds, CKKSInfos,
13 layouts::{
14 CKKSCiphertext, CKKSCiphertextViewMut, ScratchArenaTakeCKKS, UnnormalizedCKKSCiphertext,
15 ciphertext::{CKKSOffset, UnnormalizedCKKSCiphertextRefMut},
16 },
17 leveled::api::{
18 CKKSAddManyOps, CKKSAddOps, CKKSAddOpsUnnormalized, CKKSAffineOps, CKKSDotProductOps, CKKSMulAddOps, CKKSMulOps,
19 CKKSMulSubOps, CKKSRescaleOps, CKKSSubOps,
20 },
21 oep::CKKSAddImpl,
22};
23
24fn ensure_accumulation_fits<D: Data>(op: &'static str, dst: &CKKSCiphertext<D>, n: usize) -> Result<()> {
36 let base2k: usize = dst.base2k().as_usize();
37 ensure!(base2k < 64, "{op}: unsupported base2k={base2k}");
38 ensure!(
39 n <= (1usize << (63 - base2k)),
40 "{op}: {n} terms risks i64 overflow at base2k={base2k}",
41 );
42 Ok(())
43}
44
45impl<BE: Backend> CKKSAddManyOps<BE> for Module<BE>
48where
49 Module<BE>: CKKSAddOps<BE> + CKKSRescaleOps<BE>,
50{
51 fn ckks_add_many_tmp_bytes(&self) -> usize {
52 self.ckks_add_tmp_bytes()
53 }
54
55 fn ckks_add_many<Dst: Data, Src: Data>(
56 &self,
57 dst: &mut CKKSCiphertext<Dst>,
58 inputs: &[&CKKSCiphertext<Src>],
59 scratch: &mut ScratchArena<'_, BE>,
60 ) -> Result<()>
61 where
62 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
63 CKKSCiphertext<Src>: GLWEToBackendRef<BE>,
64 {
65 match inputs.len() {
66 0 => bail!("ckks_add_many: inputs must contain at least one ciphertext"),
67 1 => {
68 self.ckks_rescale_into(dst, dst.offset_unary(inputs[0]), inputs[0], scratch)?;
69 }
70 _ => {
71 ensure_accumulation_fits("ckks_add_many", dst, inputs.len())?;
72 self.ckks_add_into(dst, inputs[0], inputs[1], scratch)?;
73 for ct in &inputs[2..] {
74 self.ckks_add_assign(dst, *ct, scratch)?;
75 }
76 }
77 }
78 Ok(())
79 }
80}
81
82impl<BE: Backend> CKKSMulAddOps<BE> for Module<BE>
85where
86 Module<BE>: CKKSAddOps<BE> + CKKSMulOps<BE> + CKKSAddOpsUnnormalized<BE>,
87{
88 fn ckks_mul_add_ct_tmp_bytes<R, T>(&self, res: &R, tsk: &T) -> usize
89 where
90 R: CKKSCtBounds,
91 T: GGLWEInfos,
92 {
93 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_tmp_bytes(res, tsk).max(self.ckks_add_tmp_bytes())
94 }
95
96 fn ckks_mul_add_pt_vec_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
97 where
98 R: CKKSCtBounds,
99 A: CKKSCtBounds,
100 P: CKKSInfos,
101 {
102 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_vec_tmp_bytes(res, a, b).max(self.ckks_add_tmp_bytes())
103 }
104
105 fn ckks_mul_add_pt_const_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
106 where
107 R: CKKSCtBounds,
108 A: CKKSCtBounds,
109 P: CKKSInfos,
110 {
111 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_const_tmp_bytes(res, a, b).max(self.ckks_add_tmp_bytes())
112 }
113
114 fn ckks_mul_add_ct_into<Dst: Data, A: Data, B: Data, T: Data>(
115 &self,
116 dst: &mut CKKSCiphertext<Dst>,
117 a: &CKKSCiphertext<A>,
118 b: &CKKSCiphertext<B>,
119 tsk: &GLWETensorKeyPrepared<T, BE>,
120 scratch: &mut ScratchArena<'_, BE>,
121 ) -> Result<()>
122 where
123 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
124 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
125 CKKSCiphertext<B>: GLWEToBackendRef<BE> + GLWEInfos,
126 GLWETensorKeyPrepared<T, BE>: poulpy_core::layouts::prepared::GLWETensorKeyPreparedToBackendRef<BE>,
127 {
128 scratch.scope(|scratch_local| {
129 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
130 self.ckks_mul_into(&mut tmp, a, b, tsk, &mut scratch_local)?;
131 self.ckks_add_assign(dst, &tmp, &mut scratch_local)
132 })
133 }
134
135 fn ckks_mul_add_pt_vec_into<Dst: Data, A: Data, P>(
136 &self,
137 dst: &mut CKKSCiphertext<Dst>,
138 a: &CKKSCiphertext<A>,
139 pt: &P,
140 scratch: &mut ScratchArena<'_, BE>,
141 ) -> Result<()>
142 where
143 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
144 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
145 P: GLWEToBackendRef<BE> + CKKSCtBounds,
146 {
147 scratch.scope(|scratch_local| {
148 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
149 self.ckks_mul_pt_vec_into(&mut tmp, a, pt, &mut scratch_local)?;
150 self.ckks_add_assign(dst, &tmp, &mut scratch_local)
151 })
152 }
153
154 fn ckks_mul_add_pt_const_into<Dst: Data, A: Data, P>(
155 &self,
156 dst: &mut CKKSCiphertext<Dst>,
157 a: &CKKSCiphertext<A>,
158 pt: &P,
159 pt_coeff: usize,
160 scratch: &mut ScratchArena<'_, BE>,
161 ) -> Result<()>
162 where
163 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
164 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
165 P: GLWEToBackendRef<BE> + CKKSCtBounds,
166 {
167 scratch.scope(|scratch_local| {
168 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
169 self.ckks_mul_pt_const_into(&mut tmp, a, pt, pt_coeff, &mut scratch_local)?;
170 self.ckks_add_assign(dst, &tmp, &mut scratch_local)
171 })
172 }
173
174 fn ckks_mul_add_pt_const_into_unnormalized<Dst: Data, A, P>(
175 &self,
176 dst: &mut UnnormalizedCKKSCiphertext<Dst>,
177 a: &A,
178 pt: &P,
179 pt_coeff: usize,
180 scratch: &mut ScratchArena<'_, BE>,
181 ) -> Result<()>
182 where
183 UnnormalizedCKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
184 A: GLWEToBackendRef<BE> + CKKSCtBounds,
185 P: GLWEToBackendRef<BE> + CKKSCtBounds,
186 {
187 scratch.scope(|scratch_local| {
188 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
189 self.ckks_mul_pt_const_into(&mut tmp, a, pt, pt_coeff, &mut scratch_local)?;
190 self.ckks_add_assign_unnormalized(dst, &tmp, &mut scratch_local)
191 })
192 }
193
194 fn ckks_mul_add_pt_vec_into_unnormalized<Dst: Data, A, P>(
195 &self,
196 dst: &mut UnnormalizedCKKSCiphertext<Dst>,
197 a: &A,
198 pt: &P,
199 scratch: &mut ScratchArena<'_, BE>,
200 ) -> Result<()>
201 where
202 UnnormalizedCKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
203 A: GLWEToBackendRef<BE> + CKKSCtBounds,
204 P: GLWEToBackendRef<BE> + CKKSCtBounds,
205 {
206 scratch.scope(|scratch_local| {
207 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
208 self.ckks_mul_pt_vec_into(&mut tmp, a, pt, &mut scratch_local)?;
209 self.ckks_add_assign_unnormalized(dst, &tmp, &mut scratch_local)
210 })
211 }
212}
213
214impl<BE: Backend> CKKSAffineOps<BE> for Module<BE>
217where
218 Module<BE>: CKKSAddOps<BE> + CKKSMulOps<BE>,
219{
220 fn ckks_affine_pt_const_tmp_bytes<R, A, P>(&self, res: &R, a: &A, affine_const: &P) -> usize
221 where
222 R: CKKSCtBounds,
223 A: CKKSCtBounds,
224 P: CKKSInfos,
225 {
226 self.ckks_mul_pt_const_tmp_bytes(res, a, affine_const)
227 .max(self.ckks_add_pt_const_tmp_bytes())
228 }
229
230 fn ckks_affine_pt_const_into<Dst, A, P>(
231 &self,
232 dst: &mut Dst,
233 a: &A,
234 affine_const: &P,
235 offset_coeff: usize,
236 scale_coeff: usize,
237 scratch: &mut ScratchArena<'_, BE>,
238 ) -> Result<()>
239 where
240 Dst: GLWEToBackendMut<BE> + CKKSCtBounds + crate::SetCKKSInfos,
241 A: GLWEToBackendRef<BE> + CKKSCtBounds,
242 P: GLWEToBackendRef<BE> + CKKSCtBounds,
243 {
244 self.ckks_mul_pt_const_into(dst, a, affine_const, scale_coeff, scratch)?;
245 self.ckks_add_pt_const_assign(dst, 0, affine_const, offset_coeff, scratch)
246 }
247
248 fn ckks_affine_pt_const_assign<Dst, P>(
249 &self,
250 dst: &mut Dst,
251 affine_const: &P,
252 offset_coeff: usize,
253 scale_coeff: usize,
254 scratch: &mut ScratchArena<'_, BE>,
255 ) -> Result<()>
256 where
257 Dst: GLWEToBackendMut<BE> + GLWEToBackendRef<BE> + CKKSCtBounds + crate::SetCKKSInfos,
258 P: GLWEToBackendRef<BE> + CKKSCtBounds,
259 {
260 self.ckks_mul_pt_const_assign(dst, affine_const, scale_coeff, scratch)?;
261 self.ckks_add_pt_const_assign(dst, 0, affine_const, offset_coeff, scratch)
262 }
263
264 fn ckks_affine_pt_vec_tmp_bytes<R, A, S>(&self, res: &R, a: &A, scale: &S) -> usize
265 where
266 R: CKKSCtBounds,
267 A: CKKSCtBounds,
268 S: CKKSInfos,
269 {
270 self.ckks_mul_pt_vec_tmp_bytes(res, a, scale)
271 .max(self.ckks_add_pt_vec_tmp_bytes())
272 }
273
274 fn ckks_affine_pt_vec_into<Dst, A, S, P>(
275 &self,
276 dst: &mut Dst,
277 a: &A,
278 scale: &S,
279 offset: &P,
280 scratch: &mut ScratchArena<'_, BE>,
281 ) -> Result<()>
282 where
283 Dst: GLWEToBackendMut<BE> + CKKSCtBounds + crate::SetCKKSInfos,
284 A: GLWEToBackendRef<BE> + CKKSCtBounds,
285 S: GLWEToBackendRef<BE> + CKKSCtBounds,
286 P: GLWEToBackendRef<BE> + CKKSCtBounds,
287 {
288 self.ckks_mul_pt_vec_into(dst, a, scale, scratch)?;
289 self.ckks_add_pt_vec_assign(dst, offset, scratch)
290 }
291
292 fn ckks_affine_pt_vec_assign<Dst, S, P>(
293 &self,
294 dst: &mut Dst,
295 scale: &S,
296 offset: &P,
297 scratch: &mut ScratchArena<'_, BE>,
298 ) -> Result<()>
299 where
300 Dst: GLWEToBackendMut<BE> + GLWEToBackendRef<BE> + CKKSCtBounds + crate::SetCKKSInfos,
301 S: GLWEToBackendRef<BE> + CKKSCtBounds,
302 P: GLWEToBackendRef<BE> + CKKSCtBounds,
303 {
304 self.ckks_mul_pt_vec_assign(dst, scale, scratch)?;
305 self.ckks_add_pt_vec_assign(dst, offset, scratch)
306 }
307}
308
309impl<BE: Backend> CKKSMulSubOps<BE> for Module<BE>
312where
313 Module<BE>: CKKSMulOps<BE> + CKKSSubOps<BE>,
314{
315 fn ckks_mul_sub_ct_tmp_bytes<R, T>(&self, res: &R, tsk: &T) -> usize
316 where
317 R: CKKSCtBounds,
318 T: GGLWEInfos,
319 {
320 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_tmp_bytes(res, tsk).max(self.ckks_sub_tmp_bytes())
321 }
322
323 fn ckks_mul_sub_pt_vec_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
324 where
325 R: CKKSCtBounds,
326 A: CKKSCtBounds,
327 P: CKKSInfos,
328 {
329 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_vec_tmp_bytes(res, a, b).max(self.ckks_sub_tmp_bytes())
330 }
331
332 fn ckks_mul_sub_pt_const_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
333 where
334 R: CKKSCtBounds,
335 A: CKKSCtBounds,
336 P: CKKSInfos,
337 {
338 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_const_tmp_bytes(res, a, b).max(self.ckks_sub_tmp_bytes())
339 }
340
341 fn ckks_mul_sub_ct_into<Dst: Data, A: Data, B: Data, T: Data>(
342 &self,
343 dst: &mut CKKSCiphertext<Dst>,
344 a: &CKKSCiphertext<A>,
345 b: &CKKSCiphertext<B>,
346 tsk: &GLWETensorKeyPrepared<T, BE>,
347 scratch: &mut ScratchArena<'_, BE>,
348 ) -> Result<()>
349 where
350 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
351 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
352 CKKSCiphertext<B>: GLWEToBackendRef<BE> + GLWEInfos,
353 GLWETensorKeyPrepared<T, BE>: poulpy_core::layouts::prepared::GLWETensorKeyPreparedToBackendRef<BE>,
354 {
355 scratch.scope(|scratch_local| {
356 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
357 self.ckks_mul_into(&mut tmp, a, b, tsk, &mut scratch_local)?;
358 self.ckks_sub_assign(dst, &tmp, &mut scratch_local)
359 })
360 }
361
362 fn ckks_mul_sub_pt_vec_into<Dst: Data, A: Data, P>(
363 &self,
364 dst: &mut CKKSCiphertext<Dst>,
365 a: &CKKSCiphertext<A>,
366 pt: &P,
367 scratch: &mut ScratchArena<'_, BE>,
368 ) -> Result<()>
369 where
370 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
371 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
372 P: GLWEToBackendRef<BE> + CKKSCtBounds,
373 {
374 scratch.scope(|scratch_local| {
375 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
376 self.ckks_mul_pt_vec_into(&mut tmp, a, pt, &mut scratch_local)?;
377 self.ckks_sub_assign(dst, &tmp, &mut scratch_local)
378 })
379 }
380
381 fn ckks_mul_sub_pt_const_into<Dst: Data, A: Data, P>(
382 &self,
383 dst: &mut CKKSCiphertext<Dst>,
384 a: &CKKSCiphertext<A>,
385 pt: &P,
386 pt_coeff: usize,
387 scratch: &mut ScratchArena<'_, BE>,
388 ) -> Result<()>
389 where
390 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
391 CKKSCiphertext<A>: GLWEToBackendRef<BE> + GLWEInfos,
392 P: GLWEToBackendRef<BE> + CKKSCtBounds,
393 {
394 scratch.scope(|scratch_local| {
395 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
396 self.ckks_mul_pt_const_into(&mut tmp, a, pt, pt_coeff, &mut scratch_local)?;
397 self.ckks_sub_assign(dst, &tmp, &mut scratch_local)
398 })
399 }
400}
401
402fn check_lengths(op: &'static str, a_len: usize, b_len: usize) -> Result<()> {
405 if a_len == 0 {
406 bail!("{op}: inputs must contain at least one pair");
407 }
408 if a_len != b_len {
409 bail!("{op}: length mismatch between ct vector ({a_len}) and weight vector ({b_len})");
410 }
411 Ok(())
412}
413
414fn accumulate_unnormalized<BE, D, F>(
415 module: &Module<BE>,
416 dst: &mut CKKSCiphertext<D>,
417 n: usize,
418 scratch: &mut ScratchArena<'_, BE>,
419 mut mul_term_into_tmp: F,
420) -> Result<()>
421where
422 BE: Backend,
423 D: Data,
424 BE: CKKSAddImpl<BE>,
425 Module<BE>: GLWENormalize<BE>,
426 CKKSCiphertext<D>: GLWEToBackendMut<BE>,
427 F: for<'a> FnMut(&mut CKKSCiphertextViewMut<'a, BE>, usize, &mut ScratchArena<'a, BE>) -> Result<()>,
428{
429 if n <= 1 {
430 module.glwe_normalize_assign(dst, scratch);
431 return Ok(());
432 }
433 scratch.scope(|scratch_local| {
434 let (mut tmp, mut scratch_local) = scratch_local.take_ckks_ciphertext_like_scratch(dst);
435 let mut acc = UnnormalizedCKKSCiphertextRefMut::new(dst);
436 for i in 1..n {
437 mul_term_into_tmp(&mut tmp, i, &mut scratch_local)?;
438 BE::ckks_add_assign_unnormalized_ref(module, &mut acc, &tmp, &mut scratch_local)?;
439 }
440 acc.normalize(module, &mut scratch_local);
441 Ok(())
442 })
443}
444
445impl<BE: Backend + CKKSAddImpl<BE>> CKKSDotProductOps<BE> for Module<BE>
446where
447 Module<BE>: CKKSAddOps<BE> + CKKSMulOps<BE> + CKKSRescaleOps<BE> + GLWENormalize<BE> + GLWETensoring<BE>,
448{
449 fn ckks_dot_product_ct_tmp_bytes<R, T>(&self, n: usize, res: &R, tsk: &T) -> usize
450 where
451 R: CKKSCtBounds,
452 T: GGLWEInfos,
453 {
454 let mul_scratch: usize = self.ckks_mul_tmp_bytes(res, tsk);
455 if n <= 1 {
456 return mul_scratch.max(self.glwe_normalize_tmp_bytes());
457 }
458 let ct_bytes: usize = GLWE::<Vec<u8>>::bytes_of_from_infos(res);
459 let fallback: usize = ct_bytes + mul_scratch.max(self.ckks_add_tmp_bytes());
460 let tensor_layout = GLWELayout {
461 n: res.n(),
462 base2k: res.base2k(),
463 k: TorusPrecision(res.max_k().as_u32()),
464 rank: res.rank(),
465 };
466 let tensor_bytes: usize = GLWETensor::bytes_of_from_infos(&tensor_layout);
467 let inner: usize = self
468 .ckks_rescale_tmp_bytes()
469 .max(self.glwe_tensor_apply_tmp_bytes(&tensor_layout, res, res))
470 .max(self.glwe_tensor_relinearize_tmp_bytes(res, &tensor_layout, tsk));
471 let fast: usize = 2 * n * ct_bytes + tensor_bytes + inner;
472 fallback.max(fast)
473 }
474
475 fn ckks_dot_product_pt_vec_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
476 where
477 R: CKKSCtBounds,
478 A: CKKSCtBounds,
479 P: CKKSInfos,
480 {
481 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_vec_tmp_bytes(res, a, b).max(self.ckks_add_tmp_bytes())
482 }
483
484 fn ckks_dot_product_pt_const_tmp_bytes<R, A, P>(&self, res: &R, a: &A, b: &P) -> usize
485 where
486 R: CKKSCtBounds,
487 A: CKKSCtBounds,
488 P: CKKSInfos,
489 {
490 GLWE::<Vec<u8>>::bytes_of_from_infos(res) + self.ckks_mul_pt_const_tmp_bytes(res, a, b).max(self.ckks_add_tmp_bytes())
491 }
492
493 fn ckks_dot_product_ct<Dst: Data, D: Data, E: Data, T: Data>(
494 &self,
495 dst: &mut CKKSCiphertext<Dst>,
496 a: &[&CKKSCiphertext<D>],
497 b: &[&CKKSCiphertext<E>],
498 tsk: &GLWETensorKeyPrepared<T, BE>,
499 scratch: &mut ScratchArena<'_, BE>,
500 ) -> Result<()>
501 where
502 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
503 CKKSCiphertext<D>: GLWEToBackendRef<BE> + GLWEInfos,
504 CKKSCiphertext<E>: GLWEToBackendRef<BE> + GLWEInfos,
505 GLWETensorKeyPrepared<T, BE>: poulpy_core::layouts::prepared::GLWETensorKeyPreparedToBackendRef<BE>,
506 {
507 check_lengths("ckks_dot_product_ct", a.len(), b.len())?;
508 let n: usize = a.len();
509 ensure_accumulation_fits("ckks_dot_product_ct", dst, n)?;
510 self.ckks_mul_into(dst, a[0], b[0], tsk, scratch)?;
511 accumulate_unnormalized(self, dst, n, scratch, |tmp, i, s| self.ckks_mul_into(tmp, a[i], b[i], tsk, s))
512 }
513
514 fn ckks_dot_product_pt_vec<Dst: Data, D: Data, E>(
515 &self,
516 dst: &mut CKKSCiphertext<Dst>,
517 a: &[&CKKSCiphertext<D>],
518 b: &[&E],
519 scratch: &mut ScratchArena<'_, BE>,
520 ) -> Result<()>
521 where
522 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
523 CKKSCiphertext<D>: GLWEToBackendRef<BE> + GLWEInfos,
524 E: GLWEToBackendRef<BE> + CKKSCtBounds,
525 {
526 check_lengths("ckks_dot_product_pt_vec", a.len(), b.len())?;
527 let n: usize = a.len();
528 ensure_accumulation_fits("ckks_dot_product_pt_vec", dst, n)?;
529 self.ckks_mul_pt_vec_into(dst, a[0], b[0], scratch)?;
530 accumulate_unnormalized(self, dst, n, scratch, |tmp, i, s| {
531 self.ckks_mul_pt_vec_into(tmp, a[i], b[i], s)
532 })
533 }
534
535 fn ckks_dot_product_pt_const<Dst: Data, D: Data, E>(
536 &self,
537 dst: &mut CKKSCiphertext<Dst>,
538 a: &[&CKKSCiphertext<D>],
539 b: &[&E],
540 pt_coeffs: &[usize],
541 scratch: &mut ScratchArena<'_, BE>,
542 ) -> Result<()>
543 where
544 CKKSCiphertext<Dst>: GLWEToBackendMut<BE>,
545 CKKSCiphertext<D>: GLWEToBackendRef<BE> + GLWEInfos,
546 E: GLWEToBackendRef<BE> + CKKSCtBounds,
547 {
548 check_lengths("ckks_dot_product_pt_const", a.len(), b.len())?;
549 check_lengths("ckks_dot_product_pt_const coeffs", a.len(), pt_coeffs.len())?;
550 let n: usize = a.len();
551 ensure_accumulation_fits("ckks_dot_product_pt_const", dst, n)?;
552 self.ckks_mul_pt_const_into(dst, a[0], b[0], pt_coeffs[0], scratch)?;
553 accumulate_unnormalized(self, dst, n, scratch, |tmp, i, s| {
554 self.ckks_mul_pt_const_into(tmp, a[i], b[i], pt_coeffs[i], s)
555 })
556 }
557}