Thank you for the suggestion for approximating arccos of a ratio! It looks very interesting and I will take a closer look at it, but on first glance the fact that every iteration requires a square root might cause new problems.
As for your other point, below you can find an MWE in Python. I ran it on Ubuntu with openfhe v1.3.1.0.24.4. As you can see from the output, I lose a lot of precision after scaling up the values before the division. After multiplying the approximated reciprocal with the numerator, the end result kind of works, but with a scale mod size of 45 I am only left with 16 bit at the end with a batch size of 1024. If the batch size is increased, the final precision drops even more, for example to 14 bits with 4096.
In this example, the input ciphertexts are fresh and start with maximum precision, but in most cases the input values are already degraded and have lower precision. In these cases, the decryption will fail after the division because of too low precision.
Please let me know your thoughts on this.
Thanks a lot!
from openfhe import *
def printCiph(name: str, ciph: Ciphertext):
dec = cc.Decrypt(secretKey, ciph)
dec.SetLength(4)
print(name, ": ", str(dec.GetFormattedValues(10)), ", Level: ", ciph.GetLevel(), ", Precision: ", dec.GetLogPrecision())
### Parameters ###
sec_level = HEStd_NotSet
scale_mod_size = 45
first_mod_size = 60
batch_size = 4096
ring_dim = batch_size * 2
level_budget = [2, 2]
available_levels = 18
key_dist = SecretKeyDist.SPARSE_TERNARY
mult_depth = available_levels + FHECKKSRNS.GetBootstrapDepth(level_budget, key_dist)
parameters = CCParamsCKKSRNS()
parameters.SetSecurityLevel(HEStd_NotSet)
parameters.SetRingDim(ring_dim)
parameters.SetSecretKeyDist(key_dist)
parameters.SetMultiplicativeDepth(mult_depth)
parameters.SetScalingModSize(scale_mod_size)
parameters.SetFirstModSize(first_mod_size)
parameters.SetBatchSize(batch_size)
parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
### Crypto Setup ###
cc = GenCryptoContext(parameters)
cc.Enable(PKESchemeFeature.PKE)
cc.Enable(PKESchemeFeature.KEYSWITCH)
cc.Enable(PKESchemeFeature.LEVELEDSHE)
cc.Enable(PKESchemeFeature.ADVANCEDSHE)
cc.Enable(PKESchemeFeature.FHE)
cc.EvalBootstrapSetup(level_budget, [0, 0], batch_size)
keys = cc.KeyGen()
secretKey = keys.secretKey
publicKey = keys.publicKey
cc.EvalMultKeyGen(keys.secretKey)
cc.EvalBootstrapKeyGen(keys.secretKey, batch_size)
### Encryption ###
plain_a = [0.00002]*4
plain_b = [0.00001, 0.001, 0.1, 1]
a = cc.Encrypt(publicKey, cc.MakeCKKSPackedPlaintext(plain_a*((int)(batch_size/4))))
b = cc.Encrypt(publicKey, cc.MakeCKKSPackedPlaintext(plain_b*((int)(batch_size/4))))
scale = 100_000
### Division ###
printCiph("Denominator", b)
values_sc = cc.EvalMult(b, scale)
printCiph("Scaled denom.", values_sc)
res = cc.EvalChebyshevFunction(lambda x: 1 / x, values_sc, 1, scale, 2031)
printCiph("Reciprocal", res)
denom = cc.EvalMult(res, scale)
printCiph("Rescaled reciprocal", denom)
print("\n")
result = cc.EvalMult(a, denom)
printCiph("Division result", result)
print("Plain result:", [a/b for a, b in zip(plain_a, plain_b)])