Procházet zdrojové kódy

Merge pull request #18 from openfheorg/Oliveira_advanced_ckks

Oliveira advanced ckks
Rener Oliveira před 1 rokem
rodič
revize
d8aa98be55

+ 9 - 0
include/pke/cryptocontext_wrapper.h

@@ -19,4 +19,13 @@ Plaintext MakeCKKSPackedPlaintextWrapper(std::shared_ptr<CryptoContextImpl<DCRTP
             const std::shared_ptr<ParmType> params,
             usint slots);
 
+Ciphertext<DCRTPoly> EvalFastRotationPrecomputeWrapper(CryptoContext<DCRTPoly>& self,
+                                                        ConstCiphertext<DCRTPoly> ciphertext);
+
+Ciphertext<DCRTPoly> EvalFastRotationWrapper(CryptoContext<DCRTPoly>& self,
+                                            ConstCiphertext<DCRTPoly> ciphertext,
+                                              const usint index,
+                                              const usint m,
+                                              ConstCiphertext<DCRTPoly> digits);
+
 #endif // OPENFHE_CRYPTOCONTEXT_BINDINGS_H

+ 32 - 2
src/bindings.cpp

@@ -1,5 +1,6 @@
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
+#include <pybind11/operators.h>
 #include <pybind11/iostream.h>
 #include <iostream>
 #include "openfhe.h"
@@ -37,11 +38,17 @@ void bind_parameters(py::module &m)
         .def("SetMultiplicativeDepth", &CCParams<CryptoContextCKKSRNS>::SetMultiplicativeDepth)
         .def("SetScalingModSize", &CCParams<CryptoContextCKKSRNS>::SetScalingModSize)
         .def("SetBatchSize", &CCParams<CryptoContextCKKSRNS>::SetBatchSize)
+        .def("SetScalingTechnique", &CCParams<CryptoContextCKKSRNS>::SetScalingTechnique)
+        .def("SetNumLargeDigits", &CCParams<CryptoContextCKKSRNS>::SetNumLargeDigits)
+        .def("SetKeySwitchTechnique", &CCParams<CryptoContextCKKSRNS>::SetKeySwitchTechnique)
+        .def("SetFirstModSize", &CCParams<CryptoContextCKKSRNS>::SetFirstModSize)
+        .def("SetDigitSize", &CCParams<CryptoContextCKKSRNS>::SetDigitSize)
         // getters
         .def("GetPlaintextModulus", &CCParams<CryptoContextCKKSRNS>::GetPlaintextModulus)
         .def("GetMultiplicativeDepth", &CCParams<CryptoContextCKKSRNS>::GetMultiplicativeDepth)
         .def("GetScalingModSize", &CCParams<CryptoContextCKKSRNS>::GetScalingModSize)
-        .def("GetBatchSize", &CCParams<CryptoContextCKKSRNS>::GetBatchSize);
+        .def("GetBatchSize", &CCParams<CryptoContextCKKSRNS>::GetBatchSize)
+        .def("GetScalingTechnique", &CCParams<CryptoContextCKKSRNS>::GetScalingTechnique);
 }
 
 void bind_crypto_context(py::module &m)
@@ -50,6 +57,8 @@ void bind_crypto_context(py::module &m)
         .def(py::init<>())
         .def("GetKeyGenLevel", &CryptoContextImpl<DCRTPoly>::GetKeyGenLevel)
         .def("SetKeyGenLevel", &CryptoContextImpl<DCRTPoly>::SetKeyGenLevel)
+        //.def("GetScheme",&CryptoContextImpl<DCRTPoly>::GetScheme)
+        //.def("GetCryptoParameters", &CryptoContextImpl<DCRTPoly>::GetCryptoParameters)
         .def("GetRingDimension", &CryptoContextImpl<DCRTPoly>::GetRingDimension)
         .def("Enable", static_cast<void (CryptoContextImpl<DCRTPoly>::*)(PKESchemeFeature)>(&CryptoContextImpl<DCRTPoly>::Enable), "Enable a feature for the CryptoContext")
         .def("KeyGen", &CryptoContextImpl<DCRTPoly>::KeyGen, "Generate a key pair with public and private keys")
@@ -65,12 +74,16 @@ void bind_crypto_context(py::module &m)
             py::arg("params") = py::none(),
             py::arg("slots") = 0)
         .def("EvalRotate", &CryptoContextImpl<DCRTPoly>::EvalRotate, "Rotate a ciphertext")
