Skip to main content

tfhe/core_crypto/commons/math/decomposition/
term.rs

1use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
2use crate::core_crypto::commons::math::decomposition::DecompositionLevel;
3use crate::core_crypto::commons::numeric::{Numeric, UnsignedInteger};
4use crate::core_crypto::commons::parameters::DecompositionBaseLog;
5use std::fmt::Debug;
6
7/// A member of the decomposition.
8///
9/// If we decompose a value $\theta$ as a sum $\sum\_{i=1}^l\tilde{\theta}\_i\frac{q}{B^i}$, this
10/// represents a $\tilde{\theta}\_i$.
11#[derive(Debug, PartialEq, Eq, Clone)]
12pub struct DecompositionTerm<T>
13where
14    T: UnsignedInteger,
15{
16    level: usize,
17    base_log: usize,
18    value: T,
19}
20
21impl<T> DecompositionTerm<T>
22where
23    T: UnsignedInteger,
24{
25    // Creates a new decomposition term.
26    pub(crate) fn new(level: DecompositionLevel, base_log: DecompositionBaseLog, value: T) -> Self {
27        Self {
28            level: level.0,
29            base_log: base_log.0,
30            value,
31        }
32    }
33
34    /// Turn this term into a summand.
35    ///
36    /// If our member represents one $\tilde{\theta}\_i$ of the decomposition, this method returns
37    /// $\tilde{\theta}\_i\frac{q}{B^i}$.
38    ///
39    /// # Example
40    ///
41    /// ```rust
42    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
43    /// use tfhe::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
44    /// let decomposer =
45    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
46    /// let output = decomposer.decompose(2u32.pow(19)).next().unwrap();
47    /// assert_eq!(output.to_recomposition_summand(), 1048576);
48    /// ```
49    pub fn to_recomposition_summand(&self) -> T {
50        let shift: usize = <T as Numeric>::BITS - self.base_log * self.level;
51        self.value << shift
52    }
53
54    /// Return the value of the term. For the native modulus it is also the modular value of the
55    /// term.
56    ///
57    /// If our member represents one $\tilde{\theta}\_i$, this returns its actual value.
58    ///
59    /// # Example
60    ///
61    /// ```rust
62    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
63    /// use tfhe::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
64    /// let decomposer =
65    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
66    /// let output = decomposer.decompose(2u32.pow(19)).next().unwrap();
67    /// assert_eq!(output.value(), 1);
68    /// ```
69    pub fn value(&self) -> T {
70        self.value
71    }
72
73    /// Return the level of the term.
74    ///
75    /// If our member represents one $\tilde{\theta}\_i$, this returns the value of $i$.
76    ///
77    /// # Example
78    ///
79    /// ```rust
80    /// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
81    /// use tfhe::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
82    /// let decomposer =
83    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
84    /// let output = decomposer.decompose(2u32.pow(19)).next().unwrap();
85    /// assert_eq!(output.level(), DecompositionLevel(3));
86    /// ```
87    pub fn level(&self) -> DecompositionLevel {
88        DecompositionLevel(self.level)
89    }
90}
91
92/// A member of the decomposition.
93///
94/// If we decompose a value $\theta$ as a sum
95/// $\sum\_{i=1}^l\tilde{\theta}\_i\frac{v}{B^i}$, where $\lambda = \lceil{\log_2{q}}\rceil$ and
96/// $ v = 2^{\lambda} $. this represents a $\tilde{\theta}\_i$.
97#[derive(Debug, PartialEq, Eq, Clone)]
98pub struct DecompositionTermNonNative<T>
99where
100    T: UnsignedInteger,
101{
102    level: usize,
103    base_log: usize,
104    value: T,
105    ciphertext_modulus: CiphertextModulus<T>,
106}
107
108impl<T> DecompositionTermNonNative<T>
109where
110    T: UnsignedInteger,
111{
112    /// Creates a new decomposition term.
113    ///
114    /// The value is the actual (non modular) value of the decomposition term.
115    ///
116    /// To get the actual modular value for the given `ciphertext_modulus` use
117    /// [`Self::modular_value`].
118    pub(crate) fn new(
119        level: DecompositionLevel,
120        base_log: DecompositionBaseLog,
121        value: T,
122        ciphertext_modulus: CiphertextModulus<T>,
123    ) -> Self {
124        Self {
125            level: level.0,
126            base_log: base_log.0,
127            value,
128            ciphertext_modulus,
129        }
130    }
131
132    /// Turn this term into a summand.
133    ///
134    /// If our member represents one $\tilde{\theta}\_i$ of the decomposition, this method returns
135    /// $\tilde{\theta}\_i\frac{v}{B^i}$ where $\lambda = \lceil{\log_2{q}}\rceil$ and
136    /// $ v = 2^{\lambda} $.
137    ///
138    /// # Example
139    ///
140    /// ```rust
141    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
142    /// use tfhe::core_crypto::commons::parameters::{
143    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
144    /// };
145    /// let decomposer = SignedDecomposerNonNative::new(
146    ///     DecompositionBaseLog(4),
147    ///     DecompositionLevelCount(3),
148    ///     CiphertextModulus::try_new((1 << 64) - (1 << 32) + 1).unwrap(),
149    /// );
150    /// let output = decomposer.decompose(2u64.pow(52)).next().unwrap();
151    /// assert_eq!(output.to_approximate_recomposition_summand(), 2u64.pow(52));
152    /// ```
153    pub fn to_approximate_recomposition_summand(&self) -> T {
154        let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
155        let ciphertext_modulus_bit_count: usize = modulus_as_t.ceil_ilog2().try_into().unwrap();
156        let shift: usize = ciphertext_modulus_bit_count - self.base_log * self.level;
157
158        let value = self.value;
159        if value.into_signed() >= T::Signed::ZERO {
160            value << shift
161        } else {
162            modulus_as_t.wrapping_add(value << shift)
163        }
164    }
165
166    /// Return the value of the term.
167    ///
168    /// If our member represents one $\tilde{\theta}\_i$, this returns its actual value.
169    ///
170    /// # Example
171    ///
172    /// ```rust
173    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
174    /// use tfhe::core_crypto::commons::parameters::{
175    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
176    /// };
177    /// let decomposer = SignedDecomposerNonNative::new(
178    ///     DecompositionBaseLog(4),
179    ///     DecompositionLevelCount(3),
180    ///     CiphertextModulus::try_new((1 << 64) - (1 << 32) + 1).unwrap(),
181    /// );
182    /// let output = decomposer.decompose(2u64.pow(52)).next().unwrap();
183    /// assert_eq!(output.value(), 1);
184    /// ```
185    pub fn value(&self) -> T {
186        self.value
187    }
188
189    /// Return the value of the term modulo the modulus given when building the
190    /// [`DecompositionTermNonNative`].
191    pub fn modular_value(&self) -> T {
192        let value = self.value;
193        if value.into_signed() >= T::Signed::ZERO {
194            value
195        } else {
196            let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
197            modulus_as_t.wrapping_add(value)
198        }
199    }
200
201    /// Return the level of the term.
202    ///
203    /// If our member represents one $\tilde{\theta}\_i$, this returns the value of $i$.
204    ///
205    /// # Example
206    ///
207    /// ```rust
208    /// use tfhe::core_crypto::commons::math::decomposition::{
209    ///     DecompositionLevel, SignedDecomposerNonNative,
210    /// };
211    /// use tfhe::core_crypto::commons::parameters::{
212    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
213    /// };
214    /// let decomposer = SignedDecomposerNonNative::new(
215    ///     DecompositionBaseLog(4),
216    ///     DecompositionLevelCount(3),
217    ///     CiphertextModulus::try_new((1 << 64) - (1 << 32) + 1).unwrap(),
218    /// );
219    /// let output = decomposer.decompose(2u64.pow(52)).next().unwrap();
220    /// assert_eq!(output.level(), DecompositionLevel(3));
221    /// ```
222    pub fn level(&self) -> DecompositionLevel {
223        DecompositionLevel(self.level)
224    }
225}
226
227/// A tensor whose elements are the terms of the decomposition of another tensor.
228///
229/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
230/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
231/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
232#[derive(Debug, PartialEq, Eq, Clone)]
233pub struct DecompositionTermSlice<'a, T>
234where
235    T: UnsignedInteger,
236{
237    slice: &'a [T],
238    level: usize,
239    base_log: usize,
240}
241
242impl<'a, T> DecompositionTermSlice<'a, T>
243where
244    T: UnsignedInteger,
245{
246    // Creates a new tensor decomposition term.
247    pub(crate) fn new(
248        level: DecompositionLevel,
249        base_log: DecompositionBaseLog,
250        slice: &'a [T],
251    ) -> Self {
252        Self {
253            level: level.0,
254            base_log: base_log.0,
255            slice,
256        }
257    }
258
259    /// Fills the output tensor with the terms turned to summands.
260    ///
261    /// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
262    /// decomposition, this method fills the output tensor with a set of
263    /// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
264    ///
265    /// # Example
266    ///
267    /// ```rust
268    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
269    /// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
270    /// let decomposer =
271    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
272    /// let input = vec![2u32.pow(19); 2];
273    /// let mut decomp = decomposer.decompose_slice(&input);
274    /// let term = decomp.next_term().unwrap();
275    /// let mut output = vec![0u32; 2];
276    /// term.fill_slice_with_recomposition_summand(&mut output);
277    /// assert!(output.iter().all(|&x| x == 1048576));
278    /// ```
279    pub fn fill_slice_with_recomposition_summand(&self, output: &mut [T]) {
280        assert_eq!(self.slice.len(), output.len());
281        output
282            .iter_mut()
283            .zip(self.slice.iter())
284            .for_each(|(dst, &value)| {
285                let shift: usize = <T as Numeric>::BITS - self.base_log * self.level;
286                *dst = value << shift
287            });
288    }
289
290    /// Returns a tensor with the values of term.
291    ///
292    /// # Example
293    ///
294    /// ```rust
295    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
296    /// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
297    /// let decomposer =
298    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
299    /// let input = vec![2u32.pow(19); 2];
300    /// let mut decomp = decomposer.decompose_slice(&input);
301    /// let term = decomp.next_term().unwrap();
302    /// assert_eq!(term.as_slice()[0], 1);
303    /// ```
304    pub fn as_slice(&self) -> &'a [T] {
305        self.slice
306    }
307
308    /// Returns the level of this decomposition term tensor.
309    ///
310    /// # Example
311    ///
312    /// ```rust
313    /// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
314    /// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
315    /// let decomposer =
316    ///     SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
317    /// let input = vec![2u32.pow(19); 2];
318    /// let mut decomp = decomposer.decompose_slice(&input);
319    /// let term = decomp.next_term().unwrap();
320    /// assert_eq!(term.level(), DecompositionLevel(3));
321    /// ```
322    pub fn level(&self) -> DecompositionLevel {
323        DecompositionLevel(self.level)
324    }
325}
326
327/// A tensor whose elements are the terms of the decomposition of another tensor.
328///
329/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
330/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
331/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
332#[derive(Debug, PartialEq, Eq, Clone)]
333pub struct DecompositionTermSliceNonNative<'a, T>
334where
335    T: UnsignedInteger,
336{
337    slice: &'a [T],
338    level: usize,
339    base_log: usize,
340    ciphertext_modulus: CiphertextModulus<T>,
341}
342
343impl<'a, T> DecompositionTermSliceNonNative<'a, T>
344where
345    T: UnsignedInteger,
346{
347    // Creates a new tensor decomposition term.
348    pub(crate) fn new(
349        level: DecompositionLevel,
350        base_log: DecompositionBaseLog,
351        slice: &'a [T],
352        ciphertext_modulus: CiphertextModulus<T>,
353    ) -> Self {
354        Self {
355            level: level.0,
356            base_log: base_log.0,
357            slice,
358            ciphertext_modulus,
359        }
360    }
361
362    /// Fills the output tensor with the terms turned to summands.
363    ///
364    /// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
365    /// decomposition, this method fills the output tensor with a set of
366    /// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
367    ///
368    /// # Example
369    ///
370    /// ```rust
371    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
372    /// use tfhe::core_crypto::prelude::{
373    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
374    /// };
375    /// let decomposer = SignedDecomposerNonNative::<u32>::new(
376    ///     DecompositionBaseLog(4),
377    ///     DecompositionLevelCount(3),
378    ///     CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
379    /// );
380    /// let input = vec![2u32.pow(19); 2];
381    /// let mut decomp = decomposer.decompose_slice(&input);
382    /// let term = decomp.next_term().unwrap();
383    /// let mut output = vec![0; 2];
384    /// term.to_approximate_recomposition_summand(&mut output);
385    /// assert!(output.iter().all(|&x| x == 1048576));
386    /// ```
387    pub fn to_approximate_recomposition_summand(&self, output: &mut [T]) {
388        assert_eq!(self.slice.len(), output.len());
389        let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
390        let ciphertext_modulus_bit_count: usize = modulus_as_t.ceil_ilog2().try_into().unwrap();
391        let shift: usize = ciphertext_modulus_bit_count - self.base_log * self.level;
392
393        output
394            .iter_mut()
395            .zip(self.slice.iter())
396            .for_each(|(dst, &value)| {
397                if value.into_signed() >= T::Signed::ZERO {
398                    *dst = value << shift
399                } else {
400                    *dst = modulus_as_t.wrapping_add(value << shift)
401                }
402            });
403    }
404
405    /// Compute the value of the term modulo the modulus given when building the
406    /// [`DecompositionTermSliceNonNative`]
407    pub(crate) fn modular_value(&self, output: &mut [T]) {
408        assert_eq!(self.slice.len(), output.len());
409        let modulus_as_t = T::cast_from(self.ciphertext_modulus.get_custom_modulus());
410        self.slice
411            .iter()
412            .zip(output.iter_mut())
413            .for_each(|(&value, output)| {
414                if value.into_signed() >= T::Signed::ZERO {
415                    *output = value
416                } else {
417                    *output = modulus_as_t.wrapping_add(value)
418                }
419            });
420    }
421
422    /// Returns a tensor with the values of term.
423    ///
424    /// # Example
425    ///
426    /// ```rust
427    /// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposerNonNative;
428    /// use tfhe::core_crypto::prelude::{
429    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
430    /// };
431    /// let decomposer = SignedDecomposerNonNative::<u32>::new(
432    ///     DecompositionBaseLog(4),
433    ///     DecompositionLevelCount(3),
434    ///     CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
435    /// );
436    /// let input = vec![2u32.pow(19); 2];
437    /// let mut decomp = decomposer.decompose_slice(&input);
438    /// let term = decomp.next_term().unwrap();
439    /// assert_eq!(term.as_slice()[0], 1);
440    /// ```
441    pub fn as_slice(&self) -> &'a [T] {
442        self.slice
443    }
444
445    /// Returns the level of this decomposition term tensor.
446    ///
447    /// # Example
448    ///
449    /// ```rust
450    /// use tfhe::core_crypto::commons::math::decomposition::{
451    ///     DecompositionLevel, SignedDecomposerNonNative,
452    /// };
453    /// use tfhe::core_crypto::prelude::{
454    ///     CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
455    /// };
456    /// let decomposer = SignedDecomposerNonNative::<u32>::new(
457    ///     DecompositionBaseLog(4),
458    ///     DecompositionLevelCount(3),
459    ///     CiphertextModulus::try_new((1 << 32) - 1).unwrap(),
460    /// );
461    /// let input = vec![2u32.pow(19); 2];
462    /// let mut decomp = decomposer.decompose_slice(&input);
463    /// let term = decomp.next_term().unwrap();
464    /// assert_eq!(term.level(), DecompositionLevel(3));
465    /// ```
466    pub fn level(&self) -> DecompositionLevel {
467        DecompositionLevel(self.level)
468    }
469}