Skip to main content

poulpy_ckks/delegates/
composite.rs

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
24/// Guards `n` un-normalized accumulations against worst-case `i64` overflow.
25///
26/// Signed limb digits lie in `[−2^(base2k−1), 2^(base2k−1))`.  In the worst
27/// case (all summands aligned in sign) the digit magnitude after `n` additions
28/// is `n · 2^(base2k−1)`, which overflows `i64` once `n ≥ 2^(64 − base2k)`.
29/// The bound enforced here, `n ≤ 2^(63 − base2k)`, provides one extra bit of
30/// headroom below that threshold.
31///
32/// In the typical case (sign-balanced CKKS inputs) digit growth follows an
33/// Irwin–Hall distribution with std dev `O(sqrt(n) · 2^(base2k−1) / sqrt(3))`,
34/// so the practical limit is much higher than this conservative bound.
35fn 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
45// --- CKKSAddManyOps ---
46
47impl<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
82// --- CKKSMulAddOps ---
83
84impl<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
214// --- CKKSAffineOps ---
215
216impl<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
309// --- CKKSMulSubOps ---
310
311impl<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
402// --- CKKSDotProductOps ---
403
404fn 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}