+        .def("EvalFastRotationPrecompute", &EvalFastRotationPrecomputeWrapper)
+        .def("EvalFastRotation", &EvalFastRotationWrapper)
         .def("Encrypt", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(const PublicKey<DCRTPoly>, Plaintext) const>(&CryptoContextImpl<DCRTPoly>::Encrypt),
              "Encrypt a plaintext using public key")
         .def("EvalAdd", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(ConstCiphertext<DCRTPoly>, ConstCiphertext<DCRTPoly>) const>(&CryptoContextImpl<DCRTPoly>::EvalAdd), "Add two ciphertexts")
+        .def("EvalAdd", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(ConstCiphertext<DCRTPoly>, double) const>(&CryptoContextImpl<DCRTPoly>::EvalAdd), "Add a ciphertext with a scalar")
         .def("EvalSub", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(ConstCiphertext<DCRTPoly>, ConstCiphertext<DCRTPoly>) const>(&CryptoContextImpl<DCRTPoly>::EvalSub), "Subtract two ciphertexts")
         .def("EvalMult", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(ConstCiphertext<DCRTPoly>, ConstCiphertext<DCRTPoly>) const>(&CryptoContextImpl<DCRTPoly>::EvalMult), "Multiply two ciphertexts")
         .def("EvalMult", static_cast<Ciphertext<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(ConstCiphertext<DCRTPoly>, double) const>(&CryptoContextImpl<DCRTPoly>::EvalMult), "Multiply a ciphertext with a scalar")
