Files
lattigo/circuits/float/mod1_parameters.go
2023-10-23 18:52:34 +02:00

246 lines
8.2 KiB
Go

package float
import (
"encoding/json"
"fmt"
"math"
"math/big"
"math/bits"
"github.com/tuneinsight/lattigo/v4/circuits/float/cosine"
"github.com/tuneinsight/lattigo/v4/ckks"
"github.com/tuneinsight/lattigo/v4/rlwe"
"github.com/tuneinsight/lattigo/v4/utils"
"github.com/tuneinsight/lattigo/v4/utils/bignum"
)
// Mod1Type is the type of function/approximation used to evaluate x mod 1.
type Mod1Type uint64
// Sin and Cos are the two proposed functions for Mod1Type.
// These trigonometric functions offer a good approximation of the function x mod 1 when the values are close to the origin.
const (
CosDiscrete = Mod1Type(0) // Special approximation (Han and Ki) of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r); this method requires a minimum degree of 2*(K-1).
SinContinuous = Mod1Type(1) // Standard Chebyshev approximation of (1/2pi) * sin(2pix) on the full interval
CosContinuous = Mod1Type(2) // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r) on the full interval
)
// Mod1ParametersLiteral a struct for the parameters of the mod 1 procedure.
// The x mod 1 procedure goal is to homomorphically evaluate a modular reduction by Q[0] (the first prime of the moduli chain) on the encrypted plaintext.
// This struct is consumed by `NewMod1ParametersLiteralFromLiteral` to generate the `Mod1ParametersLiteral` struct, which notably stores
// the coefficient of the polynomial approximating the function x mod Q[0].
type Mod1ParametersLiteral struct {
LevelStart int // Starting level of x mod 1
LogScale int // Log2 of the scaling factor used during x mod 1
Mod1Type Mod1Type // Chose between [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
K int // K parameter (interpolation in the range -K to K)
Mod1Degree int // Degree of f: x mod 1
DoubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
Mod1InvDegree int // Degree of f^-1: (x mod 1)^-1
}
// MarshalBinary returns a JSON representation of the the target Mod1ParametersLiteral struct on a slice of bytes.
// See `Marshal` from the `encoding/json` package.
func (evm Mod1ParametersLiteral) MarshalBinary() (data []byte, err error) {
return json.Marshal(evm)
}
// UnmarshalBinary reads a JSON representation on the target Mod1ParametersLiteral struct.
// See `Unmarshal` from the `encoding/json` package.
func (evm *Mod1ParametersLiteral) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, evm)
}
// Depth returns the depth required to evaluate x mod 1.
func (evm Mod1ParametersLiteral) Depth() (depth int) {
if evm.Mod1Type == CosDiscrete { // this method requires a minimum degree of 2*K-1.
depth += int(bits.Len64(uint64(utils.Max(evm.Mod1Degree, 2*evm.K-1))))
} else {
depth += int(bits.Len64(uint64(evm.Mod1Degree)))
}
if evm.Mod1Type != SinContinuous {
depth += evm.DoubleAngle
}
depth += int(bits.Len64(uint64(evm.Mod1InvDegree)))
return depth
}
// Mod1Parameters is a struct storing the parameters and polynomials approximating the function x mod Q[0] (the first prime of the moduli chain).
type Mod1Parameters struct {
levelStart int // starting level of the operation
LogDefaultScale int // log2 of the default scaling factor
Mod1Type Mod1Type // type of approximation for the f: x mod 1 function
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
doubleAngle int // Number of rescale and double angle formula (only applies for cos and is ignored if sin is used)
qDiff float64 // Q / 2^round(Log2(Q))
scFac float64 // 2^doubleAngle
sqrt2Pi float64 // (1/2pi)^(1.0/scFac)
mod1Poly bignum.Polynomial // Polynomial for f: x mod 1
mod1InvPoly *bignum.Polynomial // Polynomial for f^-1: (x mod 1)^-1
k float64 // interval [-k, k]
}
// LevelStart returns the starting level of the x mod 1.
func (evp Mod1Parameters) LevelStart() int {
return evp.levelStart
}
// ScalingFactor returns scaling factor used during the x mod 1.
func (evp Mod1Parameters) ScalingFactor() rlwe.Scale {
return rlwe.NewScale(math.Exp2(float64(evp.LogDefaultScale)))
}
// ScFac returns 1/2^r where r is the number of double angle evaluation.
func (evp Mod1Parameters) ScFac() float64 {
return evp.scFac
}
// MessageRatio returns the pre-set ratio Q[0]/|m|.
func (evp Mod1Parameters) MessageRatio() float64 {
return float64(uint(1 << evp.LogMessageRatio))
}
// K return the sine approximation range.
func (evp Mod1Parameters) K() float64 {
return evp.k * evp.scFac
}
// QDiff return Q[0]/ClosetPow2
// This is the error introduced by the approximate division by Q[0].
func (evp Mod1Parameters) QDiff() float64 {
return evp.qDiff
}
// NewMod1ParametersFromLiteral generates an Mod1Parameters struct from the Mod1ParametersLiteral struct.
// The Mod1Parameters struct is to instantiates a Mod1Evaluator, which homomorphically evaluates x mod 1.
func NewMod1ParametersFromLiteral(params ckks.Parameters, evm Mod1ParametersLiteral) (Mod1Parameters, error) {
var mod1InvPoly *bignum.Polynomial
var mod1Poly bignum.Polynomial
var sqrt2pi float64
doubleAngle := evm.DoubleAngle
if evm.Mod1Type == SinContinuous {
doubleAngle = 0
}
scFac := math.Exp2(float64(doubleAngle))
K := float64(evm.K) / scFac
Q := params.Q()[0]
qDiff := float64(Q) / math.Exp2(math.Round(math.Log2(float64(Q))))
if evm.Mod1InvDegree > 0 {
sqrt2pi = 1.0
coeffs := make([]complex128, evm.Mod1InvDegree+1)
coeffs[1] = 0.15915494309189535 * complex(qDiff, 0)
for i := 3; i < evm.Mod1InvDegree+1; i += 2 {
coeffs[i] = coeffs[i-2] * complex(float64(i*i-4*i+4)/float64(i*i-i), 0)
}
p := bignum.NewPolynomial(bignum.Monomial, coeffs, nil)
mod1InvPoly = &p
mod1InvPoly.IsEven = false
for i := range mod1InvPoly.Coeffs {
if i&1 == 0 {
mod1InvPoly.Coeffs[i] = nil
}
}
} else {
sqrt2pi = math.Pow(0.15915494309189535*qDiff, 1.0/scFac)
}
switch evm.Mod1Type {
case SinContinuous:
mod1Poly = bignum.ChebyshevApproximation(sin2pi, bignum.Interval{
Nodes: evm.Mod1Degree,
A: *new(big.Float).SetPrec(cosine.EncodingPrecision).SetFloat64(-K),
B: *new(big.Float).SetPrec(cosine.EncodingPrecision).SetFloat64(K),
})
mod1Poly.IsEven = false
for i := range mod1Poly.Coeffs {
if i&1 == 0 {
mod1Poly.Coeffs[i] = nil
}
}
case CosDiscrete:
mod1Poly = bignum.NewPolynomial(bignum.Chebyshev, cosine.ApproximateCos(evm.K, evm.Mod1Degree, float64(uint(1<<evm.LogMessageRatio)), int(evm.DoubleAngle)), [2]float64{-K, K})
mod1Poly.IsOdd = false
for i := range mod1Poly.Coeffs {
if i&1 == 1 {
mod1Poly.Coeffs[i] = nil
}
}
case CosContinuous:
mod1Poly = bignum.ChebyshevApproximation(cos2pi, bignum.Interval{
Nodes: evm.Mod1Degree,
A: *new(big.Float).SetPrec(cosine.EncodingPrecision).SetFloat64(-K),
B: *new(big.Float).SetPrec(cosine.EncodingPrecision).SetFloat64(K),
})
mod1Poly.IsOdd = false
for i := range mod1Poly.Coeffs {
if i&1 == 1 {
mod1Poly.Coeffs[i] = nil
}
}
default:
return Mod1Parameters{}, fmt.Errorf("invalid Mod1Type")
}
sqrt2piBig := new(big.Float).SetFloat64(sqrt2pi)
for i := range mod1Poly.Coeffs {
if mod1Poly.Coeffs[i] != nil {
mod1Poly.Coeffs[i][0].Mul(mod1Poly.Coeffs[i][0], sqrt2piBig)
mod1Poly.Coeffs[i][1].Mul(mod1Poly.Coeffs[i][1], sqrt2piBig)
}
}
return Mod1Parameters{
levelStart: evm.LevelStart,
LogDefaultScale: evm.LogScale,
Mod1Type: evm.Mod1Type,
LogMessageRatio: evm.LogMessageRatio,
doubleAngle: doubleAngle,
qDiff: qDiff,
scFac: scFac,
sqrt2Pi: sqrt2pi,
mod1Poly: mod1Poly,
mod1InvPoly: mod1InvPoly,
k: K,
}, nil
}
func sin2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
return bignum.Sin(y)
}
func cos2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
y = bignum.Cos(y)
return y
}