Compatibility Issue with Linear Transform Pre-computation in OpenFHE v1.4 (Imaginary Mode)

Hi everyone,

I recently upgraded from OpenFHE v1.3 to v1.4 and encountered an issue with a previously working code implementation. The code involves testing S2C and C2S operations, both running in imaginary mode.

After upgrading, the code fails during the pre-computation phase for these linear transformations. Specifically, it gets stuck at the point where pre-computation for the linear transforms is performed. I’m not sure if this is due to a bug in the new version or if there were changes in the API or configuration requirements.

Has anyone experienced similar issues or have insights into what might have changed between versions regarding linear transform pre-computation in imaginary mode? Any suggestions or workarounds would be greatly appreciated.

Thanks in advance for your help!

Can you please be more specific about the error message you receive and in what part of the pre-computation it fails?

You can check whether the latest commits in ckks-fhe.cpp have touched some of the code you are reusing for your testing of S2C and C2S.

My code gets stuck at the following line:

m_bootPrecomMap[slots] = std::make_shared<CKKSBootstrapPrecom>();

Specifically, in the new version of OpenFHE, the code hangs at this point. I am trying to perform precomputations for SlotsToCoeffs and CoeffsToSlots by using the FHECKKSRNSShadow class, which is inherited from FHERNS. I access the private members m_correctionFactor and m_bootPrecomMap in this class, but it seems to fail when executing the line above.

Code Overview

EvalSlotsToCoeffsSetup Function

void EvalSlotsToCoeffsSetup(const CryptoContextImpl<DCRTPoly>& cc, std::vector<uint32_t> levelBudget,
                            std::vector<uint32_t> dim1, uint32_t numSlots, uint32_t lDec) {
    const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(cc.GetCryptoParameters());
    cout << "S2C set up flows!" << endl;
    uint32_t M = cc.GetCyclotomicOrder();
    uint32_t slots = (numSlots == 0) ? M / 4 : numSlots;

    bool precompute = true;

    // Workaround to access private members m_correctionFactor and m_bootPrecomMap in FHECKKSRNS
    auto shadow = reinterpret_cast<FHECKKSRNSShadow*>(this);
    uint32_t &m_correctionFactor = shadow->m_correctionFactor;
    std::map<uint32_t, std::shared_ptr<CKKSBootstrapPrecom>> &m_bootPrecomMap = shadow->m_bootPrecomMap;

    m_correctionFactor = 9;

    m_bootPrecomMap[slots] = std::make_shared<CKKSBootstrapPrecom>();
    auto& precom = m_bootPrecomMap[slots];
    precom->m_slots = slots;
    precom->m_dim1 = dim1[0];
    
    uint32_t logSlots = std::log2(slots);
    if (logSlots == 0) {
        logSlots = 1;
    }

    std::vector<uint32_t> newBudget = levelBudget;

    if (newBudget[1] > logSlots) {
        std::cerr << "\nWarning, the level budget for decoding is too large. Setting it to " << logSlots << std::endl;
        newBudget[1] = logSlots;
    }
    if (newBudget[1] < 1) {
        std::cerr << "\nWarning, the level budget for decoding cannot be zero. Setting it to 1" << std::endl;
        newBudget[1] = 1;
    }
    
    precom->m_paramsDec = GetCollapsedFFTParams(slots, newBudget[1], dim1[1]);

    if (precompute) {
        uint32_t m = 4 * slots;
        std::vector<uint32_t> rotGroup(slots);
        uint32_t fivePows = 1;
        for (uint32_t i = 0; i < slots; ++i) {
            rotGroup[i] = fivePows;
            fivePows *= 5;
            fivePows %= m;
        }

        std::vector<std::complex<double>> ksiPows(m + 1);
        for (uint32_t j = 0; j < m; ++j) {
            double angle = 2.0 * M_PI * j / m;
            ksiPows[j].real(cos(angle));
            ksiPows[j].imag(sin(angle));
        }
        ksiPows[m] = ksiPows[0];

        NativeInteger q = cryptoParams->GetElementParams()->GetParams()[0]->GetModulus().ConvertToInt();
        double qDouble = q.ConvertToDouble();

        uint128_t factor = ((uint128_t)1 << ((uint32_t)std::round(std::log2(qDouble))));
        double pre = qDouble / factor;
        double scaleDec = 1 / pre;

        precom->m_U0PreFFT = EvalSlotsToCoeffsPrecompute(cc, ksiPows, rotGroup, false, scaleDec, lDec);
    }
}

FHECKKSRNSShadow Class

class FHECKKSRNSShadow : public FHERNS {
public:
    const uint32_t K_SPARSE = 28;
    const uint32_t K_UNIFORM = 512;
    const uint32_t K_UNIFORMEXT = 768;
    static const uint32_t R_UNIFORM = 6;
    static const uint32_t R_SPARSE = 3;
    uint32_t m_correctionFactor = 0;
    std::map<uint32_t, std::shared_ptr<CKKSBootstrapPrecom>> m_bootPrecomMap;
};

Possible Causes

I am using reinterpret_cast<FHECKKSRNSShadow*>(this) to bypass the private members m_correctionFactor and m_bootPrecomMap in FHECKKSRNS and then trying to access them in the EvalSlotsToCoeffsSetup function. However, the issue occurs at the line:

m_bootPrecomMap[slots] = std::make_shared<CKKSBootstrapPrecom>();

This seems to cause the program to freeze at this point.

  • I am not sure why m_bootPrecomMap is not working correctly in the new version of OpenFHE. Is it because the access pattern for FHECKKSRNS or m_bootPrecomMap has changed in the new version?

  • Is there a better way to solve this issue instead of using reinterpret_cast?

I would appreciate any suggestions or insights to help resolve this issue. Thank you!