Issue with Inconsistent Outputs Using FHEW Scheme in OpenFHE-Python

I’m experimenting with the FHEW scheme after switching from CKKS in OpenFHE-Python (running inside a Docker environment). I followed the context creation steps as shown in the scheme-switching.py example.
However, when running the function for finding the maximum value and its index (argmax), the outputs vary significantly between runs. Sometimes, the function works with 100% accuracy, and other times, it gives completely incorrect results.
Example of Two Consecutive Runs
Run 1 (Correct Result)

vecA:
[-74.0, 8.0, 7.0, -80.0, 15.0, -41.0, 100.0, -29.0]
Expected maximum value  100.0  at location  6
ctx_maximum:  100.0
Argmax:  6

Run 2 (Incorrect Result):

vecA:
[-92.0, 1.0, 47.0, 37.0, 99.0, 25.0, -77.0, 2.0]
Expected maximum value  99.0  at location  4
ctx_maximum:  -92.0
Argmax:  0

Here’s the code I’m using:

from openfhe import *
import random
from math import log2

# region - Set CryptoContext
parameters = CCParamsCKKSRNS()

if get_native_int() == 128:
    rescaleTech = ScalingTechnique.FLEXIBLEAUTOEXT
    dcrtBits = 78
    firstMod = 89
else:
    rescaleTech = ScalingTechnique.FLEXIBLEAUTOEXT
    dcrtBits = 59
    firstMod = 60

dim = 2**3
batch_size = 2**3
sec_lvl = SecurityLevel.HEStd_NotSet
ringDim = pow(2, 10)

sec_lvl_Bin = TOY
secretKeyDist = SecretKeyDist.UNIFORM_TERNARY
slots = batch_size
numValues = dim

parameters.SetScalingModSize(dcrtBits)
parameters.SetFirstModSize(firstMod)
parameters.SetScalingTechnique(rescaleTech)
parameters.SetNumLargeDigits(3)
parameters.SetSecretKeyDist(secretKeyDist)
parameters.SetSecurityLevel(sec_lvl)
parameters.SetRingDim(ringDim)
parameters.SetBatchSize(batch_size)

# NOTE: Multiplicative Depth = 12 (scheme switch comparison) + 1 (extra) + log2(numValues) for argmax
multDepth = 13 + int(log2(numValues))
parameters.SetMultiplicativeDepth(multDepth)

cryptocontext = GenCryptoContext(parameters)
cryptocontext.Enable(PKESchemeFeature.PKE)
cryptocontext.Enable(PKESchemeFeature.KEYSWITCH)
cryptocontext.Enable(PKESchemeFeature.LEVELEDSHE)
cryptocontext.Enable(PKESchemeFeature.ADVANCEDSHE)
cryptocontext.Enable(PKESchemeFeature.SCHEMESWITCH)
# endregion

# region - Key Generation
keys = cryptocontext.KeyGen()
cryptocontext.EvalMultKeyGen(keys.secretKey)
# endregion

# region - FHEW Env Creation
params = SchSwchParams()
logQ_ccLWE = 25
params.SetSecurityLevelCKKS(sec_lvl)
params.SetSecurityLevelFHEW(sec_lvl_Bin)
params.SetCtxtModSizeFHEWLargePrec(logQ_ccLWE)
params.SetNumSlotsCKKS(slots)
params.SetNumValues(numValues)

privateKeyFHEW = cryptocontext.EvalSchemeSwitchingSetup(params)
ccLWE = cryptocontext.GetBinCCForSchemeSwitch()

cryptocontext.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW)

scaleSign = 512
modulus_LWE = 1 << logQ_ccLWE
beta = ccLWE.GetBeta()
pLWE = int(modulus_LWE / (2 * beta))  # Large precision

cryptocontext.EvalCompareSwitchPrecompute(pLWE, scaleSign)
# endregion

# region - PTX Generation
vecA = [random.randint(-100, 100) / 1.0 for i in range(dim)]
print(f"vecA:\n{vecA}")
actual_max = max(vecA)
actual_argmax = vecA.index(max(vecA))
# endregion

# region - Packing & Encryption
ptx_A = cryptocontext.MakeCKKSPackedPlaintext(vecA)
ctx_A = cryptocontext.Encrypt(keys.publicKey, ptx_A)
# endregion

# region - Detailed Functions Evaluations
ctx_result = cryptocontext.EvalMaxSchemeSwitching(
    ctx_A,
    keys.publicKey,
    numValues,
    slots,
)
ctx_maximum = ctx_result[0]
ctx_argmax = ctx_result[1]
# endregion

# region - Results Decryption & Clear Evaluation
ptxtMax = cryptocontext.Decrypt(keys.secretKey, ctx_maximum)
ptxtMax.SetLength(1)
res_vec = ptxtMax.GetRealPackedValue()
max_res = round(res_vec[0], 2)

oneHot_ptxtMax = cryptocontext.Decrypt(keys.secretKey, ctx_argmax)
oneHot_ptxtMax.SetLength(numValues)
oneHot_res_vec = oneHot_ptxtMax.GetRealPackedValue()
oneHot_res_vec = [round(val, 2) for val in oneHot_res_vec]

argmax_res = oneHot_res_vec.index(max(oneHot_res_vec))
print("Expected maximum value ", actual_max, " at location ", actual_argmax)
print("ctx_maximum: ", max_res)
print("Argmax: ", argmax_res)
# endregion
  • Sometimes the function correctly returns the maximum value and its index, while other times it returns completely incorrect results.
  • Since I’m using FHEW after switching from CKKS, could there be a problem with the context setup?

Would appreciate any insights or suggestions :slight_smile:

Change the scaleSign to be smaller than 512, e.g., 256, and it will work. The scaled inputs (also translated to a positive interval) need to be smaller than the LWE plaintext modulus. (Not too close because the argmin functionality is iterative and at each iteration there are some errors introduced due to CKKS.) Your current choice of inputs along with the scaleSign is getting quite close to pLWE.