concrete-core 1.0.2

Concrete is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE.
Documentation
use super::{GlweCiphertext, GlweList};
use crate::commons::crypto::encoding::PlaintextList;
use crate::commons::crypto::lwe::{LweCiphertext, LweList};
use crate::commons::crypto::secret::generators::EncryptionRandomGenerator;
use crate::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
use crate::commons::math::decomposition::{
    DecompositionLevel, DecompositionTerm, SignedDecomposer,
};
use crate::commons::math::polynomial::{Polynomial, PolynomialList};
use crate::commons::math::random::ByteRandomGenerator;
#[cfg(feature = "__commons_parallel")]
use crate::commons::math::random::ParallelByteRandomGenerator;
use crate::commons::math::tensor::{
    ck_dim_div, ck_dim_eq, tensor_traits, AsMutTensor, AsRefSlice, AsRefTensor, Container, Tensor,
};
use crate::commons::math::torus::UnsignedTorus;
use crate::prelude::{
    BinaryKeyKind, CiphertextCount, DecompositionBaseLog, DecompositionLevelCount,
    DispersionParameter, FunctionalPackingKeyswitchKeyCount, GlweDimension, GlweSize, LweDimension,
    LweSize, MonomialDegree, PlaintextCount, PolynomialCount, PolynomialSize,
};
#[cfg(feature = "__commons_parallel")]
use rayon::{iter::IndexedParallelIterator, prelude::*};
#[cfg(feature = "__commons_serialization")]
use serde::{Deserialize, Serialize};

/// A packing keyswitching key.
///
/// A packing keyswitching key allows to  pack several LWE ciphertexts into a single GLWE
/// ciphertext.
#[cfg_attr(feature = "__commons_serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LwePackingKeyswitchKey<Cont> {
    tensor: Tensor<Cont>,
    decomp_base_log: DecompositionBaseLog,
    decomp_level_count: DecompositionLevelCount,
    output_glwe_size: GlweSize,
    output_polynomial_size: PolynomialSize,
}

tensor_traits!(LwePackingKeyswitchKey);

