Skip to content

Commit

Permalink
Rename density -> _density, improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ludopulles committed Sep 25, 2024
1 parent 956f2ee commit 66228de
Showing 1 changed file with 49 additions and 30 deletions.
79 changes: 49 additions & 30 deletions estimator/nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from copy import copy
from dataclasses import dataclass

from sage.all import binomial, ceil, exp, log, oo, parent, pi, QQ, RealField, RR, sqrt
from sage.all import binomial, ceil, exp, floor, log, oo, parent, pi, QQ, RealField, RR, sqrt


def stddevf(sigma):
Expand Down Expand Up @@ -85,12 +85,12 @@ class NoiseDistribution:
**without** specifying the dimension `n` and `m` for secret/error respectively!
These are initialized, when constructing the LWEParameters object.
"""
stddev: float = 0
mean: float = 0
n: int = None
bounds: tuple = (-oo, oo)
density: float = 1.0 # hamming_weight() / n.
is_Gaussian_like: bool = False
n: int = None # dimension of noise
mean: float = 0 # expectation value
stddev: float = 0 # standard deviation (square root of variance)
bounds: tuple = (-oo, oo) # range in which each coefficient is sampled with high probability
is_Gaussian_like: bool = False # whether the distribution "decays like a gaussian"
_density: float = 1.0 # proportion of nonzero coefficients in a sample

def __lt__(self, other):
"""
Expand Down Expand Up @@ -165,14 +165,13 @@ def __hash__(self):

def __len__(self):
"""
Dimension of this noise distribution, i.e. number of coefficients that gets sampled.
EXAMPLE::
>>> from estimator import *
>>> D = ND.SparseTernary(128, 128, 1024)
>>> len(D)
>>> len(ND.SparseTernary(128, n=1024))
1024
>>> int(round(len(D) * float(D.density)))
256
"""
if self.n is None:
Expand All @@ -182,6 +181,8 @@ def __len__(self):
def resize(self, new_n):
"""
Return an altered distribution having a dimension `new_n`.
:param int new_n: new dimension to change to
"""
new_self = copy(self)
new_self.n = new_n
Expand All @@ -191,8 +192,16 @@ def resize(self, new_n):
def hamming_weight(self):
"""
The number of non-zero coefficients in this distribution
EXAMPLE::
>>> from estimator import *
>>> ND.SparseTernary(128, n=1024).hamming_weight
256
>>> ND.SparseTernary(128, 64, 1024).hamming_weight
192
"""
return round(len(self) * float(self.density))
return round(len(self) * float(self._density))

@property
def is_bounded(self):
Expand All @@ -208,7 +217,7 @@ def is_sparse(self):
Note: 1/2 might be considered somewhat arbitrary.
"""
# NOTE: somewhat arbitrary
return self.density < 0.5
return self._density < 0.5

def support_size(self, fraction=1.0):
raise NotImplementedError("support_size")
Expand All @@ -230,11 +239,17 @@ class DiscreteGaussian(NoiseDistribution):
gaussian_tail_prob: float = 1 - 2 * exp(-4 * pi)

def __init__(self, stddev, mean=0, n=None):
super().__init__(stddev=stddev, mean=mean, n=n, is_Gaussian_like=True)

b_val = oo if n is None else ceil(log(n, 2) * stddev)
self.bounds = (-b_val, b_val)
self.density = max(0.0, 1 - RR(1 / sigmaf(stddev)))
density = max(0.0, 1 - RR(1 / sigmaf(stddev))) # NOTE: approximation that is accurate for large stddev.

super().__init__(
n=n,
mean=mean,
stddev=stddev,
bounds=(-b_val, b_val),
_density=density,
is_Gaussian_like=True,
)

def support_size(self, fraction=1.0):
"""
Expand Down Expand Up @@ -287,12 +302,15 @@ class CenteredBinomial(NoiseDistribution):
D(σ=2.00)
"""
def __init__(self, eta, n=None):
density = 1 - binomial(2 * eta, eta) * 2 ** (-2 * eta)

super().__init__(
density=1 - binomial(2 * eta, eta) * 2 ** (-2 * eta),
n=n,
mean=0,
stddev=RR(sqrt(eta / 2.0)),
is_Gaussian_like=True,
bounds=(-eta, eta),
n=n,
_density=density,
is_Gaussian_like=True,
)

def support_size(self, fraction=1.0):
Expand All @@ -314,7 +332,7 @@ def support_size(self, fraction=1.0):

class Uniform(NoiseDistribution):
"""
Uniform distribution ∈ ``[a,b]``, endpoints inclusive.
Uniform distribution ∈ ``ZZ ∩ [a, b]``, endpoints inclusive.
EXAMPLE::
Expand All @@ -325,16 +343,17 @@ class Uniform(NoiseDistribution):
D(σ=2.29, μ=-0.50)
"""
def __init__(self, a, b, n=None):
a, b = int(ceil(a)), int(floor(b))
if b < a:
raise ValueError(f"upper limit must be larger than lower limit but got: {b} < {a}")

m = b - a + 1

super().__init__(
stddev=RR(sqrt((m**2 - 1) / 12)),
n=n,
mean=RR((a + b) / 2),
stddev=RR(sqrt((m**2 - 1) / 12)),
bounds=(a, b),
density=(1 - 1 / m if a <= 0 and b >= 0 else 1),
n=n,
_density=(1 - 1 / m if a <= 0 and b >= 0 else 1),
)

def __hash__(self):
Expand All @@ -354,7 +373,7 @@ def support_size(self, fraction=1.0):
EXAMPLE::
>>> from estimator import *
>>> ND.Uniform(-3,3, 64).support_size(0.99)
>>> ND.Uniform(-3, 3, 64).support_size(0.99)
1207562882759477428726191443614714994252339953407098880
"""
# TODO: this might be suboptimal/inaccurate for binomial distribution
Expand All @@ -364,7 +383,7 @@ def support_size(self, fraction=1.0):

def UniformMod(q, n=None):
"""
Uniform mod ``q``, with balanced representation, i.e. values in ZZ ∩ [q/2, q/2).
Uniform mod ``q``, with balanced representation, i.e. values in ZZ ∩ [-q/2, q/2).
EXAMPLE::
Expand Down Expand Up @@ -410,11 +429,11 @@ def __init__(self, p, m=None, n=None):
stddev = sqrt(density - mean**2)

super().__init__(
stddev=stddev,
n=n,
mean=mean,
density=density,
stddev=stddev,
bounds=(0 if m == 0 else -1, 0 if p == 0 else 1),
n=n
_density=density,
)

def __hash__(self):
Expand Down

0 comments on commit 66228de

Please sign in to comment.