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