+        .def("Rescale", &CryptoContextImpl<DCRTPoly>::Rescale, "Rescale a ciphertext")
         .def_static(
             "ClearEvalMultKeys", []()
             { CryptoContextImpl<DCRTPoly>::ClearEvalMultKeys(); },
@@ -147,6 +160,20 @@ void bind_enums_and_constants(py::module &m)
     m.attr("JSON") = py::cast(SerType::JSON);
     m.attr("BINARY") = py::cast(SerType::BINARY);
 
+    // Scaling Techniques
+    py::enum_<ScalingTechnique>(m, "ScalingTechnique")
+       .value("FIXEDMANUAL", ScalingTechnique::FIXEDMANUAL)
+       .value("FIXEDAUTO", ScalingTechnique::FIXEDAUTO)
+       .value("FLEXIBLEAUTO", ScalingTechnique::FLEXIBLEAUTO)
+       .value("FLEXIBLEAUTOEXT", ScalingTechnique::FLEXIBLEAUTOEXT)
+       .value("NORESCALE", ScalingTechnique::NORESCALE)
+       .value("INVALID_RS_TECHNIQUE", ScalingTechnique::INVALID_RS_TECHNIQUE);
+    // Key Switching Techniques
+    py::enum_<KeySwitchTechnique>(m, "KeySwitchTechnique")
+        .value("INVALID_KS_TECH", KeySwitchTechnique::INVALID_KS_TECH)
+        .value("BV", KeySwitchTechnique::BV)
+        .value("HYBRID", KeySwitchTechnique::HYBRID);
+
     //Parameters Type
     using ParmType = typename DCRTPoly::Params;
     py::class_<ParmType, std::shared_ptr<ParmType>>(m, "ParmType");
@@ -192,7 +219,10 @@ void bind_encodings(py::module &m)
 void bind_ciphertext(py::module &m)
 {
     py::class_<CiphertextImpl<DCRTPoly>, std::shared_ptr<CiphertextImpl<DCRTPoly>>>(m, "Ciphertext")
-        .def(py::init<>());
+        .def(py::init<>())
+        .def("__add__", [](const Ciphertext<DCRTPoly> &a, const Ciphertext<DCRTPoly> &b)
+             {return a + b; },py::is_operator(),pybind11::keep_alive<0, 1>());
+       // .def(py::self + py::self);
     // .def("GetDepth", &CiphertextImpl<DCRTPoly>::GetDepth)
     // .def("SetDepth", &CiphertextImpl<DCRTPoly>::SetDepth)
     // .def("GetLevel", &CiphertextImpl<DCRTPoly>::GetLevel)

+ 16 - 1
src/pke/cryptocontext_wrapper.cpp

@@ -21,4 +21,19 @@ Plaintext MakeCKKSPackedPlaintextWrapper(std::shared_ptr<CryptoContextImpl<DCRTP
                 std::vector<std::complex<double>> complexValue(value.size());
                 std::transform(value.begin(), value.end(), complexValue.begin(),
                        [](float da) { return std::complex<double>(da); });
-                return self->MakeCKKSPackedPlaintext(complexValue, depth, level, params, slots); }
+                return self->MakeCKKSPackedPlaintext(complexValue, depth, level, params, slots); }
+
+Ciphertext<DCRTPoly> EvalFastRotationPrecomputeWrapper(CryptoContext<DCRTPoly> &self,ConstCiphertext<DCRTPoly> ciphertext) {
+    std::shared_ptr<std::vector<DCRTPoly>> precomp = self->EvalFastRotationPrecompute(ciphertext);
+    std::vector<DCRTPoly> elements = *(precomp.get());
+    CiphertextImpl<DCRTPoly> cipherdigits = CiphertextImpl<DCRTPoly>(self);
+    std::shared_ptr<CiphertextImpl<DCRTPoly>> cipherdigitsPtr = std::make_shared<CiphertextImpl<DCRTPoly>>(cipherdigits);
+    cipherdigitsPtr->SetElements(elements);
+    return cipherdigitsPtr;
+}
+Ciphertext<DCRTPoly> EvalFastRotationWrapper(CryptoContext<DCRTPoly>& self,ConstCiphertext<DCRTPoly> ciphertext, const usint index, const usint m,ConstCiphertext<DCRTPoly> digits) {
+    
+        std::vector<DCRTPoly> digitsElements = digits->GetElements();
+        std::shared_ptr<std::vector<DCRTPoly>> digitsElementsPtr = std::make_shared<std::vector<DCRTPoly>>(digitsElements);
+        return self->EvalFastRotation(ciphertext, index, m, digitsElementsPtr);
+    }

+ 374 - 0
src/pke/examples/advanced-real-numbers.py

@@ -0,0 +1,374 @@
+from openfhe import *
+import time # to enable TIC-TOC timing measurements
+
+def AutomaticRescaleDemo(scalTech):
+    if(scalTech == ScalingTechnique.FLEXIBLEAUTO):
+        print("\n\n\n ===== FlexibleAutoDemo =============\n") 
+    else:
+         print("\n\n\n ===== FixedAutoDemo =============\n")
+
+    batchSize = 8
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(5)
+    parameters.SetScalingModSize(50)
+    parameters.SetScalingTechnique(scalTech)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}\n")
+
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalMultKeyGen(keys.secretKey)
+
+    # Input
+    x = [1.0, 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    # Computing f(x) = x^18 + x^9 + 1
+    #
+    # In the following we compute f(x) with a computation
+    # that has a multiplicative depth of 5.
+    #
+    # The result is correct, even though there is no call to
+    # the Rescale() operation.
+
+    c2 = cc.EvalMult(c, c)                       # x^2
+    c4 = cc.EvalMult(c2, c2)                     # x^4
+    c8 = cc.EvalMult(c4, c4)                     # x^8
+    c16 = cc.EvalMult(c8, c8)                    # x^16
+    c9 = cc.EvalMult(c8, c)                      # x^9
+    c18 = cc.EvalMult(c16, c2)                   # x^18
+    cRes = cc.EvalAdd(cc.EvalAdd(c18, c9), 1.0)  # Final result
+
+    result = Decrypt(cRes,keys.secretKey)
+    print("x^18 + x^9 + 1 = ", result)
+    result.SetLength(batchSize)
+    print(f"Result: {result}")
+
+def ManualRescaleDemo(ScalingTechnique):
+    print("\n\n\n ===== FixedManualDemo =============\n")
+    
+    batchSize = 8
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(5)
+    parameters.SetScalingModSize(50)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}\n")
+    
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalMultKeyGen(keys.secretKey)
+
+    # Input
+    x = [1.0, 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    # Computing f(x) = x^18 + x^9 + 1
+    #
+    # Compare the following with the corresponding code
+    # for FLEXIBLEAUTO. Here we need to track the depth of ciphertexts
+    # and call Rescale whenever needed. In this instance it's still
+    # not hard to do so, but this can be quite tedious in other
+    # complicated computations. (e.g. in bootstrapping)
+    #
+    #
+
+    # x^2
+    c2_depth_2 = cc.EvalMult(c, c)
+    c2_depth_1 = cc.Rescale(c2_depth_2)
+    # x^4
+    c4_depth2 = cc.EvalMult(c2_depth_1, c2_depth_1)
+    c4_depth1 = cc.Rescale(c4_depth2)
+    # x^8
+    c8_depth2 = cc.EvalMult(c4_depth1, c4_depth1)
+    c8_depth1 = cc.Rescale(c8_depth2)
+    # x^16
+    c16_depth2 = cc.EvalMult(c8_depth1, c8_depth1)
+    c16_depth1 = cc.Rescale(c16_depth2)
+    # x^9
+    c9_depth2 = cc.EvalMult(c8_depth1, c)
+    # x^18
+    c18_depth2 = cc.EvalMult(c16_depth1, c2_depth_1)
+    # Final result
+    cRes_depth2 = cc.EvalAdd(cc.EvalAdd(c18_depth2, c9_depth2), 1.0)
+    cRes_depth1 = cc.Rescale(cRes_depth2)
+
+    result = Decrypt(cRes_depth1,keys.secretKey)
+    result.SetLength(batchSize)
+    print("x^18 + x^9 + 1 = ", result)
+
+def HybridKeySwitchingDemo1():
+    
+    dnum = 2
+    batchSize = 8
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(5)
+    parameters.SetScalingModSize(50)
+    parameters.SetBatchSize(batchSize)
+    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    parameters.SetNumLargeDigits(dnum)
+
+    cc = GenCryptoContext(parameters)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}\n")
+
+    print(f"- Using HYBRID key switching with {dnum} digits\n")
+
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalRotateKeyGen(keys.secretKey,[1,-2])
+
+    # Input
+    x = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    t = time.time()
+    cRot1 = cc.EvalRotate(c,1)
+    cRot2 = cc.EvalRotate(cRot1,-2)
+    time2digits = time.time() - t
+
+    result = Decrypt(cRot2,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"x rotate by -1 = {result}")
+    print(f" - 2 rotations with HYBRID (2 digits) took {time2digits*1000} ms")
+
+
+def HybridKeySwitchingDemo2():
+    print("\n\n\n ===== HybridKeySwitchingDemo2 =============\n")
+    dnum = 3
+    batchSize = 8
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(5)
+    parameters.SetScalingModSize(50)
+    parameters.SetBatchSize(batchSize)
+    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    parameters.SetNumLargeDigits(dnum)
+
+    cc = GenCryptoContext(parameters)
+
+    # Compare the ring dimension in this demo to the one in the previous
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}\n")
+
+    print(f"- Using HYBRID key switching with {dnum} digits\n")
+
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalRotateKeyGen(keys.secretKey,[1,-2])
+
+    # Input
+    x = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    t = time.time()
+    cRot1 = cc.EvalRotate(c,1)
+    cRot2 = cc.EvalRotate(cRot1,-2)
+    time3digits = time.time() - t
+    # The runtime here is smaller than the previous demo
+
+    result = Decrypt(cRot2,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"x rotate by -1 = {result}")
+    print(f" - 2 rotations with HYBRID (3 digits) took {time3digits*1000} ms")
+
+def FastRotationDemo1():
+    print("\n\n\n ===== FastRotationDemo1 =============\n")
+    batchSize = 8
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(5)
+    parameters.SetScalingModSize(50)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    N = cc.GetRingDimension()
+    print(f"CKKS scheme is using ring dimension {N}\n")
+
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalRotateKeyGen(keys.secretKey,[1,2,3,4,5,6,7])
+
+    # Input
+    x = [0, 0, 0, 0, 0, 0, 0, 1]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    # First, we perform 7 regular (non-hoisted) rotations
+    # and measure the runtime
+    t = time.time()
+    cRot1 = cc.EvalRotate(c,1)
+    cRot2 = cc.EvalRotate(c,2)
+    cRot3 = cc.EvalRotate(c,3)
+    cRot4 = cc.EvalRotate(c,4)
+    cRot5 = cc.EvalRotate(c,5)
+    cRot6 = cc.EvalRotate(c,6)
+    cRot7 = cc.EvalRotate(c,7)
+    timeNoHoisting = time.time() - t
+
+    cResNoHoist = c + cRot1 + cRot2 + cRot3 + cRot4 + cRot5 + cRot6 + cRot7
+
+    # M is the cyclotomic order and we need it to call EvalFastRotation
+    M = 2*N
+
+    # Then, we perform 7 rotations with hoisting.
+    t = time.time()
+    cPrecomp = cc.EvalFastRotationPrecompute(c)
+    cRot1 = cc.EvalFastRotation(c, 1, M, cPrecomp)
+    cRot2 = cc.EvalFastRotation(c, 2, M, cPrecomp)
+    cRot3 = cc.EvalFastRotation(c, 3, M, cPrecomp)
+    cRot4 = cc.EvalFastRotation(c, 4, M, cPrecomp)
+    cRot5 = cc.EvalFastRotation(c, 5, M, cPrecomp)
+    cRot6 = cc.EvalFastRotation(c, 6, M, cPrecomp)
+    cRot7 = cc.EvalFastRotation(c, 7, M, cPrecomp)
+    timeHoisting = time.time() - t
+    # The time with hoisting should be faster than without hoisting.
+
+    cResHoist = c + cRot1 + cRot2 + cRot3 + cRot4 + cRot5 + cRot6 + cRot7
+    
+    result = Decrypt(cResNoHoist,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"Result without hoisting: {result}")
+    print(f" - 7 rotations without hoisting took {timeNoHoisting*1000} ms")
+
+    
+    result = Decrypt(cResHoist,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"Result with hoisting: {result}")
+    print(f" - 7 rotations with hoisting took {timeHoisting*1000} ms")
+
+
+
+
+def FastRotationDemo2():
+    print("\n\n\n ===== FastRotationDemo2 =============\n")
+
+    digitSize = 3
+    batchSize = 8
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(1)
+    parameters.SetScalingModSize(50)
+    parameters.SetBatchSize(batchSize)
+    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    parameters.SetKeySwitchTechnique(KeySwitchTechnique.BV)
+    parameters.SetFirstModSize(60)
+    parameters.SetDigitSize(digitSize)
+
+    cc = GenCryptoContext(parameters)
+
+    N = cc.GetRingDimension()
+    print(f"CKKS scheme is using ring dimension {N}\n")
+
+    cc.Enable(PKESchemeFeature.PKE)
+    cc.Enable(PKESchemeFeature.KEYSWITCH)
+    cc.Enable(PKESchemeFeature.LEVELEDSHE)
+
+    keys = cc.KeyGen()
+    cc.EvalRotateKeyGen(keys.secretKey,[1,2,3,4,5,6,7])
+
+    # Input
+    x = [0, 0, 0, 0, 0, 0, 0, 1]
+    ptxt = cc.MakeCKKSPackedPlaintext(x)
+
+    print(f"Input x: {ptxt}")
+
+    c = cc.Encrypt(keys.publicKey,ptxt)
+
+    # First, we perform 7 regular (non-hoisted) rotations
+    # and measure the runtime
+    t = time.time()
+    cRot1 = cc.EvalRotate(c,1)
+    cRot2 = cc.EvalRotate(c,2)
+    cRot3 = cc.EvalRotate(c,3)
+    cRot4 = cc.EvalRotate(c,4)
+    cRot5 = cc.EvalRotate(c,5)
+    cRot6 = cc.EvalRotate(c,6)
+    cRot7 = cc.EvalRotate(c,7)
+    timeNoHoisting = time.time() - t
+
+    cResNoHoist = c + cRot1 + cRot2 + cRot3 + cRot4 + cRot5 + cRot6 + cRot7
+
+    # M is the cyclotomic order and we need it to call EvalFastRotation
+    M = 2*N
+
+    # Then, we perform 7 rotations with hoisting.
+    t = time.time()
+    cPrecomp = cc.EvalFastRotationPrecompute(c)
+    cRot1 = cc.EvalFastRotation(c, 1, M, cPrecomp)
+    cRot2 = cc.EvalFastRotation(c, 2, M, cPrecomp)
+    cRot3 = cc.EvalFastRotation(c, 3, M, cPrecomp)
+    cRot4 = cc.EvalFastRotation(c, 4, M, cPrecomp)
+    cRot5 = cc.EvalFastRotation(c, 5, M, cPrecomp)
+    cRot6 = cc.EvalFastRotation(c, 6, M, cPrecomp)
+    cRot7 = cc.EvalFastRotation(c, 7, M, cPrecomp)
+    timeHoisting = time.time() - t
+    # The time with hoisting should be faster than without hoisting.
+    # Also, the benefits from hoisting should be more pronounced in this
+    # case because we're using BV. Of course, we also observe less
+    # accurate results than when using HYBRID, because of using
+    # digitSize = 10 (Users can decrease digitSize to see the accuracy
+    # increase, and performance decrease).
+
+    cResHoist = c + cRot1 + cRot2 + cRot3 + cRot4 + cRot5 + cRot6 + cRot7
+
+    result = Decrypt(cResNoHoist,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"Result without hoisting: {result}")
+    print(f" - 7 rotations without hoisting took {timeNoHoisting*1000} ms")
+
+    result = Decrypt(cResHoist,keys.secretKey)
+    result.SetLength(batchSize)
+    print(f"Result with hoisting: {result}")
+    print(f" - 7 rotations with hoisting took {timeHoisting*1000} ms")
+
+
+def main():
+    AutomaticRescaleDemo(ScalingTechnique.FLEXIBLEAUTO)
+    AutomaticRescaleDemo(ScalingTechnique.FIXEDAUTO)
+    ManualRescaleDemo(ScalingTechnique.FIXEDMANUAL)
+    HybridKeySwitchingDemo1()
+    HybridKeySwitchingDemo2()
+    FastRotationDemo1()
+    FastRotationDemo2()
+
+if __name__ == "__main__":
+    main()
+