abst 0.2.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,483 @@
1
+ require 'thread'
2
+
3
+ module Abst
4
+ module_function
5
+
6
+ class MPQS
7
+ @@kronecker_table = nil
8
+ @@fixed_factor_base = [-1, 2, 3, 5, 7, 11, 13].freeze
9
+ @@fixed_factor_base_log = ([nil] + @@fixed_factor_base[1..-1].map {|p| Math.log(p)}).freeze
10
+ @@mpqs_parameter_map = [[100,20]] * 9 + [
11
+ [100, 20], # 9 -digits
12
+ [100, 21], # 10
13
+ [100, 22], # 11
14
+ [100, 24], # 12
15
+ [100, 26], # 13
16
+ [100, 29], # 14
17
+ [100, 32], # 15
18
+ [200, 35], # 16
19
+ [300, 40], # 17
20
+ [300, 60], # 18
21
+ [300, 80], # 19
22
+ [300, 100], # 20
23
+ [300, 100], # 21
24
+ [300, 120], # 22
25
+ [300, 140], # 23
26
+ [600, 160], # 24
27
+ [900, 180], # 25
28
+ [1000, 200], # 26
29
+ [1000, 220], # 27
30
+ [2000, 240], # 28
31
+ [2000, 260], # 29
32
+ [2000, 325], # 30
33
+ [2000, 355], # 31
34
+ [2000, 375], # 32
35
+ [3000, 400], # 33
36
+ [2000, 425], # 34
37
+ [2000, 550], # 35
38
+ [3000, 650], # 36
39
+ [5000, 750], # 37
40
+ [4000, 850], # 38
41
+ [4000, 950], # 39
42
+ [5000, 1000], # 40
43
+ [14000, 1150], # 41
44
+ [15000, 1300], # 42
45
+ [15000, 1600], # 43
46
+ [15000, 1900], # 44
47
+ [15000, 2200], # 45
48
+ [20000, 2500], # 46
49
+ [25000, 2500], # 47
50
+ [27500, 2700], # 48
51
+ [30000, 2800], # 49
52
+ [35000, 2900], # 50
53
+ [40000, 3000], # 51
54
+ [50000, 3200], # 52
55
+ [50000, 3500]] # 53
56
+
57
+ #@@proc_time = Hash.new(0)
58
+ #def self.get_times
59
+ # return @@proc_time
60
+ #end
61
+
62
+ def self.kronecker_table
63
+ unless @@kronecker_table
64
+ target = [3, 5, 7, 11, 13]
65
+ @@kronecker_table = 4.times.map{Hash.new}
66
+ (17..3583).each_prime do |p|
67
+ k = target.map {|b| Abst.kronecker_symbol(p, b)}
68
+ @@kronecker_table[(p & 6) >> 1][k] ||= p
69
+ end
70
+ @@kronecker_table[0][[1, 1, 1, 1, 1]] = 1
71
+ end
72
+
73
+ return @@kronecker_table
74
+ end
75
+
76
+ def initialize(n, thread_num)
77
+ #@@proc_time[:init] -= Time.now.to_i + Time.now.usec.to_f / 10 ** 6
78
+ @original_n = n
79
+ @thread_num = [thread_num, 1].max
80
+ @big_prime = {}
81
+ @big_prime_mutex = Mutex.new
82
+
83
+ decide_multiplier(n)
84
+ decide_parameter
85
+ select_factor_base
86
+ some_precomputations
87
+
88
+ @d = Abst.isqrt(Abst.isqrt(@n >> 1) / @sieve_range)
89
+ @d -= (@d & 3) + 1
90
+
91
+ @matrix_left = []
92
+ @matrix_right = []
93
+ @mask = 1
94
+ @check_list = Array.new(@factor_base_size)
95
+ #@@proc_time[:init] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6
96
+ end
97
+
98
+ def decide_multiplier(n)
99
+ t = [3, 5, 7, 11, 13].map {|p| Abst.kronecker_symbol(n, p)}
100
+ multiplier = self.class.kronecker_table[(n & 6) >> 1][t]
101
+ @n = n * multiplier
102
+ end
103
+
104
+ def decide_parameter
105
+ digit = Math.log(@n, 10).floor
106
+ parameter = @@mpqs_parameter_map[digit] ? @@mpqs_parameter_map[digit].dup : @@mpqs_parameter_map.last.dup
107
+ parameter[0] = (parameter[0] * 2).floor
108
+ @sieve_range, @factor_base_size = parameter
109
+ @sieve_range_2 = @sieve_range << 1
110
+ end
111
+
112
+ def select_factor_base
113
+ @factor_base = @@fixed_factor_base.dup
114
+ (17..INFINITY).each_prime do |p|
115
+ if 1 == Abst.kronecker_symbol(@n, p)
116
+ @factor_base.push(p)
117
+ break if @factor_base_size <= @factor_base.size
118
+ end
119
+ end
120
+ end
121
+
122
+ def some_precomputations
123
+ size = @@fixed_factor_base_log.size
124
+ @factor_base_log = @@fixed_factor_base_log + @factor_base[size..-1].map {|p| Math.log(p)}
125
+
126
+ @power_limit = Array.new(@factor_base_size)
127
+ @mod_sqrt_cache = Array.new(@factor_base_size)
128
+ 2.upto(@factor_base_size - 1) do |i|
129
+ p = @factor_base[i]
130
+ @power_limit[i] = (@factor_base_log.last / @factor_base_log[i]).floor
131
+ @mod_sqrt_cache[i] = [nil] + Abst.mod_sqrt(@n, p, @power_limit[i], true)
132
+ end
133
+
134
+ target = Math.log(@n) / 2 + Math.log(@sieve_range) - 1
135
+ @closenuf = target - 1.8 * Math.log(@factor_base.last)
136
+ end
137
+
138
+ def find_factor
139
+ if 1 == @thread_num
140
+ find_factor_single_thread
141
+ else
142
+ sieve_thread_num = [@thread_num - 2, 1].max
143
+ find_factor_multi_thread(sieve_thread_num)
144
+ end
145
+ end
146
+
147
+ def find_factor_single_thread
148
+ r_list = []
149
+ factorization = []
150
+ big_prime_sup = []
151
+
152
+ loop do
153
+ # Create polynomial
154
+ a, b, c, d = next_poly
155
+
156
+ # Sieve
157
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
158
+ sieve_rslt = sieve(a, b, c, d)
159
+ #@@proc_time[:sieve] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
160
+ next if sieve_rslt.empty?
161
+ f, big, r = eliminate_big_primes(sieve_rslt)
162
+ next if f.empty?
163
+
164
+ # Gaussian elimination
165
+ factorization += f
166
+ r_list += r
167
+ big_prime_sup += big
168
+
169
+ #@@proc_time[:gaussian] -= Time.now.to_i + Time.now.usec.to_f / 10 ** 6
170
+ eliminated = gaussian_elimination(f)
171
+ #@@proc_time[:gaussian] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6
172
+ eliminated.each do |row|
173
+ x = y = 1
174
+ f = Array.new(@factor_base_size, 0)
175
+ factorization.size.times do |i|
176
+ next if row[i] == 0
177
+ x = x * r_list[i] % @n
178
+ f = f.zip(factorization[i]).map{|e1, e2| e1 + e2}
179
+ y = y * big_prime_sup[i] % @n
180
+ end
181
+
182
+ 2.upto(@factor_base_size - 1) do |i|
183
+ y = y * Abst.power(@factor_base[i], f[i] >> 1, @n) % @n
184
+ end
185
+ y = (y << (f[1] >> 1)) % @n
186
+
187
+ z = Abst.lehmer_gcd(x - y, @original_n)
188
+ return z if 1 < z and z < @original_n
189
+ end
190
+ end
191
+ end
192
+
193
+ def find_factor_multi_thread(sieve_thread_num)
194
+ queue_poly = SizedQueue.new(sieve_thread_num)
195
+ queue_sieve_rslt = SizedQueue.new(sieve_thread_num)
196
+
197
+ # Create thread make polynomials
198
+ th_make_poly = Thread.new do
199
+ loop { queue_poly.push next_poly }
200
+ end
201
+
202
+ thg_sieve = ThreadGroup.new
203
+ # Create threads for sieve
204
+ sieve_thread_num.times do
205
+ thread = Thread.new do
206
+ loop do
207
+ a, b, c, d = queue_poly.shift
208
+
209
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
210
+ # Sieve
211
+ rslt = sieve(a, b, c, d)
212
+ #@@proc_time[:sieve] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
213
+
214
+ queue_sieve_rslt.push rslt unless rslt.empty?
215
+ end
216
+ end
217
+ thg_sieve.add thread
218
+ end
219
+
220
+ r_list = []
221
+ factorization = []
222
+ big_prime_sup = []
223
+ loop do
224
+ sieve_rslt = queue_sieve_rslt.shift
225
+ next if sieve_rslt.empty?
226
+
227
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
228
+ f, big, r = eliminate_big_primes(sieve_rslt)
229
+ next if f.empty?
230
+
231
+ #p [factorization.size, r_list.size, big_prime_sup.size]
232
+ # Gaussian elimination
233
+ factorization.concat f
234
+ r_list.concat r
235
+ big_prime_sup.concat big
236
+
237
+ eliminated = gaussian_elimination(f)
238
+ #@@proc_time[:gaussian] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
239
+ eliminated.each do |row|
240
+ x = y = 1
241
+ f = Array.new(@factor_base_size, 0)
242
+ factorization.size.times do |i|
243
+ next if row[i] == 0
244
+ x = x * r_list[i] % @n
245
+ f = f.zip(factorization[i]).map{|e1, e2| e1 + e2}
246
+ y = y * big_prime_sup[i] % @n
247
+ end
248
+
249
+ 2.upto(@factor_base_size - 1) do |i|
250
+ y = y * Abst.power(@factor_base[i], f[i] >> 1, @n) % @n
251
+ end
252
+ y = (y << (f[1] >> 1)) % @n
253
+
254
+ z = Abst.lehmer_gcd(x - y, @original_n)
255
+ return z if 1 < z and z < @original_n
256
+ end
257
+ end
258
+ ensure
259
+ thg_sieve.list.each {|th| th.kill}
260
+ th_make_poly.kill
261
+ end
262
+
263
+ def eliminate_big_primes(sieve_rslt)
264
+ sieve_rslt_with_big_prime = sieve_rslt.select{|f, re, d, r| 1 != re}
265
+ sieve_rslt.select!{|f, re, d, r| 1 == re}
266
+
267
+ temp_f = sieve_rslt.map(&:first)
268
+ temp_r = sieve_rslt.map(&:last)
269
+ temp_big = sieve_rslt.map{|f, re, d, r| d}
270
+ sieve_rslt_with_big_prime.each do |f, re, d, r|
271
+ unless @big_prime[re]
272
+ @big_prime[re] = [f, r, d]
273
+ else
274
+ temp_f << (@big_prime[re][0].zip(f).map{|e1, e2| e1 + e2})
275
+ temp_big << (re * d * @big_prime[re][2])
276
+ temp_r << (r * @big_prime[re][1])
277
+ end
278
+ end
279
+
280
+ return temp_f, temp_big, temp_r
281
+ end
282
+
283
+ # Return:: a, b,c
284
+ def next_poly
285
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
286
+ @d = d = next_d
287
+ a = d ** 2
288
+ h1 = Abst.power(@n, (d >> 2) + 1, d)
289
+ h2 = ((@n - h1 ** 2) / d) * Abst.extended_lehmer_gcd(h1 << 1, d)[0] % d
290
+ b = h1 + h2 * d
291
+ b = a - b if b.even?
292
+ c = ((b ** 2 - @n) >> 2) / a
293
+
294
+ #@@proc_time[:make_poly_2] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
295
+ return a, b, c, d
296
+ end
297
+
298
+ def next_d
299
+ d = @d + 4
300
+ if d < Abst.primes_list.last
301
+ plist = Abst.primes_list
302
+ (d..plist.last).each_prime do |p|
303
+ return p if p[1] == 1 and Abst.kronecker_symbol(@n, p) == 1
304
+ end
305
+ d += 4
306
+ end
307
+
308
+ loop do
309
+ return d if Abst.kronecker_symbol(@n, d) == 1 and Abst.power(@n, d >> 1, d) == 1
310
+ d += 4
311
+ end
312
+ end
313
+
314
+ def sieve(a, b, c, d)
315
+ a2 = a << 1
316
+ lo = -(b / a2) - @sieve_range + 1
317
+
318
+ sieve = Array.new(@sieve_range_2, 0)
319
+
320
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
321
+ # Sieve by 2
322
+ # 0.upto(@sieve_range_2 - 1) do |i|
323
+ # count = 1
324
+ # count += 1 while sieve[i][2][count] == 0
325
+ # sieve[i][1] += @factor_base_log[1] * count
326
+ # end
327
+
328
+ # Sieve by 3, 5, 7, 11, ...
329
+ # 2.upto(@factor_base_size - 1) do |i|
330
+ 4.upto(@factor_base_size - 1) do |i|
331
+ p = @factor_base[i]
332
+ a_inverse = Abst.extended_lehmer_gcd(a2, p ** @power_limit[i])[0]
333
+ pe = 1
334
+ e = 1
335
+
336
+ power_limit_i = @power_limit[i]
337
+ factor_base_log_i = @factor_base_log[i]
338
+ mod_sqrt_cache_i = @mod_sqrt_cache[i]
339
+ while e <= power_limit_i
340
+ pe *= p
341
+ sqrt = mod_sqrt_cache_i[e]
342
+
343
+ t = sqrt
344
+ s = ((t - b) * a_inverse - lo) % pe
345
+ s.step(@sieve_range_2 - 1, pe) do |j|
346
+ sieve[j] += factor_base_log_i
347
+ end
348
+
349
+ t = pe - sqrt
350
+ s = ((t - b) * a_inverse - lo) % pe
351
+ s.step(@sieve_range_2 - 1, pe) do |j|
352
+ sieve[j] += factor_base_log_i
353
+ end
354
+
355
+ e += 1
356
+ end
357
+ end
358
+ #@@proc_time[:sieve_a] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
359
+
360
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
361
+ # select trial division target
362
+ td_target = []
363
+ sieve.each.with_index do |sum_of_log, idx|
364
+ if @closenuf < sum_of_log
365
+ x = idx + lo
366
+ t = a * x
367
+ td_target.push([(t << 1) + b, (t + b) * x + c])
368
+ end
369
+ end
370
+ #@@proc_time[:sieve_slct] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
371
+
372
+ # trial division on factor base
373
+ rslt = []
374
+ #temp = Time.now.to_i + Time.now.usec.to_f / 10 ** 6
375
+ td_target.each do |r, s|
376
+ f, re = trial_division_on_factor_base(s, @factor_base)
377
+ f[1] += 2
378
+ rslt.push [f, re, d, r]
379
+ end
380
+ #@@proc_time[:sieve_td] += Time.now.to_i + Time.now.usec.to_f / 10 ** 6 - temp
381
+
382
+ return rslt
383
+ end
384
+
385
+ def gaussian_elimination(m)
386
+ elim_start = @matrix_left.size
387
+ temp = Array.new(m.size)
388
+ m.size.times do |i|
389
+ temp[i] = @mask
390
+ @mask <<= 1
391
+ end
392
+ rslt = @matrix_right += temp
393
+ m = @matrix_left.concat(m.map{|row| row.reverse_each.map{|i| i[0]}})
394
+
395
+ height = m.size
396
+ width = @factor_base_size
397
+
398
+ i = 0
399
+ width.times do |j|
400
+ unless @check_list[j]
401
+ # Find non-zero entry
402
+ row = nil
403
+ elim_start.upto(height - 1) do |i2|
404
+ if 1 == m[i2][j]
405
+ row = i2
406
+ break
407
+ end
408
+ end
409
+ next unless row
410
+
411
+ @check_list[j] = row
412
+
413
+ # Swap?
414
+ if i < row
415
+ m.insert(i, m.delete_at(row))
416
+ rslt.insert(i, rslt.delete_at(row))
417
+ end
418
+
419
+ elim_start += 1
420
+ end
421
+
422
+ # Eliminate
423
+ m_i = m[i]
424
+ (row ? (row + 1) : elim_start).upto(height - 1) do |i2|
425
+ next if m[i2][j] == 0
426
+
427
+ m_i2 = m[i2]
428
+ (j + 1).upto(width - 1) do |j2|
429
+ m_i2[j2] ^= 1 if 1 == m_i[j2]
430
+ end
431
+ rslt[i2] ^= rslt[i]
432
+ end
433
+
434
+ i += 1
435
+ end
436
+
437
+ t = height - i
438
+ m.pop(t)
439
+ return rslt.pop(t)
440
+ end
441
+
442
+ def trial_division_on_factor_base(n, factor_base)
443
+ factor = Array.new(@factor_base_size, 0)
444
+ if n < 0
445
+ factor[0] = 1
446
+ n = -n
447
+ end
448
+
449
+ div_count = 1
450
+ div_count += 1 while n[div_count] == 0
451
+ factor[1] = div_count
452
+ n >>= div_count
453
+
454
+ i = 2
455
+ while i < @factor_base_size
456
+ d = factor_base[i]
457
+ q, r = n.divmod(d)
458
+ if 0 == r
459
+ n = q
460
+ div_count = 1
461
+ loop do
462
+ q, r = n.divmod(d)
463
+ break unless 0 == r
464
+
465
+ n = q
466
+ div_count += 1
467
+ end
468
+
469
+ factor[i] = div_count
470
+ end
471
+
472
+ i += 1
473
+ end
474
+
475
+ return factor, n
476
+ end
477
+ end
478
+
479
+ def mpqs(n, thread_num = Abst::THREAD_NUM)
480
+ mpqs = MPQS.new(n, thread_num)
481
+ return mpqs.find_factor
482
+ end
483
+ end