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.
- 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
|