faster_prime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,458 @@
1
+ require "faster_prime/utils"
2
+
3
+ module FasterPrime
4
+ module PrimeFactorization
5
+ module_function
6
+
7
+ # Factorize an integer
8
+ def prime_factorization(n)
9
+ return enum_for(:prime_factorization, n) unless block_given?
10
+
11
+ return if n == 0
12
+ if n < 0
13
+ yield [-1, 1]
14
+ n = -n
15
+ end
16
+
17
+ SMALL_PRIMES.each do |prime|
18
+ if n % prime == 0
19
+ c = 0
20
+ begin
21
+ n /= prime
22
+ c += 1
23
+ end while n % prime == 0
24
+ yield [prime, c]
25
+ end
26
+ if prime * prime > n
27
+ yield [n, 1] if n > 1
28
+ return
29
+ end
30
+ return if n == 1
31
+ end
32
+
33
+ if PrimalityTest.prime?(n)
34
+ yield [n, 1]
35
+ else
36
+ d = nil
37
+ until d
38
+ [PollardRho, MPQS].each do |algo|
39
+ begin
40
+ d = algo.try_find_factor(n)
41
+ rescue Failed
42
+ else
43
+ break
44
+ end
45
+ end
46
+ end
47
+
48
+ pe = Hash.new(0)
49
+ prime_factorization(n / d) {|p, e| pe[p] += e }
50
+ prime_factorization(d) {|p, e| pe[p] += e }
51
+ pe.keys.sort.each do |p|
52
+ yield [p, pe[p]]
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class Failed < StandardError
59
+ end
60
+
61
+ # An implementation of Pollard's rho algorithm (Brent's variant)
62
+ #
63
+ # https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm#Variants
64
+ module PollardRho
65
+ module_function
66
+
67
+ def self.try_find_factor(n)
68
+ x0 = rand(n)
69
+ y = x0
70
+ m = Math.log(n, 2).ceil
71
+ c = 2
72
+ r = q = 1
73
+ while true
74
+ x = y
75
+ r.times { y = (y * y + c) % n }
76
+ k = 0
77
+ while true
78
+ ys = y
79
+ [m, r - k].min.times do
80
+ y = (y * y + c) % n
81
+ q = (q * (x - y)) % n
82
+ end
83
+ g = q.gcd(n)
84
+ k += m
85
+ break if k >= r || g > 1
86
+ end
87
+ r *= 2
88
+ break if g > 1
89
+ end
90
+ if g == n
91
+ while true
92
+ ys = (ys * ys + c) % n
93
+ g = (x - ys).gcd(n)
94
+ break if g > 1
95
+ end
96
+ end
97
+ if g == n
98
+ raise Failed, "failed to find a factor: #{ n }"
99
+ else
100
+ g
101
+ end
102
+ end
103
+ end
104
+
105
+ # An implementation of MPQS factoring algorithm.
106
+ #
107
+ # R. D. Silverman,
108
+ # "The Multiple Polynomial Quadratic Sieve." (1987)
109
+ # http://www.ams.org/journals/mcom/1987-48-177/S0025-5718-1987-0866119-8/S0025-5718-1987-0866119-8.pdf
110
+
111
+ class MPQS
112
+ # MPQS parameters
113
+ # (N digits, factor base size, sieve interval, and large prime tolerance)
114
+ PARAMETERS = [
115
+ [24, 100, 250, 1.5 ],
116
+ [30, 200, 1_250, 1.5 ],
117
+ [36, 400, 1_250, 1.75],
118
+ [42, 900, 2_500, 2.0 ],
119
+ [48, 1200, 7_000, 2.0 ],
120
+ [54, 2000, 12_500, 2.2 ],
121
+ [60, 3000, 17_500, 2.4 ],
122
+ [66, 4500, 25_000, 2.6 ],
123
+ ]
124
+
125
+ SMALL_PRIMES = [2, 3, 5, 7, 11]
126
+
127
+ # Generates a table for finding k such that kN = 1 mod 4 and that kN is
128
+ # rich in small quadratic residues, i.e., (kN | p) = 1 for all p in
129
+ # SMALL_PRIMES.
130
+ def self.k_table
131
+ return @@k_table if defined?(@@k_table)
132
+
133
+ k_table = { 1 => {}, 3 => {} }
134
+ Sieve.each do |p|
135
+ next if p <= SMALL_PRIMES.last
136
+ state = SMALL_PRIMES.map {|q| Utils.kronecker(p, q) }
137
+ k_table[p % 4][state] ||= p
138
+ break if k_table.all? {|r, t| t.size == 2 ** SMALL_PRIMES.size }
139
+ end
140
+
141
+ @@k_table = k_table
142
+ end
143
+
144
+ def self.try_find_factor(n)
145
+ new(n).try_find_factor
146
+ end
147
+
148
+ S = 65536
149
+
150
+ def initialize(n)
151
+ @n = n
152
+
153
+ if @n <= 1 || PrimalityTest.prime?(@n)
154
+ @trivial_factor = 1
155
+ return
156
+ end
157
+ SMALL_PRIMES.each do |p|
158
+ if @n % p == 0
159
+ @trivial_factor = p
160
+ return
161
+ end
162
+ end
163
+
164
+ m = Utils.integer_square_root(n)
165
+ if m * m == n
166
+ @trivial_factor = m
167
+ return
168
+ end
169
+
170
+ # select parameters
171
+ # @k: a multiplier
172
+ @k = MPQS.k_table[@n % 4][SMALL_PRIMES.map {|q| Utils.kronecker(@n, q) }]
173
+ @kn = @k * @n
174
+
175
+ # @f: the size of the factor base
176
+ # @m: the length of the sieve interval (2M+1)
177
+ # @t: the large prime tolerance
178
+ _digit, @f, @m, @t =
179
+ PARAMETERS.find {|d,| Math.log(@n, 10) < d } || PARAMETERS.last
180
+
181
+ # compute factor base
182
+ # @ps: the factor base
183
+ # @log_ps: log of each factor base
184
+ # @sqrts: sqrt(kN) mod p
185
+ @ps, @log_ps, @sqrts = [], [], []
186
+ Sieve.each do |p|
187
+ if @n % p == 0
188
+ @trivial_factor = p
189
+ return
190
+ end
191
+ if p >= 3 && Utils.kronecker(@kn, p) == 1
192
+ @ps << p
193
+ @log_ps << (Math.log(p) * S).floor
194
+ @sqrts << Utils.mod_sqrt(@kn, p)
195
+ break if @ps.size >= @f
196
+ end
197
+ end
198
+ @pmax = @ps.last # max factor base
199
+
200
+ # for enumerating coefficients
201
+ @base_d = Math.sqrt(Math.sqrt(@kn) / (Math.sqrt(2) * @m)).floor | 3
202
+ @diff_d = 0 # 0, 4, -4, 8, -8, 12, -12, ...
203
+
204
+ # sieve table
205
+ @ws = [0] * (2 * @m + 1)
206
+
207
+ # a test value for sieve
208
+ @test_value = (Math.log(@m * Math.sqrt(@kn) / @pmax ** @t) * S).round
209
+
210
+ # relations found
211
+ @incomplete_relations = {}
212
+
213
+ # for gaussian elimination
214
+ @elim = GaussianElimination.new(@ps.size + 2)
215
+ end
216
+
217
+ # try to find any factor (not necessarily a prime)
218
+ def try_find_factor
219
+ return @trivial_factor if defined?(@trivial_factor)
220
+
221
+ catch(:factor_found) do
222
+ while true
223
+ p :foo
224
+ select_q
225
+ solve_roots
226
+ sieve
227
+ @ws.each_with_index do |w, i|
228
+ check_relation(i - @m) if w > @test_value
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ # Selects Q(x) = Ax^2 + Bx + C for quadratic sieve
235
+ def select_q
236
+ # find a prime D such that (kN | D) = 1 and D = 3 mod 4
237
+ begin
238
+ @d = @base_d + @diff_d
239
+ @diff_d = @diff_d > 0 ? -@diff_d : 4 - @diff_d
240
+ end until Utils.kronecker(@kn, @d) == 1 && PrimalityTest.probable_prime?(@d) != false && !@ps.include?(@d)
241
+
242
+ # select coefficients (see the paper)
243
+ @a = @d * @d
244
+
245
+ h0 = Utils.mod_pow(@kn, @d / 4, @d)
246
+ h1 = (@kn * h0) % @d
247
+ # assert: h1*h1 = kN mod D
248
+ # assert: h0*h1 = 1 mod D
249
+ h2 = (Utils.mod_inv(2, @d) * h0 * ((@kn - h1 * h1) / @d)) % @d
250
+ # assert: kN - h1^2 = 0 mod D
251
+
252
+ @b = (h1 + h2 * @d) % @a
253
+ @b -= @a if @b[0] == 0
254
+ # assert: B^2 = kN mod 4A
255
+
256
+ #@c = (@b * @b - @kn) / (4 * @a) # not needed
257
+
258
+ # compute real roots of Q(x) = 0
259
+ s = Utils.integer_square_root(@kn)
260
+ @real_root_1 = (-@b - s) / (2 * @a)
261
+ @real_root_2 = (-@b + s) / (2 * @a)
262
+ # x is between real roots if @real_root_1 <= x && x <= @real_root_2
263
+ end
264
+
265
+ # Computes the roots of Q(x) = 0 mod p (p in the factor base)
266
+ def solve_roots
267
+ @roots = []
268
+ @ps.zip(@sqrts) do |p, s|
269
+ a_inv = Utils.mod_inv(2 * @a, p)
270
+ x1 = (-@b + s) * a_inv % p
271
+ x2 = (-@b - s) * a_inv % p
272
+ @roots << [x1, x2]
273
+ end
274
+ end
275
+
276
+ # Performs the quadratic sieve
277
+ def sieve
278
+ @ws.fill(0)
279
+ @ps.zip(@log_ps, @roots) do |p, lp, (s1, s2)|
280
+ ((@m + s1) % p).step(2 * @m, p) {|i| @ws[i] += lp }
281
+ ((@m + s2) % p).step(2 * @m, p) {|j| @ws[j] += lp }
282
+ end
283
+ end
284
+
285
+ # Tests whether Q(x) is actually a product of factor bases
286
+ def check_relation(x)
287
+ h, qx = compute_q(x)
288
+ return if qx == 0
289
+ es, l = exponent_bitvector(qx)
290
+
291
+ # discard this x if the residue L is too big
292
+ return if l > @pmax ** @t
293
+
294
+ if l == 1
295
+ # complete relation found:
296
+ # Q(x) = p0^e0 * p1^e1 * ... * pk^ek (pi in the factor base)
297
+ qx_vec = Hash.new(0)
298
+ PrimeFactorization.prime_factorization(qx) {|p, e| qx_vec[p] += e }
299
+ collect_relation(es, h, qx_vec)
300
+
301
+ elsif @incomplete_relations[l]
302
+ # large prime procedure:
303
+ # make a complete relation by multiplying two incomplete relations
304
+ es2, h2, qx2 = @incomplete_relations[l]
305
+
306
+ # XXX: use FactoredInteger
307
+ qx_vec = Hash.new(0)
308
+ PrimeFactorization.prime_factorization(qx) {|p, e| qx_vec[p] += e }
309
+ PrimeFactorization.prime_factorization(qx2) {|p, e| qx_vec[p] += e }
310
+
311
+ collect_relation(es ^ es2, h * h2 % @kn, qx_vec)
312
+
313
+ else
314
+ @incomplete_relations[l] = [es, h, qx]
315
+ end
316
+ end
317
+
318
+ # Adds a complete relation found to gaussian elimination list
319
+ def collect_relation(es, h, q)
320
+ #p "%0#{ @ps.size + 1 }b" % es
321
+ @elim[es] = [h, q]
322
+
323
+ if @elim.size > 0.9 * @f && @elim.size % [1, @f / 100].max == 0
324
+ @elim.eliminate do |relations|
325
+ # factor candidate found
326
+ check_factor_candidate(relations)
327
+ end
328
+ end
329
+
330
+ if @elim.size > @f * 1.5
331
+ raise Failed, "failed to find a factor: #{ @n }"
332
+ end
333
+ end
334
+
335
+ # Computes and checks a factor candidate
336
+ def check_factor_candidate(relations)
337
+ # computes the factor candidate
338
+ p1 = 1
339
+ p2_vec = Hash.new(0) # XXX: use FactoredInteger
340
+ relations.each do |h, qx_vec|
341
+ p1 = p1 * h % @kn
342
+ qx_vec.each {|p, e| p2_vec[p] += e }
343
+ end
344
+ p2 = 1
345
+ p2_vec.each {|p, e| p2 = (p2 * Utils.mod_pow(p, e / 2, @kn)) % @kn }
346
+
347
+ return if p1 == p2 || (p1 + p2) % @kn == 0
348
+
349
+ factor = (p1 + p2).gcd(@n)
350
+
351
+ # check the factor candidate
352
+ if factor != 1 && factor != @n
353
+ throw :factor_found, factor
354
+ end
355
+ end
356
+
357
+ # Computes Q(x)
358
+ def compute_q(x)
359
+ h = (2 * @a * x + @b) * Utils.mod_inv(2 * @d, @kn) % @kn
360
+
361
+ q = h * h % @kn
362
+ q -= @kn if @real_root_1 <= x && x <= @real_root_2
363
+ # assert: q == @a*x*x + @b*x + @c
364
+
365
+ return h, q
366
+ end
367
+
368
+ # Factorizes n in factor base and returns an exponent bitvector
369
+ def exponent_bitvector(n)
370
+ vec = 0
371
+ if n < 0
372
+ vec = 1
373
+ n = -n
374
+ end
375
+ bit = 2
376
+ ([2] + @ps).each do |p|
377
+ e = false
378
+ while n % p == 0
379
+ n /= p
380
+ e = !e
381
+ end
382
+ vec |= bit if e
383
+ bit <<= 1
384
+ end
385
+ return vec, n
386
+ end
387
+
388
+
389
+ # Incremental gaussian elimination.
390
+ #
391
+ # A. K. Lenstra, and M. S. Manasse,
392
+ # "Compact incremental Gaussian Elimination over Z/2Z." (1988)
393
+ # http://dl.acm.org/citation.cfm?id=896266
394
+ class GaussianElimination
395
+ def initialize(m)
396
+ @n = 0
397
+ @m = m
398
+ @d = []
399
+ @e = []
400
+ @u = []
401
+ @v = []
402
+ end
403
+
404
+ def []=(es, val)
405
+ @d << es
406
+ @e << nil
407
+ @v << val
408
+ end
409
+
410
+ def size
411
+ @d.size
412
+ end
413
+
414
+ def eliminate
415
+ n2 = @d.size
416
+
417
+ @n.times do |i|
418
+ if @u[i]
419
+ @e[@u[i]] = i
420
+ @n.upto(n2 - 1) do |j|
421
+ @d[j] ^= @d[i] if @d[j][@u[i]] == 1
422
+ end
423
+ end
424
+ end
425
+
426
+ @n.upto(n2 - 1) do |j|
427
+ found = false
428
+
429
+ @m.times do |i|
430
+ if @d[j][i] == 1 && !@e[i]
431
+ @u[j] = i
432
+ @e[@u[j]] = j
433
+ @d[j] &= ~(1 << @u[j])
434
+ (j + 1).upto(n2 - 1) do |k|
435
+ @d[k] ^= @d[j] if @d[k][@u[j]] == 1
436
+ end
437
+ found = true
438
+ break
439
+ end
440
+ end
441
+
442
+ if !found
443
+ # linear dependency found
444
+ @u[j] = nil
445
+ c = {}
446
+ c[j] = true
447
+ @m.times do |k|
448
+ c[@e[k]] = true if @d[j][k] == 1
449
+ end
450
+ yield c.keys.map {|k| @v[k] }
451
+ end
452
+ end
453
+
454
+ @n = n2
455
+ end
456
+ end
457
+ end
458
+ end