I am trying to use the Scheme Switching capability of OpenFHE to evaluate the Heaviside function in CKKS (y(x) = 1 if x>=0, 0 otherwise).
I am doing some tests, with this code:
import time
import csv
import sys
import numpy as np
from openfhe import *
def run_sign_eval(slots: int, csv_path: str = "results.csv"):
N = 65536 # Mandatory because we need multDepth high
multDepth = 18
# -------------------------------
# CKKS CRYPTOGRAPHIC CONTEXT
# -------------------------------
params = CCParamsCKKSRNS()
params.SetSecurityLevel(HEStd_128_classic)
params.SetMultiplicativeDepth(multDepth)
params.SetScalingModSize(50)
params.SetBatchSize(slots)
params.SetRingDim(N)
if get_native_int() != 128:
params.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
else:
params.SetScalingTechnique(ScalingTechnique.FIXEDAUTO)
cc = GenCryptoContext(params)
cc.Enable(PKESchemeFeature.PKE)
cc.Enable(PKESchemeFeature.KEYSWITCH)
cc.Enable(PKESchemeFeature.LEVELEDSHE)
cc.Enable(PKESchemeFeature.ADVANCEDSHE)
cc.Enable(PKESchemeFeature.SCHEMESWITCH)
keys = cc.KeyGen()
cc.EvalMultKeyGen(keys.secretKey)
# -------------------------------
# CKKS -> FHEW SCHEME SWITCHING SETUP
# -------------------------------
fhew_params = SchSwchParams()
fhew_params.SetSecurityLevelCKKS(HEStd_128_classic)
fhew_params.SetSecurityLevelFHEW(STD128)
fhew_params.SetNumSlotsCKKS(slots)
fhew_params.SetNumValues(slots)
lwesk = cc.EvalSchemeSwitchingSetup(fhew_params)
cc.EvalSchemeSwitchingKeyGen(keys, lwesk)
pLWE = 0
scaleSign = 1.0
unit = True
cc.EvalCompareSwitchPrecompute(pLWE, scaleSign, unit)
# -------------------------------
# ENCRYPT INPUTS
# -------------------------------
x = np.random.normal(0, 1, slots).tolist()
pt_x = cc.MakeCKKSPackedPlaintext(x)
pt_0 = cc.MakeCKKSPackedPlaintext([0.0] * slots)
ct_x = cc.Encrypt(keys.publicKey, pt_x)
ct_0 = cc.Encrypt(keys.publicKey, pt_0)
# -------------------------------
# RUN COMPARISON (MEASURE TIME)
# -------------------------------
numValues = slots
numSlotsOut = numValues
t0 = time.time()
ct_cmp = cc.EvalCompareSchemeSwitching(
ct_x,
ct_0,
numValues,
numSlotsOut
)
elapsed = time.time() - t0
# -------------------------------
# APPEND TO CSV
# -------------------------------
with open(csv_path, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow([slots, elapsed])
print(f"[DONE] slots={slots} time={elapsed:.4f}s appended to {csv_path}")
# -------------------------------------
# CLI: python runnable_sign_eval.py 4096
# -------------------------------------
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python runnable_sign_eval.py <slots> [csv_path]")
sys.exit(1)
slots = int(sys.argv[1])
csv_path = sys.argv[2] if len(sys.argv) > 2 else "results.csv"
run_sign_eval(slots, csv_path)
This works well with 16, 32, 64, …, 16384 as value for slot.
However, if I try with slot=32768 (which is exactly N // 2), I get this error:
RuntimeError: /root/openfhe-python-packager/build/openfhe-development/src/pke/include/cryptocontext.h:l.433:MakeCKKSPackedPlaintextInternal(): The size [33930] of the vector with values should not be greater than ringDim/2 [32768] if the scheme is CKKS
Am I getting something wrong? From what I’ve understood, each value in the CKKS ciphertext becomes a FHEW ciphertext, the function is evaluated with bootstrapping, and then the CKKS ciphertext is rebuilt.
To test the code, run it with:
OMP_NUM_THREADS=40 python3 runnable_sign_eval.py 16384 results.csv
Note that:
- I had to set
OMP_NUM_THREADSotherwise with slots > 1024 the script spawnsn_coresthreads, resulting in a load overflow - with
slots=16384you would need about 170GB of RAM.
EDIT
I forgot to specify the openfhe version: openfhe==1.4.2.0.24.4, Ubuntu 24.04.