faster_prime 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +5 -0
- data/README.md +68 -0
- data/Rakefile +10 -0
- data/faster_prime.gemspec +24 -0
- data/lib/faster_prime.rb +45 -0
- data/lib/faster_prime/core_ext.rb +17 -0
- data/lib/faster_prime/primality_test.rb +557 -0
- data/lib/faster_prime/prime_factorization.rb +458 -0
- data/lib/faster_prime/sieve.rb +174 -0
- data/lib/faster_prime/utils.rb +225 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1ed371cae61edc695a59c5054e619ea5ad4e9db55aedd020f4f0cb4ecd5a2d0c
|
4
|
+
data.tar.gz: b2bd47bf86e05d018bd526b91dd0b95e2a3b250fb3e7ea206acebd14179b1fd4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f7afb2a310eba71bcbbb296677265546b79f6528913ccff47cdfaca9377b482728f8949b3bdb62be085b69bb66fa38dffae0496689ef6447fde66f4c2d0d3c18
|
7
|
+
data.tar.gz: a2353577ac4598150a9edf176f7590ab8d7875236ab284bf16f7876fb581c9d2fdfeb63d43bdb67a29e64d7efc6352def2ed5798d883051e66f390468259728a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# FasterPrime
|
2
|
+
|
3
|
+
This is an alternative implementation to the standard `lib/prime.rb`. It is (almost) comapatible and faster than `lib/prime.rb`.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
* Install `faster_prime` gem.
|
8
|
+
* Replace `require "prime"` with `require "faster_prime"`.
|
9
|
+
* Replace `Prime` with `FasterPrime`.
|
10
|
+
|
11
|
+
## Benchmark
|
12
|
+
|
13
|
+
### `Integer#prime?`
|
14
|
+
|
15
|
+
|test | Prime|FasterPrime|
|
16
|
+
|:---------------------|---------:|----------:|
|
17
|
+
|`(2**31-1).prime?` | 0.00355 s| 0.000035 s|
|
18
|
+
|`(10**100+267).prime?`| *n/a* | 0.7003 s|
|
19
|
+
|
20
|
+
### `Integer#prime_division`
|
21
|
+
|
22
|
+
|test | Prime|FasterPrime|
|
23
|
+
|:--------------------------|---------:|----------:|
|
24
|
+
|`(2**59-1).prime_division` | 0.1293 s| 0.01259 s|
|
25
|
+
|`(2**256+1).prime_division`| *n/a* | 71.71 s|
|
26
|
+
|
27
|
+
### `Prime.each`
|
28
|
+
|
29
|
+
|test | Prime|FasterPrime|
|
30
|
+
|:--------------------------|---------:|----------:|
|
31
|
+
|`Prime.each until 10**7` | 0.6039 s| 0.4855 s|
|
32
|
+
|`Prime.each until 10**9` | 57.21 s| 49.75 s|
|
33
|
+
|
34
|
+
## Internal
|
35
|
+
|
36
|
+
* `Integer#prime?` uses [APR-CL primality test](https://en.wikipedia.org/wiki/Adleman%E2%80%93Pomerance%E2%80%93Rumely_primality_test).
|
37
|
+
* `Integer#prime_division` uses [Pollard's rho algorithm](https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm#Variants) and [Multiple Polynomial Quadratic Sieve](https://en.wikipedia.org/wiki/Quadratic_sieve#Multiple_polynomials).
|
38
|
+
* `Prime.each` uses [the sieve of Atkin](https://en.wikipedia.org/wiki/Sieve_of_Atkin).
|
39
|
+
|
40
|
+
## Caveat
|
41
|
+
|
42
|
+
* This library is not intended for practical use because it is written only in Ruby. If you are serious about enjoying number theory, use [PARI/GP](https://pari.math.u-bordeaux.fr/).
|
43
|
+
* I have never evaluated the library throughout. It may be slower than `lib/prime.rb` in some range.
|
44
|
+
* The author does not understand the algorithms. I just read and implemented some papers. If you find a bug, I'd appreciate if you could give me a patch :-)
|
45
|
+
|
46
|
+
## License
|
47
|
+
|
48
|
+
The MIT License (MIT)
|
49
|
+
|
50
|
+
Copyright (c) 2017 Yusuke Endoh
|
51
|
+
|
52
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
53
|
+
of this software and associated documentation files (the "Software"), to deal
|
54
|
+
in the Software without restriction, including without limitation the rights
|
55
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
56
|
+
copies of the Software, and to permit persons to whom the Software is
|
57
|
+
furnished to do so, subject to the following conditions:
|
58
|
+
|
59
|
+
The above copyright notice and this permission notice shall be included in
|
60
|
+
all copies or substantial portions of the Software.
|
61
|
+
|
62
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
63
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
64
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
65
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
66
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
67
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
68
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "faster_prime"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "faster_prime"
|
7
|
+
spec.version = FasterPrime::VERSION
|
8
|
+
spec.authors = ["Yusuke Endoh"]
|
9
|
+
spec.email = ["mame@ruby-lang.org"]
|
10
|
+
|
11
|
+
spec.summary = %q{A faster substitute for `lib/prime.rb`.}
|
12
|
+
spec.description = %q{This provides `Integer#prime?`, `Integer#prime_division`, and `Prime#each` that are almost compatible to and faster than `lib/prime.rb`.}
|
13
|
+
spec.homepage = "https://github.com/mame/faster_prime"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
data/lib/faster_prime.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "faster_prime/utils"
|
2
|
+
require "faster_prime/primality_test"
|
3
|
+
require "faster_prime/prime_factorization"
|
4
|
+
require "faster_prime/sieve"
|
5
|
+
require "faster_prime/core_ext"
|
6
|
+
|
7
|
+
module FasterPrime
|
8
|
+
VERSION = "1.0.0"
|
9
|
+
|
10
|
+
module Core
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
class SuccEnumerator < Enumerator
|
14
|
+
alias succ next
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(ubound = nil, &block)
|
18
|
+
return SuccEnumerator.new(Float::INFINITY) do |y|
|
19
|
+
Sieve.each(ubound) {|p| y << p }
|
20
|
+
end unless block_given?
|
21
|
+
|
22
|
+
Sieve.each(ubound, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prime?(n)
|
26
|
+
raise ArgumentError, "Expected an integer, got #{ n }" unless n.respond_to?(:integer?) && n.integer?
|
27
|
+
PrimalityTest.prime?(n)
|
28
|
+
end
|
29
|
+
|
30
|
+
def prime_division(n)
|
31
|
+
PrimeFactorization.prime_factorization(n).to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def int_from_prime_division(pd)
|
35
|
+
pd.inject(1) {|v, (p, e)| v * (p ** e) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.instance
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
include Core
|
44
|
+
extend Core
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Integer
|
2
|
+
def Integer.from_prime_division(pd)
|
3
|
+
FasterPrime.int_from_prime_division(pd)
|
4
|
+
end
|
5
|
+
|
6
|
+
def prime_division(generator = nil)
|
7
|
+
FasterPrime.prime_division(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def prime?
|
11
|
+
FasterPrime.prime?(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def Integer.each_prime(ubound, &block) # :yields: prime
|
15
|
+
Faster.Prime.each(ubound, &block)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,557 @@
|
|
1
|
+
require "faster_prime/utils"
|
2
|
+
|
3
|
+
module FasterPrime
|
4
|
+
module PrimalityTest
|
5
|
+
module_function
|
6
|
+
|
7
|
+
DEFAULT_RANDOM = Random.new
|
8
|
+
|
9
|
+
def prime?(n, random = DEFAULT_RANDOM)
|
10
|
+
r = probable_prime?(n, random)
|
11
|
+
return r if r != nil
|
12
|
+
return APRCL.prime?(n)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Miller-Rabin primality test
|
16
|
+
# Returns true for a prime, false for a composite, nil if unknown.
|
17
|
+
def probable_prime?(n, random = Random.new)
|
18
|
+
return n >= 2 if n <= 3
|
19
|
+
return true if n == 2 || n == 3
|
20
|
+
return true if n == 5
|
21
|
+
return false unless 30.gcd(n) == 1
|
22
|
+
return SMALL_PRIME_TABLE[n / 2] if n / 2 < SMALL_PRIME_TABLE.size
|
23
|
+
m = n % 6
|
24
|
+
return false if m != 1 && m != 5
|
25
|
+
|
26
|
+
list = DETERMINISTIC_MR_TEST_TABLE.find {|t,| n < t }
|
27
|
+
if list
|
28
|
+
ret = true
|
29
|
+
list = list.last
|
30
|
+
else
|
31
|
+
ret = nil
|
32
|
+
list = (1..20).map { random.rand(n - 2) + 1 }
|
33
|
+
end
|
34
|
+
|
35
|
+
d, s = n - 1, 0
|
36
|
+
while d.even?
|
37
|
+
d >>= 1
|
38
|
+
s += 1
|
39
|
+
end
|
40
|
+
list.each do |a|
|
41
|
+
y = Utils.mod_pow(a, d, n)
|
42
|
+
if y != 1
|
43
|
+
f = s.times do
|
44
|
+
break if y == n - 1
|
45
|
+
y = (y * y) % n
|
46
|
+
end
|
47
|
+
return false if f
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
|
54
|
+
# These numbers are taken from:
|
55
|
+
# http://code.google.com/p/sympy/
|
56
|
+
# http://primes.utm.edu/prove/prove2_3.html
|
57
|
+
DETERMINISTIC_MR_TEST_TABLE = [
|
58
|
+
[ 1_373_653, [2, 3]],
|
59
|
+
[ 170_584_961, [350, 3_958_281_543]],
|
60
|
+
[ 4_759_123_141, [2, 7, 61]],
|
61
|
+
[ 75_792_980_677, [2, 379_215, 457_083_754]],
|
62
|
+
[ 1_000_000_000_000, [2, 13, 23, 1_662_803]],
|
63
|
+
[ 2_152_302_898_747, [2, 3, 5, 7, 11]],
|
64
|
+
[ 3_474_749_660_383, [2, 3, 5, 7, 11, 13]],
|
65
|
+
[341_550_071_728_321, [2, 3, 5, 7, 11, 13, 17]],
|
66
|
+
]
|
67
|
+
|
68
|
+
# APRCL Primarity Test
|
69
|
+
#
|
70
|
+
# Henry Cohen. A course in computational algebraic number theory.
|
71
|
+
# 9.1 The Jacobi Sum Test
|
72
|
+
|
73
|
+
class APRCL
|
74
|
+
# usage:
|
75
|
+
#
|
76
|
+
# (1) simplest
|
77
|
+
#
|
78
|
+
# Leonhard::APRCL.prime?(N) #=> true or false
|
79
|
+
#
|
80
|
+
# (2) reuse-table
|
81
|
+
#
|
82
|
+
# aprcl = Leonhard::APRCL.new(B)
|
83
|
+
# aprcl.prime?(N1) #=> true or false
|
84
|
+
# aprcl.prime?(N2) #=> true or false
|
85
|
+
# aprcl.bound #=> upper bound (>= B)
|
86
|
+
#
|
87
|
+
# (3) manual-t (for test or debug)
|
88
|
+
#
|
89
|
+
# aprcl = Leonhard::APRCP.new(B)
|
90
|
+
# aprcl.set_t(t)
|
91
|
+
# aprcl.prime?(N) #=> true or false
|
92
|
+
|
93
|
+
# An internal exception for terminating the algorithm because n is composite
|
94
|
+
class Composite < StandardError
|
95
|
+
end
|
96
|
+
|
97
|
+
# An exception representing that the test has failed (highly improbable)
|
98
|
+
class Failed < StandardError
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns true if n is prime, false otherwise.
|
102
|
+
def self.prime?(n)
|
103
|
+
new(n).prime?
|
104
|
+
end
|
105
|
+
|
106
|
+
# Creates an APRCL instance to test an integer that is less than bound
|
107
|
+
def initialize(n, jacobi_sum_table: DefaultJacobiSumTable, t: nil)
|
108
|
+
# an integer in question
|
109
|
+
@n = n
|
110
|
+
|
111
|
+
# precompute n-1 and (n-1)/2 because n is normally big
|
112
|
+
@n1 = n - 1
|
113
|
+
@n1_2 = @n1 / 2
|
114
|
+
|
115
|
+
# @eta[pk]: a set of p^k-th roots of unity
|
116
|
+
@eta = {}
|
117
|
+
|
118
|
+
# a cache of Jacobi sums
|
119
|
+
@js = jacobi_sum_table
|
120
|
+
|
121
|
+
# compute e(t)
|
122
|
+
if t
|
123
|
+
raise "t must be even" if t.odd?
|
124
|
+
@t = t
|
125
|
+
@et = compute_e(@t)
|
126
|
+
raise "t is too small to test n" if @n >= @et ** 2
|
127
|
+
else
|
128
|
+
@t = find_t(@n)
|
129
|
+
@et = compute_e(@t)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Algorithm 9.1.28 (Jacobi Sum Primality test)
|
134
|
+
def prime?
|
135
|
+
return false if @n <= 1
|
136
|
+
|
137
|
+
# 1. [Check GCD]
|
138
|
+
g = @n.gcd(@t * @et)
|
139
|
+
return g == @n && PrimalityTest.prime?(g) if g != 1
|
140
|
+
|
141
|
+
# 2. [Initialize]
|
142
|
+
lp = {}
|
143
|
+
PrimeFactorization.prime_factorization(@t) do |p, |
|
144
|
+
lp[p] = false if p == 2 || Utils.mod_pow(@n, p - 1, p ** 2) == 1
|
145
|
+
end
|
146
|
+
|
147
|
+
# 3. [Loop on characters]
|
148
|
+
Utils.each_divisor(@t) do |d|
|
149
|
+
q = d + 1
|
150
|
+
next unless PrimalityTest.prime?(q)
|
151
|
+
PrimeFactorization.prime_factorization(d) do |p, k|
|
152
|
+
# 4. [Check (*beta)]
|
153
|
+
lp.delete(p) if step4(q, p, k)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# 5. [Check conditions Lp]
|
158
|
+
lp.keys.each {|p| step5(p) }
|
159
|
+
|
160
|
+
# 6. [Final trial division]
|
161
|
+
r = 1
|
162
|
+
1.upto(@t - 1) do
|
163
|
+
r = r * @n % @et
|
164
|
+
return false if @n % r == 0 && 1 < r && r < @n
|
165
|
+
end
|
166
|
+
|
167
|
+
return true
|
168
|
+
|
169
|
+
rescue Composite
|
170
|
+
return false
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def step4(q, p, k)
|
177
|
+
case
|
178
|
+
when p >= 3 then step4a(q, p, k)
|
179
|
+
when k >= 3 then step4b(q, k)
|
180
|
+
when k == 2 then step4c(q)
|
181
|
+
when k == 1 then step4d(q)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# p is odd
|
186
|
+
def step4a(q, p, k)
|
187
|
+
pk = p ** k
|
188
|
+
|
189
|
+
# s1 = J(p,q)^theta, s3 = J(p,q)^alpha
|
190
|
+
s1 = s3 = ZZeta.one(p, k)
|
191
|
+
r = @n % pk
|
192
|
+
jpq = @js.get_j(p, q)
|
193
|
+
1.upto(pk - 1) do |x|
|
194
|
+
next if x % p == 0
|
195
|
+
j = jpq.substitute(x).reduce_mod!(@n)
|
196
|
+
s1 = s1 * j.mod_pow(x, @n)
|
197
|
+
s3 = s3 * j.mod_pow(r * x / pk, @n)
|
198
|
+
end
|
199
|
+
|
200
|
+
s2 = s1.mod_pow(@n / pk, @n)
|
201
|
+
|
202
|
+
# S(p,q) = s2 J(p,q)^alpha
|
203
|
+
s = (s2 * s3).reduce_mod!(@n)
|
204
|
+
|
205
|
+
i = check_root_of_unity(p, k, pk, s)
|
206
|
+
|
207
|
+
i % p != 0 # it is a primitive root of unity
|
208
|
+
end
|
209
|
+
|
210
|
+
# p = 2, k >= 3
|
211
|
+
def step4b(q, k)
|
212
|
+
pk = 2 ** k
|
213
|
+
|
214
|
+
# s1 = J_3(q)^theta, s3 = J_3(q)^alpha
|
215
|
+
s1 = s3 = ZZeta.one(2, k)
|
216
|
+
r = @n % pk
|
217
|
+
j3, j2 = @js.get_j3_j2(q)
|
218
|
+
diff = 1
|
219
|
+
0.step(pk - 1, 4) do |x|
|
220
|
+
x, diff = x + diff, -diff # x = 8m+1 or 8m+3, i.e., 1, 3, 9, 11, ...
|
221
|
+
j = j3.substitute(x).reduce_mod!(@n)
|
222
|
+
s1 = (s1 * j.mod_pow(x , @n)).reduce_mod!(@n)
|
223
|
+
s3 = (s3 * j.mod_pow(r * x / pk, @n)).reduce_mod!(@n)
|
224
|
+
end
|
225
|
+
|
226
|
+
# S(2,q) = s2 J_3(q)^alpha if r = 8m+1 or 8m+3
|
227
|
+
# s2 J_3(q)^alpha J_2(q) if r = 8m+5 or 8m+7
|
228
|
+
s = s3 * s1.mod_pow(@n / pk, @n)
|
229
|
+
s = s * j2 if r[2] == 1 # r = 8m+5 or 8m+7
|
230
|
+
s.reduce_mod!(@n)
|
231
|
+
|
232
|
+
i = check_root_of_unity(2, k, pk, s)
|
233
|
+
|
234
|
+
i[0] == 1 && Utils.mod_pow(q, @n1_2, @n) == @n1
|
235
|
+
end
|
236
|
+
|
237
|
+
# p = 2, k = 2
|
238
|
+
def step4c(q)
|
239
|
+
s0 = @js.get_j(2, q).sqr
|
240
|
+
s1 = s0.scalar(q)
|
241
|
+
s2 = s1.mod_pow(@n / 4, @n)
|
242
|
+
# S(2,q) = s2 if n = 4m+1
|
243
|
+
# s2 J(2,q)^2 if n = 4m+3
|
244
|
+
s = (@n[1] == 0 ? s2 : s2 * s0).reduce_mod!(@n)
|
245
|
+
|
246
|
+
i = check_root_of_unity(2, 2, 4, s)
|
247
|
+
|
248
|
+
i[0] != 1 && Utils.mod_pow(q, @n1_2, @n) == @n1
|
249
|
+
end
|
250
|
+
|
251
|
+
# p = 2, k = 1
|
252
|
+
def step4d(q)
|
253
|
+
# S(2,q) = (-q)^((n-1)/2) mod N
|
254
|
+
s = Utils.mod_pow(-q, @n1_2, @n)
|
255
|
+
|
256
|
+
raise Composite if s != 1 && s != @n1
|
257
|
+
|
258
|
+
s == @n && @n[1] == 0
|
259
|
+
end
|
260
|
+
|
261
|
+
# check whether there exists a p^k-th root of unity e such that s = e mod n
|
262
|
+
def check_root_of_unity(p, k, pk, s)
|
263
|
+
unless @eta[pk]
|
264
|
+
table = {}
|
265
|
+
pk.times do |i|
|
266
|
+
table[ZZeta.monomial(p, k, i).reduce_mod!(@n)] = i
|
267
|
+
end
|
268
|
+
@eta[pk] = table
|
269
|
+
end
|
270
|
+
|
271
|
+
@eta[pk][s] || raise(Composite)
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
TRIAL = 2000
|
276
|
+
def step5(p)
|
277
|
+
bound_k = 4
|
278
|
+
|
279
|
+
# Find q and k by brute force.
|
280
|
+
# We first try to find a small k because step4 might take long time
|
281
|
+
# when k is big (>= 4). (An example case: prime?(19541) with TRIAL = 128)
|
282
|
+
# If we cannot do so, we use any k.
|
283
|
+
2.times do
|
284
|
+
q = p + 1
|
285
|
+
TRIAL.times do
|
286
|
+
if @et % q != 0 && PrimalityTest.prime?(q)
|
287
|
+
if @n % q == 0
|
288
|
+
return if @n == q
|
289
|
+
raise Composite
|
290
|
+
end
|
291
|
+
k = Utils.valuation(q - 1, p)
|
292
|
+
return if k <= bound_k && step4(q, p, k)
|
293
|
+
end
|
294
|
+
q += p
|
295
|
+
end
|
296
|
+
bound_k = Float::INFINITY
|
297
|
+
end
|
298
|
+
|
299
|
+
raise Failed, "APRCL primality test failed (highly improbable)"
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
# The pairs of t candidate and floor(log_2(e^2(t))).
|
304
|
+
# generated by tool/build-aprcl-t-table.rb
|
305
|
+
T_TABLE = [
|
306
|
+
[12, 31], [24, 33], [30, 34], [36, 54], [60, 65], [72, 68], [108, 70],
|
307
|
+
[120, 78], [180, 102], [240, 104], [360, 127], [420, 136], [540, 153],
|
308
|
+
[840, 165], [1008, 169], [1080, 178], [1200, 192], [1260, 206],
|
309
|
+
[1620, 211], [1680, 222], [2016, 225], [2160, 244], [2520, 270],
|
310
|
+
[3360, 279], [3780, 293], [5040, 346], [6480, 348], [7560, 383],
|
311
|
+
[8400, 396], [10080, 426], [12600, 458], [15120, 527], [25200, 595],
|
312
|
+
[30240, 636], [42840, 672], [45360, 684], [55440, 708], [60480, 771],
|
313
|
+
[75600, 775], [85680, 859], [100800, 893], [110880, 912], [128520, 966],
|
314
|
+
[131040, 1009], [166320, 1042], [196560, 1124], [257040, 1251],
|
315
|
+
[332640, 1375], [393120, 1431], [514080, 1483], [655200, 1546],
|
316
|
+
[665280, 1585], [786240, 1661], [831600, 1667], [917280, 1677],
|
317
|
+
[982800, 1728], [1081080, 1747], [1179360, 1773], [1285200, 1810],
|
318
|
+
[1310400, 1924], [1441440, 2001], [1663200, 2096], [1965600, 2166],
|
319
|
+
[2162160, 2321], [2751840, 2368], [2827440, 2377], [3326400, 2514],
|
320
|
+
[3341520, 2588], [3603600, 2636], [3931200, 2667], [4324320, 3028],
|
321
|
+
[5654880, 3045], [6652800, 3080], [6683040, 3121], [7207200, 3283],
|
322
|
+
[8648640, 3514], [10810800, 3725], [12972960, 3817], [14414400, 3976],
|
323
|
+
[18378360, 3980], [21621600, 4761], [36756720, 5067], [43243200, 5657],
|
324
|
+
[64864800, 5959], [73513440, 6423], [86486400, 6497],
|
325
|
+
]
|
326
|
+
|
327
|
+
# Find t such that e(t) > n^(1/2).
|
328
|
+
def find_t(n)
|
329
|
+
n_log2 = n.bit_length
|
330
|
+
|
331
|
+
# e^2(t) >= 2^(et2_log2) >= 2^n_log2 > n
|
332
|
+
t, = T_TABLE.find {|_t, et2_log2| et2_log2 >= n_log2 }
|
333
|
+
raise "too large" unless t
|
334
|
+
|
335
|
+
t
|
336
|
+
end
|
337
|
+
|
338
|
+
# Compute e(t) (the definition comes from Theorem 9.1.12)
|
339
|
+
def compute_e(t)
|
340
|
+
r = 2
|
341
|
+
Utils.each_divisor(t) do |d|
|
342
|
+
q = d + 1
|
343
|
+
r *= q ** (Utils.valuation(t, q) + 1) if PrimalityTest.prime?(q)
|
344
|
+
end
|
345
|
+
r
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
class JacobiSumTableClass
|
350
|
+
# Algorithm 9.1.27 (Precomputations)
|
351
|
+
# Note that this computes the table lazily, i.e., on demand
|
352
|
+
|
353
|
+
def initialize
|
354
|
+
@f = {} # a table of the function f(x) in 2. (1)
|
355
|
+
@j = {} # J(p,q)
|
356
|
+
@j3_j2 = {} # J3(p,q) and J2(p,q)
|
357
|
+
end
|
358
|
+
|
359
|
+
def clear
|
360
|
+
@f.clear
|
361
|
+
@j.clear
|
362
|
+
@j3_j2.clear
|
363
|
+
end
|
364
|
+
|
365
|
+
# compute a table of the function f(x) such that 1 - g^x = g^f(x),
|
366
|
+
# where g is a primitive root modulo q
|
367
|
+
def calc_f(q)
|
368
|
+
return @f[q] if @f[q]
|
369
|
+
|
370
|
+
g = Utils.primitive_root(q)
|
371
|
+
f, h = [], {}
|
372
|
+
1.upto(q - 2) {|x| h[(1 - Utils.mod_pow(g, x, q)) % q] = x }
|
373
|
+
1.upto(q - 2) {|fx| f[h[Utils.mod_pow(g, fx, q)]] = fx }
|
374
|
+
@f[q] = f
|
375
|
+
end
|
376
|
+
|
377
|
+
# compute J(p,q)
|
378
|
+
def get_j(p, q)
|
379
|
+
# assumes that p >= 3 or (p == 2 && k >= 2)
|
380
|
+
return @j[[p, q]] if @j[[p, q]]
|
381
|
+
|
382
|
+
f = calc_f(q)
|
383
|
+
k = Utils.valuation(q - 1, p)
|
384
|
+
pk = p ** k
|
385
|
+
|
386
|
+
# J(p,q) = \Sum_{1<=x<=q-2} (zeta_{p^k}^{x+f(x)})
|
387
|
+
coefs = [0] * pk
|
388
|
+
1.upto(q - 2) {|x| coefs[(x + f[x]) % pk] += 1 }
|
389
|
+
@j[[p, q]] = ZZeta.new(coefs, p, k).reduce!
|
390
|
+
end
|
391
|
+
|
392
|
+
# compute J_3(q) and J_2(q)
|
393
|
+
def get_j3_j2(q)
|
394
|
+
# assumes that p == 2 && k >= 3
|
395
|
+
return @j3_j2[q] if @j3_j2[q]
|
396
|
+
|
397
|
+
f = calc_f(q)
|
398
|
+
k = Utils.valuation(q - 1, 2)
|
399
|
+
pk = 2 ** k
|
400
|
+
|
401
|
+
# coefs3: \Sum_{1<=x<=q-2} (zeta_{2^k}^{2x+f(x)})
|
402
|
+
# coefs2: \Sum_{1<=x<=q-2} (zeta_{2^3}^{3x+f(x)})
|
403
|
+
coefs3 = [0] * pk
|
404
|
+
coefs2 = [0] * pk
|
405
|
+
1.upto(q - 2) do |x|
|
406
|
+
coefs3[(2 * x + f[x]) % pk] += 1
|
407
|
+
coefs2[(3 * x + f[x]) % 8 * (pk / 8)] += 1
|
408
|
+
end
|
409
|
+
# J_3(q) = J(2,q) coefs3, J_2(q) = ( coefs2 )^2
|
410
|
+
j3 = (get_j(2, q) * ZZeta.new(coefs3, 2, k)).reduce!
|
411
|
+
j2 = ZZeta.new(coefs2, 2, k).reduce!.sqr.reduce!
|
412
|
+
@j3_j2[q] = [j3, j2]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
DefaultJacobiSumTable = JacobiSumTableClass.new
|
416
|
+
|
417
|
+
|
418
|
+
# A group algebra Z[zeta_{p^k}]
|
419
|
+
# (linear sum of p^k-th roots of unity)
|
420
|
+
class ZZeta
|
421
|
+
def initialize(coefs, p, k)
|
422
|
+
@coefs = coefs
|
423
|
+
@p, @k = p, k
|
424
|
+
@pk1 = p ** (k - 1) # p^(k-1)
|
425
|
+
@pk = @pk1 * p # p^k
|
426
|
+
@p1pk1 = @pk - @pk1 # (p-1) p^(k-1)
|
427
|
+
if @pk != @coefs.size
|
428
|
+
raise "coefs.size (#{ coefs.size }) must be equal to #{ @p }**#{ @k }"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
attr_reader :coefs
|
433
|
+
protected :coefs
|
434
|
+
|
435
|
+
# generates 1
|
436
|
+
def self.one(p, k)
|
437
|
+
@@one[p ** k] ||= monomial(p, k, 0)
|
438
|
+
end
|
439
|
+
@@one = {}
|
440
|
+
|
441
|
+
# generates zeta_{p^k}^i
|
442
|
+
def self.monomial(p, k, i)
|
443
|
+
coefs = [0] * (p ** k)
|
444
|
+
coefs[i] = 1
|
445
|
+
ZZeta.new(coefs, p, k)
|
446
|
+
end
|
447
|
+
|
448
|
+
# for hashing
|
449
|
+
def ==(other); @coefs == other.coefs; end
|
450
|
+
def hash; @coefs.hash; end
|
451
|
+
alias eql? ==
|
452
|
+
|
453
|
+
# scalar multiplication
|
454
|
+
def scalar(n)
|
455
|
+
ZZeta.new(@coefs.map {|x| x * n }, @p, @k)
|
456
|
+
end
|
457
|
+
|
458
|
+
# polynomial multiplication
|
459
|
+
def *(other)
|
460
|
+
coefs = [0] * @pk
|
461
|
+
other.coefs.each_with_index do |oc, j|
|
462
|
+
next if oc == 0
|
463
|
+
@pk.times do |i|
|
464
|
+
coefs[(i + j) % @pk] += @coefs[i] * oc
|
465
|
+
end
|
466
|
+
end
|
467
|
+
ZZeta.new(coefs, @p, @k)
|
468
|
+
end
|
469
|
+
|
470
|
+
# polynomial power modulo n
|
471
|
+
def mod_pow(exp, n)
|
472
|
+
return ZZeta.one(@p, @k) if exp == 0
|
473
|
+
|
474
|
+
r = nil
|
475
|
+
z = reduce_mod!(n)
|
476
|
+
len = exp.bit_length
|
477
|
+
len.times do |i|
|
478
|
+
if exp[i] == 1
|
479
|
+
r = r ? (z * r).reduce_mod!(n) : z
|
480
|
+
return r if i == len - 1
|
481
|
+
end
|
482
|
+
z = z.sqr.reduce_mod!(n)
|
483
|
+
end
|
484
|
+
|
485
|
+
raise "assert not reached"
|
486
|
+
end
|
487
|
+
|
488
|
+
# polynomial square (self * self)
|
489
|
+
# assumes self is already reduced
|
490
|
+
def sqr
|
491
|
+
coefs = [0] * @pk
|
492
|
+
@p1pk1.times do |j|
|
493
|
+
oc = @coefs[j]
|
494
|
+
next if oc == 0
|
495
|
+
@p1pk1.times do |i|
|
496
|
+
coefs[(i + j) % @pk] += @coefs[i] * oc
|
497
|
+
end
|
498
|
+
end
|
499
|
+
ZZeta.new(coefs, @p, @k)
|
500
|
+
end
|
501
|
+
|
502
|
+
# generate a reminder divided by CyclotomicPolynomial(p^k)
|
503
|
+
def reduce!
|
504
|
+
# CyclotomicPolynomial(n) = \Prod_{d|n} (x^{n/d} - 1)^\moeb{d}
|
505
|
+
#
|
506
|
+
# CyclotomicPolynomial(p^k) =
|
507
|
+
# (x^{p^ k } - 1)^\moeb{p^0} (case d = p^0)
|
508
|
+
# * (x^{p^{k-1}} - 1)^\moeb{p^1} (case d = p^1)
|
509
|
+
# ...
|
510
|
+
# * (x^{p^ 0 } - 1)^\moeb{p^k} (case d = p^k)
|
511
|
+
# = (x^{p^k} - 1) / (x^{p^{k-1}} - 1)
|
512
|
+
# = \Sum_{0 <= i < p} (x^{i p^{k-1}})
|
513
|
+
@p1pk1.times do |i|
|
514
|
+
@coefs[i] -= @coefs[@p1pk1 + (i % @pk1)]
|
515
|
+
end
|
516
|
+
@pk1.times do |i|
|
517
|
+
@coefs[@p1pk1 + i] = 0
|
518
|
+
end
|
519
|
+
self
|
520
|
+
end
|
521
|
+
|
522
|
+
# reduce modulo n
|
523
|
+
def reduce_mod!(n)
|
524
|
+
@pk.times do |i|
|
525
|
+
# I would use centermod if it was built-in...
|
526
|
+
@coefs[i] = (@coefs[i] - @coefs[@p1pk1 + (i % @pk1)]) % n
|
527
|
+
end
|
528
|
+
self
|
529
|
+
end
|
530
|
+
|
531
|
+
# replace zeta with zeta^x
|
532
|
+
def substitute(x)
|
533
|
+
coefs = []
|
534
|
+
@pk.times {|i| coefs << @coefs[(x * i) % @pk] }
|
535
|
+
ZZeta.new(coefs, @p, @k)
|
536
|
+
end
|
537
|
+
|
538
|
+
def inspect
|
539
|
+
s = []
|
540
|
+
(@pk - 1).downto(0) do |i|
|
541
|
+
c = @coefs[i]
|
542
|
+
next if c == 0
|
543
|
+
s << (c > 0 ? ?+ : ?-) +
|
544
|
+
case
|
545
|
+
when i == 0 then c.abs.to_s
|
546
|
+
when c.abs == 1 then i == 1 ? "z" : "z^#{ i }"
|
547
|
+
else "#{ c.abs }#{ i == 1 ? "z" : "z^#{ i }" }"
|
548
|
+
end
|
549
|
+
end
|
550
|
+
return "0" if s.empty?
|
551
|
+
s.first[0, 1] = "" if s.first[0] == ?+
|
552
|
+
s.join
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
end
|