57
57
#*****************************************************************************/
58
58
59
59
from sage .functions .log import exp
60
- from sage .functions .other import ceil
61
60
from sage .rings .real_mpfr import RealField
62
61
from sage .rings .real_mpfr import RR
63
62
from sage .rings .integer_ring import ZZ
64
63
from sage .rings .rational_field import QQ
65
64
from .discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler
66
65
from sage .structure .sage_object import SageObject
67
- from sage .matrix .constructor import matrix , identity_matrix
66
+ from sage .misc .functional import sqrt
67
+ from sage .symbolic .constants import pi
68
+ from sage .matrix .constructor import matrix
68
69
from sage .modules .free_module import FreeModule
69
70
from sage .modules .free_module_element import vector
70
71
@@ -156,7 +157,7 @@ def compute_precision(precision, sigma):
156
157
157
158
INPUT:
158
159
159
- - ``precision`` - an integer `> 53` nor ``None``.
160
+ - ``precision`` - an integer `>= 53` nor ``None``.
160
161
- ``sigma`` - if ``precision`` is ``None`` then the precision of
161
162
``sigma`` is used.
162
163
@@ -185,19 +186,20 @@ def compute_precision(precision, sigma):
185
186
precision = max (53 , precision )
186
187
return precision
187
188
188
- def _normalisation_factor_zz (self , tau = 3 ):
189
+ def _normalisation_factor_zz (self , tau = None , prec = None ):
189
190
r"""
190
- This function returns an approximation of `∑_{x ∈ \ZZ^n }
191
+ This function returns an approximation of `∑_{x ∈ B }
191
192
\exp(-|x|_2^2/(2σ²))`, i.e. the normalisation factor such that the sum
192
- over all probabilities is 1 for `\ZZⁿ`.
193
-
194
- If this ``self.B`` is not an identity matrix over `\ZZ` a
195
- ``NotImplementedError`` is raised.
193
+ over all probabilities is 1 for `B`, via Poisson summation.
196
194
197
195
INPUT:
198
196
199
- - ``tau`` -- all vectors `v` with `|v|_∞ ≤ τ·σ` are enumerated
200
- (default: ``3``).
197
+ - ``tau`` -- (default: ``None``) all vectors `v` with `|v|_2^2 ≤ τ·σ`
198
+ are enumerated; if none is provided, enumerate vectors with
199
+ increasing norm until the sum converges to given precision. For high
200
+ dimension lattice, this is recommended.
201
+
202
+ - ``prec`` -- (default: ``None``) Passed to :meth:`compute_precision`
201
203
202
204
EXAMPLES::
203
205
@@ -206,7 +208,7 @@ def _normalisation_factor_zz(self, tau=3):
206
208
sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma)
207
209
sage: f = D.f
208
210
sage: c = D._normalisation_factor_zz(); c
209
- 15.528. ..
211
+ 15.7496 ..
210
212
211
213
sage: from collections import defaultdict
212
214
sage: counter = defaultdict(Integer)
@@ -228,15 +230,73 @@ def _normalisation_factor_zz(self, tau=3):
228
230
sage: while v not in counter: add_samples(1000)
229
231
230
232
sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.2: add_samples(1000) # long time
233
+
234
+ sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 0.5)
235
+ sage: D._normalisation_factor_zz(tau=3)
236
+ 3.1653...
237
+ sage: D._normalisation_factor_zz()
238
+ 6.8249...
239
+ sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 1000)
240
+ sage: round(D._normalisation_factor_zz(prec=100))
241
+ 1558545456544038969634991553
242
+
243
+ sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1], [3, -4, 2]])
244
+ sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3)
245
+
246
+ sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1]])
247
+ sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3)
231
248
"""
232
- if self .B != identity_matrix (ZZ , self .B .nrows ()):
233
- raise NotImplementedError ("This function is only implemented when B is an identity matrix." )
234
249
235
- f = self .f
236
- n = self .B .ncols ()
250
+ # If σ > 1:
251
+ # We use the Fourier transform g(t) of f(x) = exp(-k^2 / 2σ^2), but
252
+ # taking the norm of vector t^2 as input, and with norm_factor factored.
253
+ # If σ ≤ 1:
254
+ # The formula in docstring converges quickly since it has -1 / σ^2 in
255
+ # the exponent
256
+ def f (x ):
257
+ # Fun fact: If you remove this R() and delay the call to return,
258
+ # It might give an error due to precision error. For example,
259
+ # RR(1 + 100 * exp(-5.0 * pi^2)) == 0
260
+ if sigma > 1 :
261
+ return R (exp (- pi ** 2 * (2 * sigma ** 2 ) * x ))
262
+ return R (exp (- x / (2 * sigma ** 2 )))
263
+
264
+ if self .B != 1 :
265
+ # TODO: Implement
266
+ raise NotImplementedError ("Implement" )
267
+
237
268
sigma = self ._sigma
238
- return sum (f (x ) for x in _iter_vectors (n , - ceil (tau * sigma ),
239
- ceil (tau * sigma )))
269
+ prec = DiscreteGaussianDistributionLatticeSampler .compute_precision (
270
+ prec , sigma
271
+ )
272
+ R = RealField (prec = prec )
273
+ if sigma > 1 :
274
+ B = self .B
275
+ # TODO: Take B dual
276
+ raise NotImplementedError ("oh no" )
277
+ norm_factor = (sigma * sqrt (2 * pi ))** self .B .ncols ()
278
+ else :
279
+ B = self .B
280
+ norm_factor = 1
281
+
282
+ # qfrep computes theta series of a quadratic form, which is *half* the
283
+ # generating function of number of vectors with given norm (and no 0)
284
+ if tau is not None :
285
+ freq = self .Q .__pari__ ().qfrep (tau * sigma , 0 )
286
+ res = R (1 )
287
+ for x , fq in enumerate (freq ):
288
+ res += 2 * ZZ (fq ) * f (x + 1 )
289
+ return R (norm_factor * res )
290
+
291
+ res = R (1 )
292
+ bound = 0
293
+ while True :
294
+ bound += 1
295
+ cnt = ZZ (self .Q .__pari__ ().qfrep (bound , 0 )[bound - 1 ])
296
+ inc = 2 * cnt * f (bound )
297
+ if cnt > 0 and res == res + inc :
298
+ return R (norm_factor * res )
299
+ res += inc
240
300
241
301
def __init__ (self , B , sigma = 1 , c = None , precision = None ):
242
302
r"""
@@ -262,7 +322,7 @@ def __init__(self, B, sigma=1, c=None, precision=None):
262
322
sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma)
263
323
sage: f = D.f
264
324
sage: c = D._normalisation_factor_zz(); c
265
- 56.2162803067524
325
+ 56.5486677646162
266
326
267
327
sage: from collections import defaultdict
268
328
sage: counter = defaultdict(Integer)
0 commit comments