faster_prime 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
@@ -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.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -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
@@ -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