def distribution_to_tex(p): """Formats distribution for use in TeX file. Args: p: A dictionary with entries for 'distr', 'sigma', 'a', 'name' Returns: LaTeX string. """ distr, sigma, a, distr_name = p['distr'], p['sigma'], p['a'], p['name'] dg = dgauss(sigma) b = bits_needed_to_sample(distr) ret = '{} & {:.3f} &'.format(distr_name, sigma) for i in sorted(distr.iterkeys()): if i >= 0: p = int(round(distr[i] * 2**b)) if i == 0: p *= 2 ret += r'\!\!{}\!\!&'.format(p) for i in range(max(distr.iterkeys()), 12): ret += '&' divergence = renyi(distr, nonnegative_half(dg), a) ret += r' {:.1f} & ${:.2f}\times 10^{{-4}}$'.format(a, divergence * 10**4) return ret + r' \\'
def distribution_to_TeX(p): """Formats distribution for use in TeX file. Args: p: A dictionary with entries for 'distr', 'sigma', 'a', 'D' Returns: LaTeX string. """ distr, sigma, a, distr_name = p['distr'], p['sigma'], p['a'], p['D'] n = max(distr.iterkeys()) + 1 # range is [0..n) b = bits_needed_to_sample(distr) ret = "${}$ & {} & {:.2f} & ".format(distr_name, b + 1, sigma**2) for i in sorted(distr.iterkeys()): if i >= 0: p = int(round(distr[i] * 2**b)) if i == 0: p *= 2 ret += r"\!\!{}\!\!&".format(p) for i in xrange(max(distr.iterkeys()), 6): ret += "&" divergence = renyi(distr, nonnegative_half(dgauss(sigma)), a) ret += " {:.1f} & {:.7f}".format(a, divergence) return ret + r" \\"
def round_distr_opt(d, eps, a, maxsupport=None, quiet=True): """Finds a distribution of accuracy eps approximating d, minimizing their Renyi divergence of order a. Args: d: A distribution given as a dictionary. eps: The rounding parameter. eps must divide 1 evenly (typically eps is a negative power of 2). a: The order of Renyi divergence between d and its approximation. maxsupport: An upper bound on the support of the output (can be None). quiet: Suppress outputs if True. Returns: A discrete distribution encoded as a dictionary. All probabilities are multiple of eps. None if no such distribution is found. """ def round_distr(d, eps, vec, support): """Rounds a distribution in a given direction. Args: d: A distribution given as a dictionary. eps: The rounding parameter. vec: The direction of rounding as a string ('0' - down, '1' - up). support: Support of the distribution as a vector. Returns: A distribution with entries that are multiple of eps. """ # Although support represents the set of d's keys, support is a vector and # as such its order is fixed (unlike d.iter_keys()). An OrderedDict would be # just fine too. assert len(vec) == len(support) r = {} for i, ch in enumerate(vec): v = support[i] _, int_part = modf(d[v] / eps) if ch == '0': # round down if int_part > 0: r[v] = int_part * eps else: # round up r[v] = (int_part + 1) * eps return r assert valid_distr(d) assert fmod(1. / eps, 1.0) < 1E-9 cache_key = (distr_to_str(filter_negl(d)), eps, a, maxsupport) if quiet and cache_key in _cache_round_distr_opt: return _cache_round_distr_opt[cache_key] # Only keep enough elements of d to get sufficiently close to 1. d_trunc = {} s = 0. for v in sorted(d, key=d.get, reverse=True): if s > 1. - eps / 8: break s += d[v] d_trunc[v] = d[v] if maxsupport is not None and len(d_trunc) == maxsupport: break if not quiet: print( 'Truncated distribution has support {}. The truncated tail has mass ' '{}.').format(len(d_trunc), 1. - sum(d_trunc.itervalues())) support = d_trunc.keys() n = len(support) # round everything down lb = sum(round_distr(d_trunc, eps, '0' * n, support).itervalues()) # round everything up lu = sum(round_distr(d_trunc, eps, '1' * n, support).itervalues()) if lb > 1. or lu < 1.: # Fail early if not quiet: print('Rounding is not feasible.') return None best_rdiv = float('inf') best_appr = None # Enumerate all possible combinations of rounding for vec in range(2**n): binvec = bin(vec)[2:].zfill(n) appr = round_distr(d_trunc, eps, binvec, support) if valid_distr(appr): rdiv = renyi(appr, d, a) if rdiv < best_rdiv: best_rdiv = rdiv best_appr = appr if not quiet: if best_appr is None: print('The distribution cannot be rounded.') else: best_appr = filter_negl(best_appr) print( 'Optimal distribution rounded to {} has support {}. Renyi divergence ' 'of order {} is {:.5f}.').format(eps, len(best_appr), a, best_rdiv) _cache_round_distr_opt[cache_key] = best_appr return best_appr
def approximate_dgauss(sigma, samples, base_security, max_table_len, max_rand_bits, suffix='', quiet=True): """Approximates rounded Gaussian with a binomial and an optimal discrete distribution. Args: sigma: The standard deviation of the target Gaussian distribution. samples: Total number of samples per protocol run. base_security: The baseline security level, in bits (e.g., 150.34). max_table_len: Upper bound on the support of the distribution (can be None). max_rand_bits: Total number of uniformly random bits required for sampling. suffix: Suffix for printed out names. quiet: If quiet, suppress all output. Returns: (security bound, non-negative part of distribution, optimal Renyi order). """ dg = dgauss(sigma) half_dg = nonnegative_half(dg) if not quiet: print(suffix) z = sigma**2 * 2 if fmod(z, 1.) != 0: print('Skipping binomial') else: sb = sym_binomial(2 * int(z)) opt_a_sb, opt_bound_sb = opt_renyi_bound(-base_security * log(2), sb, dg, samples) print( 'Sigma = {:.3f}: Binomial distribution z = {}, security = {:.2f}, a ' '= {:.2f};').format(sigma, z, -opt_bound_sb / log(2), opt_a_sb) max_security = 0 opt_r = None opt_d = {} opt_a = None for a in orders: for random_bits in range( 1, max_rand_bits): # reserve one bit for the sign d = round_distr_opt(half_dg, 2**-random_bits, a, max_table_len, quiet=True) if d is not None: r = renyi(d, half_dg, a) security = -renyi_bound(-base_security * log(2), r * samples, a) if security > max_security: max_security = security opt_a = a opt_d = d opt_r = r if not quiet: if max_security == 0: print('Approximation is infeasible under given constraints') else: print('Security = {:.2f} a = {} Renyi divergence = {}'.format( max_security / log(2), opt_a, opt_r)) return [max_security / log(2), opt_d, opt_a]
def approximate_dgauss(sigma, samples, base_security, max_table_len, max_rand_bits, suffix="", quiet=True): """Approximates rounded Gaussian with a binomial and an optimal discrete distribution. Args: sigma: The standard deviation of the target Gaussian distribution. samples: Total number of samples per protocol run. base_security: The baseline security level, in bits (e.g., 150.34). max_table_len: Upper bound on the support of the distribution (can be None). max_rand_bits: Total number of uniformly random bits required for sampling. suffix: Suffix for printed out names. quiet: If quiet, suppress all output. Returns: Optimal rounded distribution (only the non-negative support), its security bound, and the order of Renyi divergence used to derive this bound. """ dg = dgauss(sigma) half_dg = nonnegative_half(dg) if not quiet: print suffix z = sigma**2 * 2 if fmod(z, 1.) != 0: print "Skipping binomial" else: sb = sym_binomial(2 * int(z)) opt_a_sb, opt_bound_sb = opt_renyi_bound(-base_security * log(2), sb, dg, samples) print( "Sigma = {:.3f}: Binomial distribution z = {}, security = {:.2f}, a = " "{:.2f};").format(sigma, z, -opt_bound_sb / log(2), opt_a_sb) # Constrain Renyi orders of interest to the following set for performance # and aesthetic reasons a_values = [ 1.5, 2., 5., 10., 15., 20., 25., 30., 40., 50., 75., 100., 200., 500., float("inf") ] max_security = 0 opt_r = None opt_d = {} opt_a = None for a in a_values: for random_bits in xrange( 1, max_rand_bits): # reserve one bit for the sign d = round_distr_opt(half_dg, 2**-random_bits, a, max_table_len, quiet=True) if d is not None: r = renyi(d, half_dg, a) security = -renyi_bound(-base_security * log(2), log(r) * samples, a) if security > max_security: max_security = security opt_a = a opt_d = d opt_r = r if not quiet: if max_security == 0: print "Approximation is infeasible under given constraints" else: print "Security = {:.2f} a = {} Renyi divergence = {}".format( max_security / log(2), opt_a, opt_r) return [max_security / log(2), opt_d, opt_a]