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}