impl<Scalar> LwePackingKeyswitchKey<Vec<Scalar>>
where
    Scalar: Copy,
{
    /// Allocates a packing keyswitching key whose masks and bodies are all `value`.
    ///
    /// # Note
    ///
    /// This function does *not* generate a keyswitch key, but merely allocates a container of the
    /// right size. See [`LwePackingKeyswitchKey::fill_with_keyswitch_key`] to fill the container
    /// with a proper keyswitching key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(
    ///     pksk.decomposition_level_count(),
    ///     DecompositionLevelCount(10)
    /// );
    /// assert_eq!(pksk.decomposition_base_log(), DecompositionBaseLog(16));
    /// assert_eq!(pksk.output_glwe_key_dimension(), GlweDimension(2));
    /// assert_eq!(pksk.input_lwe_key_dimension(), LweDimension(10));
    /// ```
    pub fn allocate(
        value: Scalar,
        decomp_size: DecompositionLevelCount,
        decomp_base_log: DecompositionBaseLog,
        input_dimension: LweDimension,
        output_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
    ) -> Self {
        LwePackingKeyswitchKey {
            tensor: Tensor::from_container(vec![
                value;
                decomp_size.0
                    * output_dimension.to_glwe_size().0
                    * output_polynomial_size.0
                    * input_dimension.0
            ]),
            decomp_base_log,
            decomp_level_count: decomp_size,
            output_glwe_size: output_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }
}

impl<Cont> LwePackingKeyswitchKey<Cont> {
    /// Creates a packing keyswitching key from a container.
    ///
    /// # Notes
    ///
    /// This method does not create a packing keyswitch key, but merely wraps the container in
    /// the proper type. It assumes that either the container already contains a proper keyswitching
    /// key, or that [`LwePackingKeyswitchKey::fill_with_keyswitch_key`] will be called right
    /// after.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// let input_size = LweDimension(200);
    /// let output_size = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(7);
    /// let decomp_level_count = DecompositionLevelCount(4);
    ///
    /// let pksk = LwePackingKeyswitchKey::from_container(
    ///     vec![
    ///         0 as u8;
    ///         input_size.0 * (output_size.0 + 1) * polynomial_size.0 * decomp_level_count.0
    ///     ],
    ///     decomp_base_log,
    ///     decomp_level_count,
    ///     output_size,
    ///     polynomial_size,
    /// );
    ///
    /// assert_eq!(pksk.decomposition_level_count(), DecompositionLevelCount(4));
    /// assert_eq!(pksk.decomposition_base_log(), DecompositionBaseLog(7));
    /// assert_eq!(pksk.output_glwe_key_dimension(), GlweDimension(2));
    /// assert_eq!(pksk.input_lwe_key_dimension(), LweDimension(200));
    /// ```
    pub fn from_container(
        cont: Cont,
        decomp_base_log: DecompositionBaseLog,
        decomp_size: DecompositionLevelCount,
        output_glwe_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
    ) -> LwePackingKeyswitchKey<Cont>
    where
        Cont: AsRefSlice,
    {
        let tensor = Tensor::from_container(cont);
        ck_dim_div!(tensor.len() => output_glwe_dimension.to_glwe_size().0 * output_polynomial_size.0, decomp_size.0);
        LwePackingKeyswitchKey {
            tensor,
            decomp_base_log,
            decomp_level_count: decomp_size,
            output_glwe_size: output_glwe_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }

    /// Returns the dimension of the output GLWE key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pksk.output_glwe_key_dimension(), GlweDimension(2));
    /// ```
    pub fn output_glwe_key_dimension(&self) -> GlweDimension {
        self.output_glwe_size.to_glwe_dimension()
    }

    /// Returns the size of the polynomials composing the GLWE ciphertext
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, LweSize,
    ///     PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pksk.output_polynomial_size(), PolynomialSize(256));
    /// ```
    pub fn output_polynomial_size(&self) -> PolynomialSize {
        self.output_polynomial_size
    }

    /// Returns the dimension of the input LWE key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pksk.input_lwe_key_dimension(), LweDimension(10));
    /// ```
    pub fn input_lwe_key_dimension(&self) -> LweDimension
    where
        Self: AsRefTensor,
    {
        LweDimension(
            self.as_tensor().len()
                / (self.output_glwe_size.0
                    * self.output_polynomial_size.0
                    * self.decomp_level_count.0),
        )
    }

    /// Returns the number of levels used for the decomposition of the input key bits.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(
    ///     pksk.decomposition_level_count(),
    ///     DecompositionLevelCount(10)
    /// );
    /// ```
    pub fn decomposition_level_count(&self) -> DecompositionLevelCount
    where
        Self: AsRefTensor,
    {
        self.decomp_level_count
    }

    /// Returns the logarithm of the base used for the decomposition of the input key bits.
    ///
    /// Indeed, the basis used is always of the form $2^b$. This function returns $b$.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pksk.decomposition_base_log(), DecompositionBaseLog(16));
    /// ```
    pub fn decomposition_base_log(&self) -> DecompositionBaseLog
    where
        Self: AsRefTensor,
    {
        self.decomp_base_log
    }

    /// Fills the current keyswitch key container with an actual keyswitching key constructed from
    /// an input and an output key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePackingKeyswitchKey;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LogStandardDev, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_size = LweDimension(10);
    /// let output_size = GlweDimension(3);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(5);
    /// let cipher_size = LweSize(55);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let noise = LogStandardDev::from_log_standard_dev(-15.);
    ///
    /// let input_key = LweSecretKey::generate_binary(input_size, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_size, polynomial_size, &mut secret_generator);
    ///
    /// let mut pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u32,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_size,
    ///     output_size,
    ///     polynomial_size,
    /// );
    /// pksk.fill_with_packing_keyswitch_key(&input_key, &output_key, noise, &mut encryption_generator);
    ///
    /// assert!(!pksk.as_tensor().iter().all(|a| *a == 0));
    /// ```
    pub fn fill_with_packing_keyswitch_key<InKeyCont, OutKeyCont, Scalar, Gen>(
        &mut self,
        input_lwe_key: &LweSecretKey<BinaryKeyKind, InKeyCont>,
        output_glwe_key: &GlweSecretKey<BinaryKeyKind, OutKeyCont>,
        noise_parameters: impl DispersionParameter,
        generator: &mut EncryptionRandomGenerator<Gen>,
    ) where
        Self: AsMutTensor<Element = Scalar>,
        LweSecretKey<BinaryKeyKind, InKeyCont>: AsRefTensor<Element = Scalar>,
        GlweSecretKey<BinaryKeyKind, OutKeyCont>: AsRefTensor<Element = Scalar>,
        Scalar: UnsignedTorus,
        Gen: ByteRandomGenerator,
    {
        // We instantiate a buffer
        let mut messages = PlaintextList::from_container(vec![
            <Self as AsMutTensor>::Element::ZERO;
            self.decomp_level_count.0
                * self.output_polynomial_size.0
        ]);

        // We retrieve decomposition arguments
        let decomp_level_count = self.decomp_level_count;
        let decomp_base_log = self.decomp_base_log;
        let polynomial_size = self.output_polynomial_size;

        // loop over the before key blocks
        for (input_key_bit, keyswitch_key_block) in input_lwe_key
            .as_tensor()
            .iter()
            .zip(self.bit_decomp_iter_mut())
        {
            // We reset the buffer
            messages
                .as_mut_tensor()
                .fill_with_element(<Self as AsMutTensor>::Element::ZERO);

            // We fill the buffer with the powers of the key bits
            for (level, mut message) in (1..=decomp_level_count.0)
                .map(DecompositionLevel)
                .zip(messages.sublist_iter_mut(PlaintextCount(polynomial_size.0)))
            {
                *message.as_mut_tensor().first_mut() =
                    DecompositionTerm::new(level, decomp_base_log, *input_key_bit)
                        .to_recomposition_summand();
            }

            // We encrypt the buffer
            output_glwe_key.encrypt_glwe_list(
                &mut keyswitch_key_block.into_glwe_list(),
                &messages,
                noise_parameters,
                generator,
            );
        }
    }

    /// Iterates over borrowed `LweKeyBitDecomposition` elements.
    ///
    /// One `LweKeyBitDecomposition` being a set of LWE ciphertexts, encrypting under the output
    /// key, the $l$ levels of the signed decomposition of a single bit of the input key.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LwePackingKeyswitchKey};
    /// use concrete_core::backends::default::private::math::decomposition::{DecompositionLevelCount, DecompositionBaseLog};
    /// let pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(15),
    ///     LweDimension(20)
    /// );
    /// for decomp in pksk.bit_decomp_iter() {
    ///     assert_eq!(decomp.lwe_size(), pksk.lwe_size());
    ///     assert_eq!(decomp.count().0, 10);
    /// }
    /// assert_eq!(pksk.bit_decomp_iter().count(), 15);
    /// ```
    pub(crate) fn bit_decomp_iter(
        &self,
    ) -> impl Iterator<Item = LweKeyBitDecomposition<&[<Self as AsRefTensor>::Element]>>
    where
        Self: AsRefTensor,
    {
        ck_dim_div!(self.as_tensor().len() => self.output_glwe_size.0 * self.output_polynomial_size.0, self.decomp_level_count.0);
        let size =
            self.decomp_level_count.0 * self.output_glwe_size.0 * self.output_polynomial_size.0;
        let glwe_size = self.output_glwe_size;
        let poly_size = self.output_polynomial_size;
        self.as_tensor().subtensor_iter(size).map(move |sub| {
            LweKeyBitDecomposition::from_container(sub.into_container(), glwe_size, poly_size)
        })
    }

    /// Iterates over mutably borrowed `LweKeyBitDecomposition` elements.
    ///
    /// One `LweKeyBitDecomposition` being a set of LWE ciphertexts, encrypting under the output
    /// key, the $l$ levels of the signed decomposition of a single bit of the input key.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LwePackingKeyswitchKey};
    /// use concrete_core::backends::default::private::math::tensor::{AsRefTensor, AsMutTensor};
    /// use concrete_core::backends::default::private::math::decomposition::{DecompositionLevelCount, DecompositionBaseLog};
    /// let mut pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(15),
    ///     LweDimension(20)
    /// );
    /// for mut decomp in pksk.bit_decomp_iter_mut() {
    ///     for mut ciphertext in decomp.ciphertext_iter_mut() {
    ///         ciphertext.as_mut_tensor().fill_with_element(0);
    ///     }
    /// }
    /// assert!(pksk.as_tensor().iter().all(|a| *a == 0));
    /// assert_eq!(pksk.bit_decomp_iter_mut().count(), 15);
    /// ```
    pub(crate) fn bit_decomp_iter_mut(
        &mut self,
    ) -> impl Iterator<Item = LweKeyBitDecomposition<&mut [<Self as AsMutTensor>::Element]>>
    where
        Self: AsMutTensor,
    {
        ck_dim_div!(self.as_tensor().len() => self.output_glwe_size.0 * self.output_polynomial_size.0, self.decomp_level_count.0);
        let chunks_size =
            self.decomp_level_count.0 * self.output_glwe_size.0 * self.output_polynomial_size.0;
        let glwe_size = self.output_glwe_size;
        let poly_size = self.output_polynomial_size;
        self.as_mut_tensor()
            .subtensor_iter_mut(chunks_size)
            .map(move |sub| {
                LweKeyBitDecomposition::from_container(sub.into_container(), glwe_size, poly_size)
            })
    }

    /// Keyswitches a single LWE ciphertext into a GLWE
    ///
    /// # Example
    ///
    /// ```rust
    /// use concrete_core::commons::crypto::encoding::*;
    /// use concrete_core::commons::crypto::glwe::*;
    /// use concrete_core::commons::crypto::lwe::*;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LogStandardDev,
    ///     LweDimension, LweSize, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_size = LweDimension(1024);
    /// let output_size = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(8);
    /// let noise = LogStandardDev::from_log_standard_dev(-15.);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let input_key = LweSecretKey::generate_binary(input_size, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_size, polynomial_size, &mut secret_generator);
    ///
    /// let mut pksk = LwePackingKeyswitchKey::allocate(
    ///     0 as u64,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_size,
    ///     output_size,
    ///     polynomial_size,
    /// );
    /// pksk.fill_with_packing_keyswitch_key(&input_key, &output_key, noise, &mut encryption_generator);
    ///
    /// let plaintext: Plaintext<u64> = Plaintext(1432154329994324);
    /// let mut ciphertext = LweCiphertext::allocate(0. as u64, LweSize(1025));
    /// let mut switched_ciphertext =
    ///     GlweCiphertext::allocate(0. as u64, PolynomialSize(256), GlweSize(3));
    /// input_key.encrypt_lwe(
    ///     &mut ciphertext,
    ///     &plaintext,
    ///     noise,
    ///     &mut encryption_generator,
    /// );
    ///
    /// pksk.keyswitch_ciphertext(&mut switched_ciphertext, &ciphertext);
    ///
    /// let mut decrypted = PlaintextList::from_container(vec![0 as u64; 256]);
    /// output_key.decrypt_glwe(&mut decrypted, &switched_ciphertext);
    /// ```
    pub fn keyswitch_ciphertext<InCont, OutCont, Scalar>(
        &self,
        after: &mut GlweCiphertext<OutCont>,
        before: &LweCiphertext<InCont>,
    ) where
        Self: AsRefTensor<Element = Scalar>,
        GlweCiphertext<OutCont>: AsMutTensor<Element = Scalar>,
        LweCiphertext<InCont>: AsRefTensor<Element = Scalar>,
        Scalar: UnsignedTorus,
    {
        ck_dim_eq!(self.input_lwe_key_dimension().0 => before.lwe_size().to_lwe_dimension().0);
        ck_dim_eq!(self.output_glwe_key_dimension().0 => after.size().to_glwe_dimension().0);

        // We reset the output
        after.as_mut_tensor().fill_with(|| Scalar::ZERO);

        // We copy the body
        *after.get_mut_body().tensor.as_mut_tensor().first_mut() = before.get_body().0;

        // We instantiate a decomposer
        let decomposer = SignedDecomposer::new(self.decomp_base_log, self.decomp_level_count);

        // Loop over the number of levels:
        // We compute the multiplication of a ciphertext from the keyswitching key with a
        // piece of the decomposition and subtract it to the buffer
        for (block, input_lwe_mask) in self
            .bit_decomp_iter()
            .zip(before.get_mask().mask_element_iter())
        {
            // We decompose
            let mask_rounded = decomposer.closest_representable(*input_lwe_mask);
            let decomp = decomposer.decompose(mask_rounded);

            // Loop over the number of levels:
            // We compute the multiplication of a ciphertext from the keyswitching key with a
            // piece of the decomposition and subtract it to the buffer
            for (level_key_cipher, decomposed) in block
                .as_tensor()
                .subtensor_iter(self.output_glwe_size.0 * self.output_polynomial_size.0)
                .rev()
                .zip(decomp)
            {
                after
                    .as_mut_tensor()
                    .update_with_wrapping_sub_element_mul(&level_key_cipher, decomposed.value());
            }
        }
    }

    /// Packs several LweCiphertext into a single GlweCiphertext
    /// with a keyswitch technique
    pub fn packing_keyswitch<InCont, OutCont, Scalar>(
        &self,
        output: &mut GlweCiphertext<OutCont>,
        input: &LweList<InCont>,
    ) where
        Self: AsRefTensor<Element = Scalar>,
        LweList<InCont>: AsRefTensor<Element = Scalar>,
        GlweCiphertext<OutCont>: AsMutTensor<Element = Scalar>,
        OutCont: Clone,
        Scalar: UnsignedTorus,
    {
        debug_assert!(input.count().0 <= output.polynomial_size().0);
        output.as_mut_tensor().fill_with_element(Scalar::ZERO);
        let mut buffer = output.clone();
        // for each ciphertext, call mono_key_switch
        for (degree, input_cipher) in input.ciphertext_iter().enumerate() {
            self.keyswitch_ciphertext(&mut buffer, &input_cipher);
            buffer
                .as_mut_polynomial_list()
                .polynomial_iter_mut()
                .for_each(|mut poly| {
                    poly.update_with_wrapping_monic_monomial_mul(MonomialDegree(degree))
                });
            output
                .as_mut_tensor()
                .update_with_wrapping_add(buffer.as_tensor());
        }
    }
}

/// A private functional packing keyswitching key.
///
/// A private functional packing keyswitching key allows to pack several LWE ciphertexts
/// into a single GLWE ciphertext while performing a private function on each
#[cfg_attr(feature = "__commons_serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LwePrivateFunctionalPackingKeyswitchKey<Cont> {
    tensor: Tensor<Cont>,
    decomp_base_log: DecompositionBaseLog,
    decomp_level_count: DecompositionLevelCount,
    output_glwe_size: GlweSize,
    output_polynomial_size: PolynomialSize,
}

tensor_traits!(LwePrivateFunctionalPackingKeyswitchKey);

impl<Scalar> LwePrivateFunctionalPackingKeyswitchKey<Vec<Scalar>>
where
    Scalar: Copy,
{
    /// Allocates a private functional packing keyswitching key whose masks and bodies are all
    /// `value`.
    ///
    /// # Note
    ///
    /// This function does *not* generate a private functional packing keyswitching key , but
    /// merely allocates a container of the right size.
    /// See [`LwePrivateFunctionalPackingKeyswitchKey::fill_with_private_functional_keyswitch_key`]
    /// to fill the container with a proper functional keyswitching key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(
    ///     pfpksk.decomposition_level_count(),
    ///     DecompositionLevelCount(10)
    /// );
    /// assert_eq!(pfpksk.decomposition_base_log(), DecompositionBaseLog(16));
    /// assert_eq!(pfpksk.output_glwe_key_dimension(), GlweDimension(2));
    /// assert_eq!(pfpksk.input_lwe_key_dimension(), LweDimension(10));
    /// ```
    pub fn allocate(
        value: Scalar,
        decomp_size: DecompositionLevelCount,
        decomp_base_log: DecompositionBaseLog,
        input_dimension: LweDimension,
        output_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
    ) -> Self {
        LwePrivateFunctionalPackingKeyswitchKey {
            tensor: Tensor::from_container(vec![
                value;
                decomp_size.0
                    * output_dimension.to_glwe_size().0
                    * output_polynomial_size.0
                    * input_dimension.to_lwe_size().0
            ]),
            decomp_base_log,
            decomp_level_count: decomp_size,
            output_glwe_size: output_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }
}

impl<Cont> LwePrivateFunctionalPackingKeyswitchKey<Cont> {
    /// Creates a private functional packing keyswitching key from a container.
    ///
    /// # Notes
    ///
    /// This method does not create a private functional packing keyswitch key, but merely wraps
    /// the container in the proper type. It assumes that either the container already contains a
    /// proper functional keyswitching key, or that
    /// [`LwePrivateFunctionalPackingKeyswitchKey::fill_with_private_functional_keyswitch_key`] will
    /// be called right after.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// let input_lwe_dim = LweDimension(200);
    /// let output_glwe_dim = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(7);
    /// let decomp_level_count = DecompositionLevelCount(4);
    ///
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::from_container(
    ///     vec![
    ///         0 as u8;
    ///         input_lwe_dim.to_lwe_size().0
    ///             * output_glwe_dim.to_glwe_size().0
    ///             * polynomial_size.0
    ///             * decomp_level_count.0
    ///     ],
    ///     decomp_base_log,
    ///     decomp_level_count,
    ///     output_glwe_dim,
    ///     polynomial_size,
    /// );
    ///
    /// assert_eq!(pfpksk.decomposition_level_count(), decomp_level_count);
    /// assert_eq!(pfpksk.decomposition_base_log(), decomp_base_log);
    /// assert_eq!(pfpksk.output_glwe_key_dimension(), output_glwe_dim);
    /// assert_eq!(pfpksk.input_lwe_key_dimension(), input_lwe_dim);
    /// ```
    pub fn from_container(
        cont: Cont,
        decomp_base_log: DecompositionBaseLog,
        decomp_size: DecompositionLevelCount,
        output_glwe_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
    ) -> LwePrivateFunctionalPackingKeyswitchKey<Cont>
    where
        Cont: AsRefSlice,
    {
        let tensor = Tensor::from_container(cont);
        ck_dim_div!(tensor.len() => output_glwe_dimension.to_glwe_size().0 * output_polynomial_size.0, decomp_size.0);
        LwePrivateFunctionalPackingKeyswitchKey {
            tensor,
            decomp_base_log,
            decomp_level_count: decomp_size,
            output_glwe_size: output_glwe_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }

    /// Returns the dimension of the output GLWE key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pfpksk.output_glwe_key_dimension(), GlweDimension(2));
    /// ```
    pub fn output_glwe_key_dimension(&self) -> GlweDimension {
        self.output_glwe_size.to_glwe_dimension()
    }

    /// Returns the size of the polynomials composing the GLWE ciphertext
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, LweSize,
    ///     PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pfpksk.output_polynomial_size(), PolynomialSize(256));
    /// ```
    pub fn output_polynomial_size(&self) -> PolynomialSize {
        self.output_polynomial_size
    }

    /// Returns the dimension of the input LWE key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pfpksk.input_lwe_key_dimension(), LweDimension(10));
    /// ```
    pub fn input_lwe_key_dimension(&self) -> LweDimension
    where
        Self: AsRefTensor,
    {
        LweDimension(
            self.as_tensor().len()
                / (self.output_glwe_size.0
                    * self.output_polynomial_size.0
                    * self.decomp_level_count.0)
                - 1,
        )
    }

    /// Returns the number of levels used for the decomposition of the input key bits.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(
    ///     pfpksk.decomposition_level_count(),
    ///     DecompositionLevelCount(10)
    /// );
    /// ```
    pub fn decomposition_level_count(&self) -> DecompositionLevelCount
    where
        Self: AsRefTensor,
    {
        self.decomp_level_count
    }

    /// Returns the logarithm of the base used for the decomposition of the input key bits.
    ///
    /// Indeed, the basis used is always of the form $2^b$. This function returns $b$.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
    /// };
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(10),
    ///     GlweDimension(2),
    ///     PolynomialSize(256),
    /// );
    /// assert_eq!(pfpksk.decomposition_base_log(), DecompositionBaseLog(16));
    /// ```
    pub fn decomposition_base_log(&self) -> DecompositionBaseLog
    where
        Self: AsRefTensor,
    {
        self.decomp_base_log
    }

    /// Fills the current private functional keyswitch key container with an actual private
    /// functional keyswitching key constructed from an input and an output key.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::polynomial::Polynomial;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LogStandardDev, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_size = LweDimension(10);
    /// let output_size = GlweDimension(3);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(5);
    /// let cipher_size = LweSize(55);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let noise = LogStandardDev::from_log_standard_dev(-15.);
    ///
    /// let input_key = LweSecretKey::generate_binary(input_size, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_size, polynomial_size, &mut secret_generator);
    ///
    /// let mut pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u32,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_size,
    ///     output_size,
    ///     polynomial_size,
    /// );
    /// pfpksk.fill_with_private_functional_packing_keyswitch_key(
    ///     &input_key,
    ///     &output_key,
    ///     noise,
    ///     &mut encryption_generator,
    ///     &|x| x,
    ///     &Polynomial::allocate(1 as u32, output_key.polynomial_size()),
    /// );
    ///
    /// assert!(!pfpksk.as_tensor().iter().all(|a| *a == 0));
    /// ```
    pub fn fill_with_private_functional_packing_keyswitch_key<
        InKeyCont,
        OutKeyCont,
        PolyCont,
        Scalar,
        Gen,
        ScalarFunc,
    >(
        &mut self,
        input_lwe_key: &LweSecretKey<BinaryKeyKind, InKeyCont>,
        output_glwe_key: &GlweSecretKey<BinaryKeyKind, OutKeyCont>,
        noise_parameters: impl DispersionParameter,
        generator: &mut EncryptionRandomGenerator<Gen>,
        f: &ScalarFunc,
        polynomial: &Polynomial<PolyCont>,
    ) where
        Self: AsMutTensor<Element = Scalar>,
        LweSecretKey<BinaryKeyKind, InKeyCont>: AsRefTensor<Element = Scalar>,
        GlweSecretKey<BinaryKeyKind, OutKeyCont>: AsRefTensor<Element = Scalar>,
        Polynomial<PolyCont>: AsRefTensor<Element = Scalar>,
        Scalar: UnsignedTorus,
        Gen: ByteRandomGenerator,
        ScalarFunc: Fn(Scalar) -> Scalar + ?Sized,
    {
        // We instantiate a buffer
        let mut messages = PlaintextList::from_container(vec![
            <Self as AsMutTensor>::Element::ZERO;
            self.decomp_level_count.0
                * self.output_polynomial_size.0
        ]);
        // We retrieve decomposition arguments
        let decomp_level_count = self.decomp_level_count;
        let decomp_base_log = self.decomp_base_log;
        let polynomial_size = self.output_polynomial_size;

        let last_key_iter_bit = [Scalar::MAX];
        // add minus one for the function which will be applied to the decomposed body
        // ( Scalar::MAX = -Scalar::ONE )
        let input_key_bit_iter = input_lwe_key
            .as_tensor()
            .as_slice()
            .iter()
            .chain(last_key_iter_bit.iter());

        let gen_iter = generator
            .fork_pfpksk_to_pfpksk_chunks::<Scalar>(
                decomp_level_count,
                output_glwe_key.key_size().to_glwe_size(),
                output_glwe_key.polynomial_size(),
                input_lwe_key.key_size().to_lwe_size(),
            )
            .unwrap();

        // loop over the before key blocks
        for ((&input_key_bit, keyswitch_key_block), mut loop_generator) in input_key_bit_iter
            .zip(self.bit_decomp_iter_mut())
            .zip(gen_iter)
        {
            // We reset the buffer
            messages
                .as_mut_tensor()
                .fill_with_element(<Self as AsMutTensor>::Element::ZERO);

            // We fill the buffer with the powers of the key bits
            for (level, mut message) in (1..=decomp_level_count.0)
                .map(DecompositionLevel)
                .zip(messages.sublist_iter_mut(PlaintextCount(polynomial_size.0)))
            {
                message
                    .as_mut_tensor()
                    .update_with_wrapping_add_element_mul(
                        polynomial.as_tensor(),
                        DecompositionTerm::new(
                            level,
                            decomp_base_log,
                            f(Scalar::ONE).wrapping_mul(input_key_bit),
                        )
                        .to_recomposition_summand(),
                    );
            }

            // We encrypt the buffer
            output_glwe_key.encrypt_glwe_list(
                &mut keyswitch_key_block.into_glwe_list(),
                &messages,
                noise_parameters,
                &mut loop_generator,
            );
        }
    }

    /// Fills the current private functional keyswitch key container with an actual private
    /// functional keyswitching key constructed from an input and an output key using multiple
    /// threads .
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKey;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::polynomial::Polynomial;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LogStandardDev, LweDimension,
    ///     LweSize, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_size = LweDimension(10);
    /// let output_size = GlweDimension(3);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(5);
    /// let cipher_size = LweSize(55);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let noise = LogStandardDev::from_log_standard_dev(-15.);
    ///
    /// let input_key = LweSecretKey::generate_binary(input_size, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_size, polynomial_size, &mut secret_generator);
    ///
    /// let mut pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u32,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_size,
    ///     output_size,
    ///     polynomial_size,
    /// );
    /// pfpksk.par_fill_with_private_functional_packing_keyswitch_key(
    ///     &input_key,
    ///     &output_key,
    ///     noise,
    ///     &mut encryption_generator,
    ///     &|x| x,
    ///     &Polynomial::allocate(1 as u32, output_key.polynomial_size()),
    /// );
    ///
    /// assert!(!pfpksk.as_tensor().iter().all(|a| *a == 0));
    /// ```
    #[cfg(feature = "__commons_parallel")]
    pub fn par_fill_with_private_functional_packing_keyswitch_key<
        InKeyCont,
        OutKeyCont,
        PolyCont,
        Scalar,
        Gen,
        Noise,
        ScalarFunc,
    >(
        &mut self,
        input_lwe_key: &LweSecretKey<BinaryKeyKind, InKeyCont>,
        output_glwe_key: &GlweSecretKey<BinaryKeyKind, OutKeyCont>,
        noise_parameters: Noise,
        generator: &mut EncryptionRandomGenerator<Gen>,
        f: &ScalarFunc,
        polynomial: &Polynomial<PolyCont>,
    ) where
        Self: AsMutTensor<Element = Scalar>,
        <Self as AsMutTensor>::Element: Sync + Send,
        LweSecretKey<BinaryKeyKind, InKeyCont>: AsRefTensor<Element = Scalar>,
        GlweSecretKey<BinaryKeyKind, OutKeyCont>: AsRefTensor<Element = Scalar>,
        OutKeyCont: AsRefSlice<Element = Scalar> + Sync + Send,
        Polynomial<PolyCont>: AsRefTensor<Element = Scalar>,
        PolyCont: AsRefSlice<Element = Scalar> + Sync + Send,
        Scalar: UnsignedTorus + Sync + Send,
        Gen: ParallelByteRandomGenerator,
        Noise: DispersionParameter + Sync + Send,
        ScalarFunc: Fn(Scalar) -> Scalar + Sync + Send + ?Sized,
        Cont: Sync + Send,
    {
        // We retrieve decomposition arguments
        let decomp_level_count = self.decomp_level_count;
        let decomp_base_log = self.decomp_base_log;
        let polynomial_size = self.output_polynomial_size;

        let last_key_iter_bit = [Scalar::MAX];
        // add minus one for the function which will be applied to the decomposed body
        // ( Scalar::MAX = -Scalar::ONE )
        let input_key_bit_iter = input_lwe_key
            .as_tensor()
            .as_slice()
            .par_iter()
            .chain(last_key_iter_bit.par_iter());

        let gen_iter = generator
            .par_fork_pfpksk_to_pfpksk_chunks::<Scalar>(
                decomp_level_count,
                output_glwe_key.key_size().to_glwe_size(),
                output_glwe_key.polynomial_size(),
                input_lwe_key.key_size().to_lwe_size(),
            )
            .unwrap();

        // loop over the before key blocks
        input_key_bit_iter
            .zip(self.par_bit_decomp_iter_mut())
            .zip(gen_iter)
            .for_each(
                move |((&input_key_bit, keyswitch_key_block), mut loop_generator)| {
                    // We instantiate a buffer
                    let mut messages = PlaintextList::from_container(vec![
                        <Self as AsMutTensor>::Element::ZERO;
                        decomp_level_count.0
                            * polynomial_size.0
                    ]);

                    // We fill the buffer with the powers of the key bits
                    for (level, mut message) in (1..=decomp_level_count.0)
                        .map(DecompositionLevel)
                        .zip(messages.sublist_iter_mut(PlaintextCount(polynomial_size.0)))
                    {
                        message
                            .as_mut_tensor()
                            .update_with_wrapping_add_element_mul(
                                polynomial.as_tensor(),
                                DecompositionTerm::new(
                                    level,
                                    decomp_base_log,
                                    f(Scalar::ONE).wrapping_mul(input_key_bit),
                                )
                                .to_recomposition_summand(),
                            );
                    }

                    // We encrypt the buffer
                    output_glwe_key.encrypt_glwe_list(
                        &mut keyswitch_key_block.into_glwe_list(),
                        &messages,
                        noise_parameters,
                        &mut loop_generator,
                    );
                },
            )
    }

    /// Iterates over borrowed `LweKeyBitDecomposition` elements.
    ///
    /// One `LweKeyBitDecomposition` being a set of LWE ciphertexts, encrypting under the output
    /// key, the $l$ levels of the signed decomposition of a single bit of the input key.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use concrete_core::commons::crypto::{*, glwe::LwePrivateFunctionalPackingKeyswitchKey};
    /// use concrete_core::prelude::{DecompositionLevelCount, DecompositionBaseLog,
    /// GlweDimension, LweDimension, PolynomialSize};
    /// let pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(15),
    ///     GlweDimension(20),
    ///     PolynomialSize(256)
    /// );
    /// for decomp in pfpksk.bit_decomp_iter() {
    ///     assert_eq!(decomp.glwe_size(), pfpksk.output_glwe_size());
    ///     assert_eq!(decomp.count().0, 10);
    /// }
    /// assert_eq!(pfpksk.bit_decomp_iter().count(), 15 + 1);
    /// ```
    pub(crate) fn bit_decomp_iter(
        &self,
    ) -> impl Iterator<Item = LweKeyBitDecomposition<&[<Self as AsRefTensor>::Element]>>
    where
        Self: AsRefTensor,
    {
        ck_dim_div!(self.as_tensor().len() => self.output_glwe_size.0 * self.output_polynomial_size.0, self.decomp_level_count.0);
        let size =
            self.decomp_level_count.0 * self.output_glwe_size.0 * self.output_polynomial_size.0;
        let glwe_size = self.output_glwe_size;
        let poly_size = self.output_polynomial_size;
        self.as_tensor().subtensor_iter(size).map(move |sub| {
            LweKeyBitDecomposition::from_container(sub.into_container(), glwe_size, poly_size)
        })
    }

    /// Iterates over mutably borrowed `LweKeyBitDecomposition` elements.
    ///
    /// One `LweKeyBitDecomposition` being a set of LWE ciphertexts, encrypting under the output
    /// key, the $l$ levels of the signed decomposition of a single bit of the input key.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use concrete_core::commons::crypto::{*, glwe::LwePrivateFunctionalPackingKeyswitchKey};
    /// use concrete_core::commons::math::tensor::{AsRefTensor, AsMutTensor};
    /// use concrete_core::prelude::{DecompositionLevelCount, DecompositionBaseLog,
    /// GlweDimension, LweDimension, PolynomialSize};
    /// let mut pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u8,
    ///     DecompositionLevelCount(10),
    ///     DecompositionBaseLog(16),
    ///     LweDimension(15),
    ///     GlweDimension(20),
    ///     PolynomialSize(256)
    /// );
    /// for mut decomp in pfpksk.bit_decomp_iter_mut() {
    ///     for mut ciphertext in decomp.ciphertext_iter_mut() {
    ///         ciphertext.as_mut_tensor().fill_with_element(0);
    ///     }
    /// }
    /// assert!(pfpksk.as_tensor().iter().all(|a| *a == 0));
    /// assert_eq!(pfpksk.bit_decomp_iter_mut().count(), 15 + 1);
    /// ```
    pub(crate) fn bit_decomp_iter_mut(
        &mut self,
    ) -> impl Iterator<Item = LweKeyBitDecomposition<&mut [<Self as AsMutTensor>::Element]>>
    where
        Self: AsMutTensor,
    {
        ck_dim_div!(self.as_tensor().len() => self.output_glwe_size.0 * self.output_polynomial_size.0, self.decomp_level_count.0);
        let chunks_size =
            self.decomp_level_count.0 * self.output_glwe_size.0 * self.output_polynomial_size.0;
        let glwe_size = self.output_glwe_size;
        let poly_size = self.output_polynomial_size;
        self.as_mut_tensor()
            .subtensor_iter_mut(chunks_size)
            .map(move |sub| {
                LweKeyBitDecomposition::from_container(sub.into_container(), glwe_size, poly_size)
            })
    }

    #[cfg(feature = "__commons_parallel")]
    pub(crate) fn par_bit_decomp_iter_mut(
        &mut self,
    ) -> impl IndexedParallelIterator<Item = LweKeyBitDecomposition<&mut [<Self as AsMutTensor>::Element]>>
    where
        Self: AsMutTensor,
        <Self as AsMutTensor>::Element: Sync + Send,
    {
        ck_dim_div!(self.as_tensor().len() => self.output_glwe_size.0 * self.output_polynomial_size.0, self.decomp_level_count.0);
        let chunks_size =
            self.decomp_level_count.0 * self.output_glwe_size.0 * self.output_polynomial_size.0;
        let glwe_size = self.output_glwe_size;
        let poly_size = self.output_polynomial_size;
        self.as_mut_tensor()
            .par_subtensor_iter_mut(chunks_size)
            .map(move |sub| {
                LweKeyBitDecomposition::from_container(sub.into_container(), glwe_size, poly_size)
            })
    }

    /// Keyswitches a single LWE ciphertext into a GLWE using a
    /// private functional packing keyswitch key
    ///
    /// # Example
    ///
    /// ```rust
    /// use concrete_core::commons::crypto::encoding::*;
    /// use concrete_core::commons::crypto::glwe::*;
    /// use concrete_core::commons::crypto::lwe::*;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::polynomial::Polynomial;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LogStandardDev,
    ///     LweDimension, LweSize, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_lwe_dim = LweDimension(1024);
    /// let output_glwe_dim = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(8);
    /// let noise = LogStandardDev::from_log_standard_dev(-60.);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let input_key = LweSecretKey::generate_binary(input_lwe_dim, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_glwe_dim, polynomial_size, &mut secret_generator);
    ///
    /// let mut pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u64,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_lwe_dim,
    ///     output_glwe_dim,
    ///     polynomial_size,
    /// );
    /// pfpksk.fill_with_private_functional_packing_keyswitch_key(
    ///     &input_key,
    ///     &output_key,
    ///     noise,
    ///     &mut encryption_generator,
    ///     &|x| x,
    ///     &Polynomial::allocate(1 as u64, polynomial_size),
    /// );
    ///
    /// let plaintext: Plaintext<u64> = Plaintext(5 << 60);
    /// let mut ciphertext = LweCiphertext::allocate(0. as u64, input_lwe_dim.to_lwe_size());
    /// let mut switched_ciphertext =
    ///     GlweCiphertext::allocate(0. as u64, polynomial_size, output_glwe_dim.to_glwe_size());
    /// input_key.encrypt_lwe(
    ///     &mut ciphertext,
    ///     &plaintext,
    ///     noise,
    ///     &mut encryption_generator,
    /// );
    ///
    /// pfpksk.private_functional_keyswitch_ciphertext(&mut switched_ciphertext, &ciphertext);
    ///
    /// let mut decrypted = PlaintextList::from_container(vec![0 as u64; polynomial_size.0]);
    /// output_key.decrypt_glwe(&mut decrypted, &switched_ciphertext);
    /// ```
    pub fn private_functional_keyswitch_ciphertext<InCont, OutCont, Scalar>(
        &self,
        after: &mut GlweCiphertext<OutCont>,
        before: &LweCiphertext<InCont>,
    ) where
        Self: AsRefTensor<Element = Scalar>,
        GlweCiphertext<OutCont>: AsMutTensor<Element = Scalar>,
        LweCiphertext<InCont>: AsRefTensor<Element = Scalar>,
        Scalar: UnsignedTorus,
    {
        ck_dim_eq!(self.input_lwe_key_dimension().0  => before.lwe_size().to_lwe_dimension().0 );
        ck_dim_eq!(self.output_glwe_key_dimension().0 => after.size().to_glwe_dimension().0);

        // We reset the output
        after.as_mut_tensor().fill_with(|| Scalar::ZERO);

        // We instantiate a decomposer
        let decomposer = SignedDecomposer::new(self.decomp_base_log, self.decomp_level_count);

        for (block, input_lwe) in self.bit_decomp_iter().zip(before.as_tensor().iter()) {
            // We decompose
            let rounded = decomposer.closest_representable(*input_lwe);
            let decomp = decomposer.decompose(rounded);

            // Loop over the number of levels:
            // We compute the multiplication of a ciphertext from the private functional
            // keyswitching key with a piece of the decomposition and subtract it to the buffer
            for (level_key_cipher, decomposed) in block
                .as_tensor()
                .subtensor_iter(self.output_glwe_size.0 * self.output_polynomial_size.0)
                .rev()
                .zip(decomp)
            {
                after
                    .as_mut_tensor()
                    .update_with_wrapping_sub_element_mul(&level_key_cipher, decomposed.value());
            }
        }
    }

    /// Packs several LweCiphertext into a single GlweCiphertext
    /// with a private functional keyswitch technique
    ///
    /// # Example
    ///
    /// ```rust
    /// use concrete_core::commons::crypto::encoding::*;
    /// use concrete_core::commons::crypto::glwe::*;
    /// use concrete_core::commons::crypto::lwe::*;
    /// use concrete_core::commons::crypto::secret::generators::{
    ///     EncryptionRandomGenerator, SecretRandomGenerator,
    /// };
    /// use concrete_core::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::commons::math::polynomial::Polynomial;
    /// use concrete_core::commons::math::tensor::AsRefTensor;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, GlweDimension, GlweSize, LogStandardDev,
    ///     LweDimension, LweSize, PlaintextCount, PolynomialSize,
    /// };
    /// use concrete_csprng::generators::SoftwareRandomGenerator;
    /// use concrete_csprng::seeders::{Seed, UnixSeeder};
    ///
    /// let input_lwe_dim = LweDimension(1024);
    /// let output_glwe_dim = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(3);
    /// let decomp_level_count = DecompositionLevelCount(8);
    /// let noise = LogStandardDev::from_log_standard_dev(-60.);
    /// let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
    /// let mut encryption_generator =
    ///     EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0), &mut UnixSeeder::new(0));
    /// let input_key = LweSecretKey::generate_binary(input_lwe_dim, &mut secret_generator);
    /// let output_key =
    ///     GlweSecretKey::generate_binary(output_glwe_dim, polynomial_size, &mut secret_generator);
    ///
    /// let mut pfpksk = LwePrivateFunctionalPackingKeyswitchKey::allocate(
    ///     0 as u64,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_lwe_dim,
    ///     output_glwe_dim,
    ///     polynomial_size,
    /// );
    /// let mut vec = vec![0u64; polynomial_size.0];
    /// vec[0] = 1;
    ///
    /// pfpksk.fill_with_private_functional_packing_keyswitch_key(
    ///     &input_key,
    ///     &output_key,
    ///     noise,
    ///     &mut encryption_generator,
    ///     &|x| x,
    ///     &Polynomial::from_container(vec),
    /// );
    ///
    /// let plaintext_list = PlaintextList::allocate(1 << 60 as u64, PlaintextCount(10));
    /// let ciphertext_list =
    ///     LweList::new_trivial_encryption(input_key.key_size().to_lwe_size(), &plaintext_list);
    /// let mut switched_ciphertext =
    ///     GlweCiphertext::allocate(0 as u64, polynomial_size, output_glwe_dim.to_glwe_size());
    ///
    /// pfpksk.private_functional_packing_keyswitch(&mut switched_ciphertext, &ciphertext_list);
    ///
    /// let mut decrypted = PlaintextList::from_container(vec![0 as u64; polynomial_size.0]);
    /// output_key.decrypt_glwe(&mut decrypted, &switched_ciphertext);
    /// ```
    pub fn private_functional_packing_keyswitch<InCont, OutCont, Scalar>(
        &self,
        output: &mut GlweCiphertext<OutCont>,
        input: &LweList<InCont>,
    ) where
        Self: AsRefTensor<Element = Scalar>,
        LweList<InCont>: AsRefTensor<Element = Scalar>,
        GlweCiphertext<OutCont>: AsMutTensor<Element = Scalar>,
        OutCont: Clone,
        Scalar: UnsignedTorus,
    {
        debug_assert!(input.count().0 <= output.polynomial_size().0);
        output.as_mut_tensor().fill_with_element(Scalar::ZERO);
        let mut buffer = output.clone();
        // for each ciphertext, call mono_key_switch
        for (degree, input_cipher) in input.ciphertext_iter().enumerate() {
            self.private_functional_keyswitch_ciphertext(&mut buffer, &input_cipher);
            buffer
                .as_mut_polynomial_list()
                .polynomial_iter_mut()
                .for_each(|mut poly| {
                    poly.update_with_wrapping_monic_monomial_mul(MonomialDegree(degree))
                });
            output
                .as_mut_tensor()
                .update_with_wrapping_add(buffer.as_tensor());
        }
    }
}

/// The encryption of a single bit of the output key.
#[cfg_attr(feature = "__commons_serialization", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq)]
pub(crate) struct LweKeyBitDecomposition<Cont> {
    pub(crate) tensor: Tensor<Cont>,
    pub(crate) glwe_size: GlweSize,
    pub(crate) poly_size: PolynomialSize,
}

tensor_traits!(LweKeyBitDecomposition);

impl<Cont> LweKeyBitDecomposition<Cont> {
    /// Creates a key bit decomposition from a container.
    ///
    /// # Notes
    ///
    /// This method does not decompose a key bit in a basis, but merely wraps a container in the
    /// right structure. See [`LwePackingKeyswitchKey::bit_decomp_iter`] for an iterator that
    /// returns key bit decompositions.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 1500], GlweSize(10),
    /// PolynomialSize(10);
    /// assert_eq!(kbd.count(), CiphertextCount(15));
    /// assert_eq!(kbd.glwe_size(), GlweSize(10));
    /// assert_eq!(kbd.polynomial_size(), PolynomialSize(10));
    /// ```
    pub fn from_container(cont: Cont, glwe_size: GlweSize, poly_size: PolynomialSize) -> Self
    where
        Tensor<Cont>: AsRefSlice,
    {
        LweKeyBitDecomposition {
            tensor: Tensor::from_container(cont),
            glwe_size,
            poly_size,
        }
    }

    /// Returns the size of the GLWE ciphertexts encoding each level of the key bit decomposition.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// assert_eq!(kbd.lwe_size(), LweSize(10));
    /// ```
    #[allow(dead_code)]
    pub fn glwe_size(&self) -> GlweSize {
        self.glwe_size
    }

    /// Returns the size of the lwe ciphertexts encoding each level of the key bit decomposition.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// assert_eq!(kbd.lwe_size(), LweSize(10));
    /// ```
    #[allow(dead_code)]
    pub fn polynomial_size(&self) -> PolynomialSize {
        self.poly_size
    }

    /// Returns the number of ciphertexts in the decomposition.
    ///
    /// Note that this is actually equals to the number of levels in the decomposition.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// assert_eq!(kbd.count(), CiphertextCount(15));
    /// ```
    #[allow(dead_code)]
    pub fn count(&self) -> CiphertextCount
    where
        Self: AsRefTensor,
    {
        ck_dim_div!(self.as_tensor().len() => self.glwe_size.0 * self.poly_size.0);
        CiphertextCount(self.as_tensor().len() / (self.glwe_size.0 * self.poly_size.0))
    }

    /// Returns an iterator over borrowed `GlweCiphertext`.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// for ciphertext in kbd.ciphertext_iter(){
    ///     assert_eq!(ciphertext.lwe_size(), LweSize(10));
    /// }
    /// assert_eq!(kbd.ciphertext_iter().count(), 15);
    /// ```
    #[allow(dead_code)]
    pub fn ciphertext_iter(
        &self,
    ) -> impl Iterator<Item = GlweCiphertext<&[<Self as AsRefTensor>::Element]>>
    where
        Self: AsRefTensor,
    {
        self.as_tensor()
            .subtensor_iter(self.glwe_size.0 * self.poly_size.0)
            .map(move |sub| GlweCiphertext::from_container(sub.into_container(), self.poly_size))
    }

    /// Returns an iterator over mutably borrowed `GlweCiphertext`.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// use concrete_core::backends::default::private::math::tensor::{AsRefTensor, AsMutTensor};
    /// let mut kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// for mut ciphertext in kbd.ciphertext_iter_mut(){
    ///     ciphertext.as_mut_tensor().fill_with_element(9);
    /// }
    /// assert!(kbd.as_tensor().iter().all(|a| *a == 9));
    /// assert_eq!(kbd.ciphertext_iter().count(), 15);
    /// ```
    #[allow(dead_code)]
    pub fn ciphertext_iter_mut(
        &mut self,
    ) -> impl Iterator<Item = GlweCiphertext<&mut [<Self as AsMutTensor>::Element]>>
    where
        Self: AsMutTensor,
    {
        let chunks_size = self.glwe_size.0 * self.poly_size.0;
        let poly_size = self.poly_size;
        self.as_mut_tensor()
            .subtensor_iter_mut(chunks_size)
            .map(move |sub| GlweCiphertext::from_container(sub.into_container(), poly_size))
    }

    /// Consumes the current key bit decomposition and returns a GLWE.
    ///
    /// Note that this operation is super cheap, as it merely rewraps the current container in
    /// a GLWE structure.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use concrete_core::backends::default::private::crypto::{*, glwe::LweKeyBitDecomposition};
    /// let kbd = LweKeyBitDecomposition::from_container(vec![0 as u8; 150], LweSize(10));
    /// let glwe = kbd.into_glwe_list();
    /// assert_eq!(list.count(), CiphertextCount(15));
    /// assert_eq!(list.lwe_size(), LweSize(10));
    /// ```
    pub fn into_glwe_list(self) -> GlweList<Cont> {
        GlweList {
            tensor: self.tensor,
            rlwe_size: self.glwe_size,
            poly_size: self.poly_size,
        }
    }
}

/// A private functional packing keyswitching key list.
#[cfg_attr(feature = "__commons_serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LwePrivateFunctionalPackingKeyswitchKeyList<Cont> {
    tensor: Tensor<Cont>,
    decomp_base_log: DecompositionBaseLog,
    decomp_level_count: DecompositionLevelCount,
    input_lwe_size: LweSize,
    output_glwe_size: GlweSize,
    output_polynomial_size: PolynomialSize,
}

tensor_traits!(LwePrivateFunctionalPackingKeyswitchKeyList);

impl<Scalar> LwePrivateFunctionalPackingKeyswitchKeyList<Vec<Scalar>>
where
    Scalar: Copy,
{
    /// Allocates storage for an owned [`LwePrivateFunctionalPackingKeyswitchKeyList`].
    ///
    /// # Note
    ///
    /// This function does *not* generate a private functional packing keyswitch key list, but
    /// merely allocates a container of the right size.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKeyList;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, FunctionalPackingKeyswitchKeyCount,
    ///     GlweDimension, GlweSize, LweDimension, LweSize, PolynomialSize,
    /// };
    /// let input_lwe_dim = LweDimension(200);
    /// let output_glwe_dim = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(7);
    /// let decomp_level_count = DecompositionLevelCount(4);
    /// let fpksk_count = FunctionalPackingKeyswitchKeyCount(3);
    ///
    /// let pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyList::allocate(
    ///     0u8,
    ///     decomp_level_count,
    ///     decomp_base_log,
    ///     input_lwe_dim,
    ///     output_glwe_dim,
    ///     polynomial_size,
    ///     fpksk_count,
    /// );
    ///
    /// assert_eq!(pfpksk_list.decomposition_level_count(), decomp_level_count);
    /// assert_eq!(pfpksk_list.decomposition_base_log(), decomp_base_log);
    /// assert_eq!(pfpksk_list.output_glwe_key_dimension(), output_glwe_dim);
    /// assert_eq!(pfpksk_list.input_lwe_key_dimension(), input_lwe_dim);
    /// assert_eq!(pfpksk_list.fpksk_count(), fpksk_count);
    /// ```
    pub fn allocate(
        value: Scalar,
        decomp_size: DecompositionLevelCount,
        decomp_base_log: DecompositionBaseLog,
        input_dimension: LweDimension,
        output_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
        fpksk_count: FunctionalPackingKeyswitchKeyCount,
    ) -> Self {
        LwePrivateFunctionalPackingKeyswitchKeyList {
            tensor: Tensor::from_container(vec![
                value;
                decomp_size.0
                    * output_dimension.to_glwe_size().0
                    * output_polynomial_size.0
                    * input_dimension.to_lwe_size().0
                    * fpksk_count.0
            ]),
            decomp_base_log,
            decomp_level_count: decomp_size,
            input_lwe_size: input_dimension.to_lwe_size(),
            output_glwe_size: output_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }
}

impl<Cont> LwePrivateFunctionalPackingKeyswitchKeyList<Cont> {
    /// Creates a list from a container of values.
    ///
    /// # Notes
    ///
    /// This method does not create a private functional packing keyswitch key list, but merely
    /// wraps the container in the proper type. It assumes that either the container already
    /// contains a proper functional keyswitching key list, or that it will be filled right after.
    ///
    /// # Example
    ///
    /// ```
    /// use concrete_core::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKeyList;
    /// use concrete_core::commons::crypto::*;
    /// use concrete_core::prelude::{
    ///     DecompositionBaseLog, DecompositionLevelCount, FunctionalPackingKeyswitchKeyCount,
    ///     GlweDimension, GlweSize, LweDimension, LweSize, PolynomialSize,
    /// };
    /// let input_lwe_dim = LweDimension(200);
    /// let output_glwe_dim = GlweDimension(2);
    /// let polynomial_size = PolynomialSize(256);
    /// let decomp_base_log = DecompositionBaseLog(7);
    /// let decomp_level_count = DecompositionLevelCount(4);
    /// let fpksk_count = FunctionalPackingKeyswitchKeyCount(3);
    ///
    /// let pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyList::from_container(
    ///     vec![
    ///         0 as u8;
    ///         input_lwe_dim.to_lwe_size().0
    ///             * output_glwe_dim.to_glwe_size().0
    ///             * polynomial_size.0
    ///             * decomp_level_count.0
    ///             * fpksk_count.0
    ///     ],
    ///     decomp_base_log,
    ///     decomp_level_count,
    ///     input_lwe_dim,
    ///     output_glwe_dim,
    ///     polynomial_size,
    ///     fpksk_count,
    /// );
    ///
    /// assert_eq!(pfpksk_list.decomposition_level_count(), decomp_level_count);
    /// assert_eq!(pfpksk_list.decomposition_base_log(), decomp_base_log);
    /// assert_eq!(pfpksk_list.output_glwe_key_dimension(), output_glwe_dim);
    /// assert_eq!(pfpksk_list.input_lwe_key_dimension(), input_lwe_dim);
    /// assert_eq!(pfpksk_list.fpksk_count(), fpksk_count);
    /// ```
    pub fn from_container(
        cont: Cont,
        decomp_base_log: DecompositionBaseLog,
        decomp_size: DecompositionLevelCount,
        input_dimension: LweDimension,
        output_glwe_dimension: GlweDimension,
        output_polynomial_size: PolynomialSize,
        fpksk_count: FunctionalPackingKeyswitchKeyCount,
    ) -> LwePrivateFunctionalPackingKeyswitchKeyList<Cont>
    where
        Cont: AsRefSlice,
    {
        let tensor = Tensor::from_container(cont);
        ck_dim_div!(tensor.len() =>
            output_glwe_dimension.to_glwe_size().0 * output_polynomial_size.0,
            decomp_size.0,
            input_dimension.to_lwe_size().0,
            fpksk_count.0);
        LwePrivateFunctionalPackingKeyswitchKeyList {
            tensor,
            decomp_base_log,
            decomp_level_count: decomp_size,
            input_lwe_size: input_dimension.to_lwe_size(),
            output_glwe_size: output_glwe_dimension.to_glwe_size(),
            output_polynomial_size,
        }
    }

    pub fn into_container(self) -> Cont {
        self.tensor.into_container()
    }

    pub fn as_view(&self) -> LwePrivateFunctionalPackingKeyswitchKeyList<&'_ [Cont::Element]>
    where
        Cont: Container,
    {
        LwePrivateFunctionalPackingKeyswitchKeyList {
            tensor: Tensor::from_container(self.tensor.as_container().as_ref()),
            decomp_base_log: self.decomp_base_log,
            decomp_level_count: self.decomp_level_count,
            input_lwe_size: self.input_lwe_size,
            output_glwe_size: self.output_glwe_size,
            output_polynomial_size: self.output_polynomial_size,
        }
    }

    pub fn as_mut_view(
        &mut self,
    ) -> LwePrivateFunctionalPackingKeyswitchKeyList<&'_ mut [Cont::Element]>
    where
        Cont: Container + AsMut<[Cont::Element]>,
    {
        LwePrivateFunctionalPackingKeyswitchKeyList {
            tensor: Tensor::from_container(self.tensor.as_mut_container().as_mut()),
            decomp_base_log: self.decomp_base_log,
            decomp_level_count: self.decomp_level_count,
            input_lwe_size: self.input_lwe_size,
            output_glwe_size: self.output_glwe_size,
            output_polynomial_size: self.output_polynomial_size,
        }
    }

    /// Returns the dimension of the output GLWE key.
    pub fn output_glwe_key_dimension(&self) -> GlweDimension {
        self.output_glwe_size.to_glwe_dimension()
    }

    /// Returns the size of the polynomials composing the GLWE ciphertext
    pub fn output_polynomial_size(&self) -> PolynomialSize {
        self.output_polynomial_size
    }

    /// Returns the dimension of the input LWE key.
    pub fn input_lwe_key_dimension(&self) -> LweDimension {
        self.input_lwe_size.to_lwe_dimension()
    }

    /// Returns the number of levels used for the decomposition of the input key bits.
    pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
        self.decomp_level_count
    }

    /// Returns the logarithm of the base used for the decomposition of the input key bits.
    ///
    /// Indeed, the basis used is always of the form $2^b$. This function returns $b$.
    pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
        self.decomp_base_log
    }

    /// Returns the number of private functional packing keyswitch key in the list.
    pub fn fpksk_count(&self) -> FunctionalPackingKeyswitchKeyCount
    where
        Self: AsRefTensor,
    {
        let single_ksk_size = self.output_glwe_size.0
            * self.output_polynomial_size.0
            * self.decomp_level_count.0
            * self.input_lwe_size.0;
        ck_dim_div!(self.as_tensor().len() => single_ksk_size);
        FunctionalPackingKeyswitchKeyCount(self.as_tensor().len() / single_ksk_size)
    }

    /// Returns an iterator over keys borrowed from the list.
    pub fn fpksk_iter(
        &self,
    ) -> impl DoubleEndedIterator<
        Item = LwePrivateFunctionalPackingKeyswitchKey<&[<Self as AsRefTensor>::Element]>,
    >
    where
        Self: AsRefTensor,
    {
        let single_ksk_size = self.output_glwe_size.0
            * self.output_polynomial_size.0
            * self.decomp_level_count.0
            * self.input_lwe_size.0;
        ck_dim_div!(self.as_tensor().len() => single_ksk_size);
        self.as_tensor()
            .subtensor_iter(single_ksk_size)
            .map(move |sub| {
                LwePrivateFunctionalPackingKeyswitchKey::from_container(
                    sub.into_container(),
                    self.decomposition_base_log(),
                    self.decomposition_level_count(),
                    self.output_glwe_key_dimension(),
                    self.output_polynomial_size(),
                )
            })
    }

    /// Returns an iterator over keys borrowed from the list.
    pub fn fpksk_iter_mut(
        &mut self,
    ) -> impl DoubleEndedIterator<
        Item = LwePrivateFunctionalPackingKeyswitchKey<&mut [<Self as AsMutTensor>::Element]>,
    >
    where
        Self: AsMutTensor,
    {
        let single_ksk_size = self.output_glwe_size.0
            * self.output_polynomial_size.0
            * self.decomp_level_count.0
            * self.input_lwe_size.0;
        ck_dim_div!(self.as_mut_tensor().len() => single_ksk_size);

        let decomposition_base_log = self.decomposition_base_log();
        let decomposition_level_count = self.decomposition_level_count();
        let output_glwe_key_dimension = self.output_glwe_key_dimension();
        let output_polynomial_size = self.output_polynomial_size();

        self.as_mut_tensor()
            .subtensor_iter_mut(single_ksk_size)
            .map(move |sub| {
                LwePrivateFunctionalPackingKeyswitchKey::from_container(
                    sub.into_container(),
                    decomposition_base_log,
                    decomposition_level_count,
                    output_glwe_key_dimension,
                    output_polynomial_size,
                )
            })
    }

    /// Returns an iterator over keys borrowed from the list.
    #[cfg(feature = "__commons_parallel")]
    pub fn par_fpksk_iter_mut(
        &mut self,
    ) -> impl IndexedParallelIterator<
        Item = LwePrivateFunctionalPackingKeyswitchKey<&mut [<Self as AsMutTensor>::Element]>,
    >
    where
        Self: AsMutTensor,
        <Self as AsMutTensor>::Element: Sync + Send,
    {
        let single_ksk_size = self.output_glwe_size.0
            * self.output_polynomial_size.0
            * self.decomp_level_count.0
            * self.input_lwe_size.0;
        ck_dim_div!(self.as_mut_tensor().len() => single_ksk_size);

        let decomposition_base_log = self.decomposition_base_log();
        let decomposition_level_count = self.decomposition_level_count();
        let output_glwe_key_dimension = self.output_glwe_key_dimension();
        let output_polynomial_size = self.output_polynomial_size();

        self.as_mut_tensor()
            .par_subtensor_iter_mut(single_ksk_size)
            .map(move |sub| {
                LwePrivateFunctionalPackingKeyswitchKey::from_container(
                    sub.into_container(),
                    decomposition_base_log,
                    decomposition_level_count,
                    output_glwe_key_dimension,
                    output_polynomial_size,
                )
            })
    }

    pub fn fill_with_fpksk_for_circuit_bootstrap<Scalar, C1, C2, Gen>(
        &mut self,
        input_lwe_key: &LweSecretKey<BinaryKeyKind, C1>,
        output_glwe_key: &GlweSecretKey<BinaryKeyKind, C2>,
        noise_parameters: impl DispersionParameter,
        generator: &mut EncryptionRandomGenerator<Gen>,
    ) where
        Scalar: UnsignedTorus,
        Self: AsMutTensor<Element = Scalar>,
        LweSecretKey<BinaryKeyKind, C1>: AsRefTensor<Element = Scalar>,
        GlweSecretKey<BinaryKeyKind, C2>: AsRefTensor<Element = Scalar>,
        Gen: ByteRandomGenerator,
    {
        debug_assert!(
            self.fpksk_count().0 == output_glwe_key.key_size().to_glwe_size().0,
            "Current list has {} fpksk, need to have {} \
            (encrypted_glwe_key.key_size().to_glwe_size())",
            self.fpksk_count().0,
            output_glwe_key.key_size().to_glwe_size().0
        );

        let decomp_level_count = self.decomp_level_count;

        let gen_iter = generator
            .fork_cbs_pfpksk_to_pfpksk::<Scalar>(
                decomp_level_count,
                output_glwe_key.key_size().to_glwe_size(),
                output_glwe_key.polynomial_size(),
                input_lwe_key.key_size().to_lwe_size(),
                self.fpksk_count(),
            )
            .unwrap();

        let mut last_polynomial_as_list = PolynomialList::allocate(
            Scalar::ZERO,
            PolynomialCount(1),
            output_glwe_key.polynomial_size(),
        );
        // We apply the x -> -x function so instead of putting one in the first coeff of the
        // polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function in
        // the loop avoiding branching
        *last_polynomial_as_list
            .get_mut_polynomial(0)
            .get_mut_monomial(MonomialDegree(0))
            .get_mut_coefficient() = Scalar::MAX;

        for ((mut fpksk, polynomial_to_encrypt), mut loop_generator) in self
            .fpksk_iter_mut()
            .zip(
                output_glwe_key
                    .as_polynomial_list()
                    .polynomial_iter()
                    .chain(last_polynomial_as_list.polynomial_iter()),
            )
            .zip(gen_iter)
        {
            fpksk.fill_with_private_functional_packing_keyswitch_key(
                input_lwe_key,
                output_glwe_key,
                noise_parameters,
                &mut loop_generator,
                &|x| Scalar::ZERO.wrapping_sub(x),
                &polynomial_to_encrypt,
            );
        }
    }

    #[cfg(feature = "__commons_parallel")]
    pub fn par_fill_with_fpksk_for_circuit_bootstrap<Scalar, C1, C2, Gen, Noise>(
        &mut self,
        input_lwe_key: &LweSecretKey<BinaryKeyKind, C1>,
        output_glwe_key: &GlweSecretKey<BinaryKeyKind, C2>,
        noise_parameters: Noise,
        generator: &mut EncryptionRandomGenerator<Gen>,
    ) where
        Scalar: UnsignedTorus + Sync + Send,
        Self: AsMutTensor<Element = Scalar>,
        <Self as AsMutTensor>::Element: Sync + Send,
        LweSecretKey<BinaryKeyKind, C1>: AsRefTensor<Element = Scalar>,
        GlweSecretKey<BinaryKeyKind, C2>: AsRefTensor<Element = Scalar>,
        C1: AsRefSlice<Element = Scalar> + Sync + Send,
        C2: AsRefSlice<Element = Scalar> + Sync + Send,
        Gen: ParallelByteRandomGenerator,
        Noise: DispersionParameter + Sync + Send,
    {
        debug_assert!(
            self.fpksk_count().0 == output_glwe_key.key_size().to_glwe_size().0,
            "Current list has {} fpksk, need to have {} \
            (encrypted_glwe_key.key_size().to_glwe_size())",
            self.fpksk_count().0,
            output_glwe_key.key_size().to_glwe_size().0
        );

        let decomp_level_count = self.decomp_level_count;

        let gen_iter = generator
            .par_fork_cbs_pfpksk_to_pfpksk::<Scalar>(
                decomp_level_count,
                output_glwe_key.key_size().to_glwe_size(),
                output_glwe_key.polynomial_size(),
                input_lwe_key.key_size().to_lwe_size(),
                self.fpksk_count(),
            )
            .unwrap();

        let mut last_polynomial_as_list = PolynomialList::allocate(
            Scalar::ZERO,
            PolynomialCount(1),
            output_glwe_key.polynomial_size(),
        );
        // We apply the x -> -x function so instead of putting one in the first coeff of the
        // polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function
        // in the loop avoiding branching
        *last_polynomial_as_list
            .get_mut_polynomial(0)
            .get_mut_monomial(MonomialDegree(0))
            .get_mut_coefficient() = Scalar::MAX;

        self.par_fpksk_iter_mut()
            .zip(
                output_glwe_key
                    .as_polynomial_list()
                    .par_polynomial_iter()
                    .chain(last_polynomial_as_list.par_polynomial_iter()),
            )
            .zip(gen_iter)
            .for_each(|((mut fpksk, polynomial_to_encrypt), mut loop_generator)| {
                fpksk.par_fill_with_private_functional_packing_keyswitch_key(
                    input_lwe_key,
                    output_glwe_key,
                    noise_parameters,
                    &mut loop_generator,
                    &|x| Scalar::ZERO.wrapping_sub(x),
                    &polynomial_to_encrypt,
                );
            })
    }
}

#[cfg(feature = "__commons_parallel")]
#[cfg(test)]
mod test {
    use crate::commons::crypto::glwe::LwePrivateFunctionalPackingKeyswitchKeyList;
    use crate::commons::crypto::secret::generators::{
        DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
    };
    use crate::commons::crypto::secret::{GlweSecretKey, LweSecretKey};
    use crate::prelude::{
        DecompositionBaseLog, DecompositionLevelCount, FunctionalPackingKeyswitchKeyCount,
        GlweDimension, LogStandardDev, LweDimension, PolynomialSize,
    };
    use concrete_csprng::generators::SoftwareRandomGenerator;
    use concrete_csprng::seeders::Seed;

    #[test]
    fn check_equivalence_serial_parallel_pfpksk_gen() {
        let input_lwe_dimension = LweDimension(10);
        let output_glwe_dimension = GlweDimension(3);
        let polynomial_size = PolynomialSize(256);
        let decomp_base_log = DecompositionBaseLog(3);
        let decomp_level_count = DecompositionLevelCount(5);
        let mut secret_generator = SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
        let noise = LogStandardDev::from_log_standard_dev(-15.);

        let mask_seed = Seed(crate::commons::test_tools::any_usize() as u128);
        let deterministic_seeder_seed = Seed(crate::commons::test_tools::any_usize() as u128);
        let number_of_runs = 10_usize;

        for _ in 0..number_of_runs {
            let input_key =
                LweSecretKey::generate_binary(input_lwe_dimension, &mut secret_generator);
            let output_key = GlweSecretKey::generate_binary(
                output_glwe_dimension,
                polynomial_size,
                &mut secret_generator,
            );

            let mut encryption_generator =
                EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(
                    mask_seed,
                    &mut DeterministicSeeder::<SoftwareRandomGenerator>::new(
                        deterministic_seeder_seed,
                    ),
                );

            let mut pfpksk_serial = LwePrivateFunctionalPackingKeyswitchKeyList::allocate(
                0u32,
                decomp_level_count,
                decomp_base_log,
                input_lwe_dimension,
                output_glwe_dimension,
                polynomial_size,
                FunctionalPackingKeyswitchKeyCount(output_glwe_dimension.to_glwe_size().0),
            );
            pfpksk_serial.fill_with_fpksk_for_circuit_bootstrap(
                &input_key,
                &output_key,
                noise,
                &mut encryption_generator,
            );

            let mut encryption_generator =
                EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(
                    mask_seed,
                    &mut DeterministicSeeder::<SoftwareRandomGenerator>::new(
                        deterministic_seeder_seed,
                    ),
                );
            let mut pfpksk_par = LwePrivateFunctionalPackingKeyswitchKeyList::allocate(
                0u32,
                decomp_level_count,
                decomp_base_log,
                input_lwe_dimension,
                output_glwe_dimension,
                polynomial_size,
                FunctionalPackingKeyswitchKeyCount(output_glwe_dimension.to_glwe_size().0),
            );
            pfpksk_par.par_fill_with_fpksk_for_circuit_bootstrap(
                &input_key,
                &output_key,
                noise,
                &mut encryption_generator,
            );

            assert_eq!(pfpksk_par, pfpksk_serial);
        }
    }
}