from math import factorial as fac from math import ceil, erf, sqrt, exp def build_Gaussian_law(sigma, t): D = {} for i in range(0, t + 1): D[i] = exp(-i ** 2 / (2 * sigma ** 2)) D[-i] = D[i] normalization = sum([D[i] for i in D]) for i in D: D[i] = D[i] / normalization assert abs(sum([D[i] for i in range(-t, t + 1)]) - 1.) <= 10 ** -10 return D def gaussian_center_weight(sigma, t): """ Weight of the gaussian of std deviation s, on the interval [-t, t] :param x: (float) :param y: (float) :returns: erf( t / (sigma*sqrt 2) ) """ return erf(t / (sigma * sqrt(2.))) def binomial(x, y): """ Binomial coefficient :param x: (integer) :param y: (integer) :returns: y choose x """ try: binom = fac(x) // fac(y) // fac(x - y) except ValueError: binom = 0 return binom def centered_binomial_pdf(k, x): """ Probability density function of the centered binomial law of param k at x :param k: (integer) :param x: (integer) :returns: p_k(x) """ return binomial(2 * k, x + k) / 2.**(2 * k) def build_centered_binomial_law(k): """ Construct the binomial law as a dictionnary :param k: (integer) :param x: (integer) :returns: A dictionnary {x:p_k(x) for x in {-k..k}} """ D = {} for i in range(-k, k + 1): D[i] = centered_binomial_pdf(k, i) return D def build_uniform_law(p): """ Construct the binomial law as a dictionnary :param k: (integer) :param x: (integer) :returns: A dictionnary {x:p_k(x) for x in {-k..k}} """ D = {} for i in range(p): D[i - p / 2] = 1. / p return D def mod_switch(x, q, rq): """ Modulus switching (rounding to a different discretization of the Torus) :param x: value to round (integer) :param q: input modulus (integer) :param rq: output modulus (integer) """ return int(round(1. * rq * x / q) % rq) def mod_centered(x, q): """ reduction mod q, centered (ie represented in -q/2 .. q/2) :param x: value to round (integer) :param q: input modulus (integer) """ a = x % q if a < q / 2: return a return a - q def build_mod_switching_error_law(q, rq): """ Construct Error law: law of the difference introduced by switching from and back a uniform value mod q :param q: original modulus (integer) :param rq: intermediate modulus (integer) """ D = {} V = {} for x in range(q): y = mod_switch(x, q, rq) z = mod_switch(y, rq, q) d = mod_centered(x - z, q) D[d] = D.get(d, 0) + 1. / q V[y] = V.get(y, 0) + 1 return D def law_convolution(A, B): """ Construct the convolution of two laws (sum of independent variables from two input laws) :param A: first input law (dictionnary) :param B: second input law (dictionnary) """ C = {} for a in A: for b in B: c = a + b C[c] = C.get(c, 0) + A[a] * B[b] return C def law_product(A, B): """ Construct the law of the product of independent variables from two input laws :param A: first input law (dictionnary) :param B: second input law (dictionnary) """ C = {} for a in A: for b in B: c = a * b C[c] = C.get(c, 0) + A[a] * B[b] return C def clean_dist(A): """ Clean a distribution to accelerate furthe computation (drop element of the support with proba less than 2^-300) :param A: input law (dictionnary) """ B = {} for (x, y) in A.items(): if y > 2**(-300): B[x] = y return B def renormalize_dist(A): B = {} summ = sum([y for (x,y) in A.items()]) for x in A: B[x] = A[x] / summ return B def iter_law_convolution(A, i): """ compute the -ith forld convolution of a distribution (using double-and-add) :param A: first input law (dictionnary) :param i: (integer) """ D = {0: 1.0} i_bin = bin(i)[2:] # binary representation of n for ch in i_bin: D = law_convolution(D, D) D = clean_dist(D) if ch == '1': D = law_convolution(D, A) D = clean_dist(D) return D def tail_probability(D, t): ''' Probability that an drawn from D is strictly greater than t in absolute value :param D: Law (Dictionnary) :param t: tail parameter (integer) ''' s = 0 ma = max(D.keys()) if t >= ma: return 0 # Summing in reverse for better numerical precision (assuming tails are decreasing) for i in reversed(range(int(ceil(t)), ma)): s += D.get(i, 0) + D.get(-i, 0) return s