abst 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/abst.rb +38 -0
- data/lib/config.rb +21 -0
- data/lib/include/array.rb +45 -0
- data/lib/include/bisect.rb +95 -0
- data/lib/include/cache.rb +60 -0
- data/lib/include/compatibility.rb +3 -0
- data/lib/include/complex.rb +10 -0
- data/lib/include/float.rb +19 -0
- data/lib/include/fundamental.rb +903 -0
- data/lib/include/graph.rb +11 -0
- data/lib/include/group.rb +35 -0
- data/lib/include/integer.rb +385 -0
- data/lib/include/matrix.rb +143 -0
- data/lib/include/polynomial.rb +137 -0
- data/lib/include/prime.rb +556 -0
- data/lib/include/prime_mpqs.rb +483 -0
- data/lib/include/rational.rb +155 -0
- data/lib/include/residue.rb +27 -0
- data/lib/include/ring.rb +21 -0
- data/lib/include/sequence.rb +54 -0
- data/lib/include/set.rb +67 -0
- data/lib/include/vector.rb +90 -0
- data/test/test_all.rb +15 -0
- data/test/test_array.rb +10 -0
- data/test/test_bisect.rb +43 -0
- data/test/test_combination.rb +6 -0
- data/test/test_float.rb +13 -0
- data/test/test_fundamental.rb +406 -0
- data/test/test_group.rb +10 -0
- data/test/test_integer.rb +161 -0
- data/test/test_matrix.rb +54 -0
- data/test/test_polynomial.rb +107 -0
- data/test/test_prime.rb +153 -0
- data/test/test_prime_mpqs.rb +24 -0
- data/test/test_rational.rb +33 -0
- data/test/test_sequence.rb +55 -0
- data/test/test_set.rb +36 -0
- data/test/test_vector.rb +37 -0
- metadata +98 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module Abst
|
2
|
+
module Group
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
[:+, :-].each do |sym|
|
6
|
+
unless method_defined? sym
|
7
|
+
define_method(sym) do |other|
|
8
|
+
return add_sub(sym, other)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function
|
17
|
+
|
18
|
+
def element_order(g, group_order)
|
19
|
+
one = g.class.one
|
20
|
+
order = group_order
|
21
|
+
factor = factorize(group_order)
|
22
|
+
|
23
|
+
factor.each do |f, e|
|
24
|
+
order /= f ** e
|
25
|
+
|
26
|
+
t = g ** order
|
27
|
+
until one == t
|
28
|
+
t **= f
|
29
|
+
order *= f
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
return order
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,385 @@
|
|
1
|
+
class Integer
|
2
|
+
class << self
|
3
|
+
def zero
|
4
|
+
return 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def one
|
8
|
+
return 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def *(other)
|
12
|
+
raise ArgumentError unless other.kind_of?(self)
|
13
|
+
Abst::IntegerIdeal.new(other)
|
14
|
+
end
|
15
|
+
|
16
|
+
def /(other)
|
17
|
+
if other.kind_of?(Integer)
|
18
|
+
other = Integer * other
|
19
|
+
else
|
20
|
+
raise ArgumentError unless other.kind_of?(Abst::IntegerIdeal)
|
21
|
+
end
|
22
|
+
return residue_class(other)
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce(other)
|
26
|
+
return [self, other]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_tex
|
30
|
+
return "\\mathbb{Z}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def bit_size
|
35
|
+
return Abst.ilog2(self) + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return:: formatted string
|
39
|
+
def to_fs
|
40
|
+
return self.to_s.gsub(/(?<=\d)(?=(\d\d\d)+$)/, ' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Param:: non-negative integer self
|
44
|
+
# Return:: factorial self!
|
45
|
+
def factorial
|
46
|
+
return 2.upto(self).inject(1, &:*)
|
47
|
+
end
|
48
|
+
|
49
|
+
def inverse
|
50
|
+
return 1 / Rational(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Param:: prime
|
54
|
+
# Return:: self! mod prime
|
55
|
+
def pmod_factorial(prime)
|
56
|
+
return 2.upto(self).inject(1) {|r, i| r * i % prime}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Param:: prime
|
60
|
+
# Return:: integer f > 0 and e >= 0 s.t.
|
61
|
+
# prime ** e || self! and
|
62
|
+
# f == self! / (prime ** e) % prime
|
63
|
+
def extended_pmod_factorial(prime)
|
64
|
+
q, r = self.divmod prime
|
65
|
+
fac = q.even? ? 1 : prime - 1
|
66
|
+
fac = fac * r.pmod_factorial(prime) % prime
|
67
|
+
|
68
|
+
a = nil
|
69
|
+
e = q
|
70
|
+
if prime <= q
|
71
|
+
a, b = q.extended_pmod_factorial(prime)
|
72
|
+
e += b
|
73
|
+
else
|
74
|
+
a = q.pmod_factorial(prime)
|
75
|
+
end
|
76
|
+
fac = fac * a % prime
|
77
|
+
|
78
|
+
return fac, e
|
79
|
+
end
|
80
|
+
|
81
|
+
def combination(r)
|
82
|
+
r = self - r if self - r < r
|
83
|
+
|
84
|
+
if r <= 0
|
85
|
+
return 1 if 0 == r
|
86
|
+
return 0
|
87
|
+
end
|
88
|
+
|
89
|
+
rslt = self
|
90
|
+
2.upto(r) do |i|
|
91
|
+
rslt = rslt * (self - i + 1) / i
|
92
|
+
end
|
93
|
+
|
94
|
+
return rslt
|
95
|
+
end
|
96
|
+
alias :C :combination
|
97
|
+
|
98
|
+
def pmod_combination(r, prime)
|
99
|
+
f1, e1 = self.extended_pmod_factorial(prime)
|
100
|
+
f2, e2 = r.extended_pmod_factorial(prime)
|
101
|
+
f3, e3 = (self - r).extended_pmod_factorial(prime)
|
102
|
+
|
103
|
+
return 0 if e2 + e3 < e1
|
104
|
+
return f1 * Abst.inverse(f2 * f3, prime) % prime
|
105
|
+
end
|
106
|
+
|
107
|
+
def repeated_combination(r)
|
108
|
+
return (self + r - 1).combination(r)
|
109
|
+
end
|
110
|
+
alias :H :repeated_combination
|
111
|
+
|
112
|
+
# @param positive integer m > 1
|
113
|
+
# @return boolean whether self is power of m or not
|
114
|
+
def power_of?(m)
|
115
|
+
n = self
|
116
|
+
until 1 == n
|
117
|
+
n, r = n.divmod(m)
|
118
|
+
return false unless 0 == r
|
119
|
+
end
|
120
|
+
|
121
|
+
return true
|
122
|
+
end
|
123
|
+
|
124
|
+
# Triangle numbers are generated by the formula, T_n = n * (n + 1) / 2.
|
125
|
+
# The first ten triangle numbers are:
|
126
|
+
# 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
|
127
|
+
# Return:: integer n s.t. self == T_n if exist else false
|
128
|
+
def triangle?
|
129
|
+
return false unless r = ((self << 3) + 1).square?
|
130
|
+
return false unless (r - 1).even?
|
131
|
+
|
132
|
+
return (r - 1) >> 1
|
133
|
+
end
|
134
|
+
|
135
|
+
# Test whether self is a square number or not
|
136
|
+
# Param:: positive integer self
|
137
|
+
# Return:: square root of self if self is square else false
|
138
|
+
def square?
|
139
|
+
check = {
|
140
|
+
11=>[true, true, false, true, true, true, false, false, false, true, false],
|
141
|
+
63=>[true, true, false, false, true, false, false, true, false, true, false, false, false, false, false, false, true, false, true, false, false, false, true, false, false, true, false, false, true, false, false, false, false, false, false, false, true, true, false, false, false, false, false, true, false, false, true, false, false, true, false, false, false, false, false, false, false, false, true,false, false, false, false],
|
142
|
+
64=>[true, true, false, false, true, false, false, false, false, true, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true, false, false, true, false, false, false, false, true, false, false, false, false, false, false,false, true, false, false, false, false, false, false, false, true, false, false, false, false, false, false],
|
143
|
+
65=>[true, true, false, false, true, false, false, false, false, true, true, false, false, false, true, false, true, false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, false, false, true, true, false, false, true, true, false, false, false, false, false, false, false, false, true, false, true, false, false, false, true, true, false, false, false, false, true, false, false, true]
|
144
|
+
}
|
145
|
+
|
146
|
+
# 64
|
147
|
+
t = self & 63
|
148
|
+
return false unless check[64][t]
|
149
|
+
|
150
|
+
r = self % 45045 # == 63 * 65 * 11
|
151
|
+
[63, 65, 11].each do |c|
|
152
|
+
return false unless check[c][r % c]
|
153
|
+
end
|
154
|
+
|
155
|
+
q = Abst.isqrt(self)
|
156
|
+
return false unless q ** 2 == self
|
157
|
+
|
158
|
+
return q
|
159
|
+
end
|
160
|
+
|
161
|
+
#@slow
|
162
|
+
# Param:: positive integer self
|
163
|
+
# Return:: true if self is squarefree, false otherwise
|
164
|
+
def squarefree?
|
165
|
+
n = self
|
166
|
+
if n.even?
|
167
|
+
return false if 0 == n[1]
|
168
|
+
n >>= 1
|
169
|
+
end
|
170
|
+
|
171
|
+
return true if self <= 3
|
172
|
+
return false if self.square?
|
173
|
+
|
174
|
+
pl = Abst.primes_list
|
175
|
+
|
176
|
+
# trial division
|
177
|
+
limit = Abst.isqrt(n)
|
178
|
+
1.upto(pl.size - 1).each do |i|
|
179
|
+
d = pl[i]
|
180
|
+
return true if limit < d
|
181
|
+
|
182
|
+
if n % d == 0
|
183
|
+
n /= d
|
184
|
+
return false if n % d == 0
|
185
|
+
limit = Abst.isqrt(n)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
d = pl.last + 2
|
190
|
+
loop do
|
191
|
+
return true if limit < d
|
192
|
+
|
193
|
+
if n % d == 0
|
194
|
+
n /= d
|
195
|
+
return false if n % d == 0
|
196
|
+
limit = Abst.isqrt(n)
|
197
|
+
end
|
198
|
+
|
199
|
+
d += 2
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Pentagonal numbers are generated by the formula, P_n = n * (3 * n - 1) / 2.
|
204
|
+
# The first ten pentagonal numbers are:
|
205
|
+
# 1, 5, 12, 22, 35, 51, 70, 92, 117, 145, ...
|
206
|
+
# Return:: integer n s.t. self == P_n if exist else false
|
207
|
+
def pentagonal?
|
208
|
+
return false unless r = (24 * self + 1).square?
|
209
|
+
|
210
|
+
q, r = (1 + r).divmod(6)
|
211
|
+
return false unless 0 == r
|
212
|
+
|
213
|
+
return q
|
214
|
+
end
|
215
|
+
|
216
|
+
# Hexagonal numbers are generated by the formula, H_n = n * (2 * n - 1)
|
217
|
+
# The first ten hexagonal numbers are:
|
218
|
+
# 1, 6, 15, 28, 45, 66, 91, 120, 153, 190, ...
|
219
|
+
# Return:: integer n s.t. self == H_n if exist else false
|
220
|
+
def hexagonal?
|
221
|
+
return false unless r = ((self << 3) + 1).square?
|
222
|
+
return false unless 0 == (1 + r) & 3
|
223
|
+
|
224
|
+
return (1 + r) >> 2
|
225
|
+
end
|
226
|
+
|
227
|
+
# Heptagonal numbers are generated by the formula, P7_n = n * (5 * n - 3) / 2
|
228
|
+
# The first ten heptagonal numbers are:
|
229
|
+
# 1, 7, 18, 34, 55
|
230
|
+
# Return:: integer n s.t. self == P7_n if exist else false
|
231
|
+
def heptagonal?
|
232
|
+
return false unless r = (self * 40 + 9).square?
|
233
|
+
|
234
|
+
q, r = (3 + r).divmod(10)
|
235
|
+
return false unless 0 == r
|
236
|
+
|
237
|
+
return q
|
238
|
+
end
|
239
|
+
|
240
|
+
# Octagonal numbers are generated by the formula, P8_n = n * (3 * n - 2)
|
241
|
+
# The first ten octagonal numbers are:
|
242
|
+
# 1, 8, 21, 40, 65, ...
|
243
|
+
# Return:: integer n s.t. self == P7_n if exist else false
|
244
|
+
def octagonal?
|
245
|
+
return false unless r = (self * 3 + 1).square?
|
246
|
+
|
247
|
+
q, r = (1 + r).divmod(3)
|
248
|
+
return false unless 0 == r
|
249
|
+
|
250
|
+
return q
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
module Abst
|
255
|
+
Z = Integer
|
256
|
+
|
257
|
+
class IntegerIdeal
|
258
|
+
attr_reader :n
|
259
|
+
|
260
|
+
def initialize(n)
|
261
|
+
@n = n
|
262
|
+
end
|
263
|
+
|
264
|
+
def +(other)
|
265
|
+
self.class.new(Abst.gcd(@mod, other.mod))
|
266
|
+
end
|
267
|
+
|
268
|
+
def *(other)
|
269
|
+
self.class.new(@mod * other.mod)
|
270
|
+
end
|
271
|
+
|
272
|
+
def &(other)
|
273
|
+
self.class.new(Abst.lcm(@mod, other.mod))
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
return "IntegerIdeal #{@n}Z"
|
278
|
+
end
|
279
|
+
alias inspect to_s
|
280
|
+
|
281
|
+
def to_tex
|
282
|
+
return "#{@n}#{Z.to_tex}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class IntegerResidueRing
|
287
|
+
class << self
|
288
|
+
attr_reader :mod
|
289
|
+
|
290
|
+
def zero
|
291
|
+
return self.new(0)
|
292
|
+
end
|
293
|
+
|
294
|
+
def one
|
295
|
+
return self.new(1)
|
296
|
+
end
|
297
|
+
|
298
|
+
def order(op = :+)
|
299
|
+
case op
|
300
|
+
when :+
|
301
|
+
return @mod
|
302
|
+
when :*
|
303
|
+
@order = phi(@mod) unless defined?(@order)
|
304
|
+
return @order
|
305
|
+
end
|
306
|
+
|
307
|
+
raise ArgumentError, "unrecognized argument #{op} was specified"
|
308
|
+
end
|
309
|
+
alias cardinality order
|
310
|
+
|
311
|
+
def to_s
|
312
|
+
return "Z / #{@mod}Z"
|
313
|
+
end
|
314
|
+
|
315
|
+
def to_tex
|
316
|
+
z = Z.to_tex
|
317
|
+
return z + " / #{@mod}" + z
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
attr_reader :n
|
322
|
+
|
323
|
+
def initialize(n)
|
324
|
+
@n = n % self.class.mod
|
325
|
+
end
|
326
|
+
|
327
|
+
[:+, :-, :*].each do |op|
|
328
|
+
define_method(op) do |other|
|
329
|
+
return self.class.new(@n.__send__(op, other.to_i))
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def ==(other)
|
334
|
+
return false unless self.class.superclass == other.class.superclass
|
335
|
+
return false unless self.class.mod == other.class.mod
|
336
|
+
return @n == other.n
|
337
|
+
end
|
338
|
+
|
339
|
+
def inverse
|
340
|
+
return self.class.new(Abst.inverse(@n, self.class.mod))
|
341
|
+
end
|
342
|
+
|
343
|
+
def order(op)
|
344
|
+
mod = self.class.mod
|
345
|
+
|
346
|
+
case op
|
347
|
+
when :+
|
348
|
+
return mod / gcd(@n, mod)
|
349
|
+
when :*
|
350
|
+
raise ArgumentError, "#{self} is not invertible" unless 1 == gcd(@n, mod)
|
351
|
+
return element_order(self, self.class.order(op))
|
352
|
+
end
|
353
|
+
|
354
|
+
raise ArgumentError, "unrecognized argument #{op} was specified"
|
355
|
+
end
|
356
|
+
|
357
|
+
def to_s
|
358
|
+
return "#{@n} (mod #{self.class.mod})"
|
359
|
+
end
|
360
|
+
alias inspect to_s
|
361
|
+
|
362
|
+
def to_i
|
363
|
+
return @n
|
364
|
+
end
|
365
|
+
|
366
|
+
include Abst::Ring
|
367
|
+
end
|
368
|
+
|
369
|
+
class IntegerResidueField < IntegerResidueRing
|
370
|
+
class << self
|
371
|
+
def order(op = :+)
|
372
|
+
return @mod - 1 if :* == op
|
373
|
+
return super
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def inverse
|
378
|
+
return self.class.new(Abst.inverse(@n, self.class.mod))
|
379
|
+
end
|
380
|
+
|
381
|
+
def /(other)
|
382
|
+
return self.class.new(@n * other.inverse.n)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Abst
|
2
|
+
module_function
|
3
|
+
|
4
|
+
class Matrix
|
5
|
+
include Abst::Group
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :coef_class, :height, :width, :coef_vector
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
return "#{height} * #{width} #{self.coef_class} Matrix"
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
return to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :coef, :height, :width
|
20
|
+
protected :coef
|
21
|
+
|
22
|
+
def initialize(m)
|
23
|
+
raise MatrixSizeError unless self.class.height == m.size
|
24
|
+
@coef = m.map{|row| self.class.coef_vector.new(row)}
|
25
|
+
@height = self.class.height
|
26
|
+
@width = self.class.width
|
27
|
+
rescue VectorSizeError
|
28
|
+
raise MatrixSizeError
|
29
|
+
end
|
30
|
+
|
31
|
+
def each
|
32
|
+
return Enumerator.new(self) unless block_given?
|
33
|
+
|
34
|
+
@coef.each do |row|
|
35
|
+
row.each do |i|
|
36
|
+
yield i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_sub(op, other)
|
42
|
+
return self.class.new(@coef.zip(other.coef).map{|a, b| a.__send__(op, b)})
|
43
|
+
end
|
44
|
+
|
45
|
+
def *(other)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
# Param:: self must be n * (n + 1) matrix s.t. (MB)
|
50
|
+
# M is invertible n * n matrix
|
51
|
+
# B is a column vector
|
52
|
+
# Return:: a column vector X s.t. MX == B
|
53
|
+
def solve
|
54
|
+
raise MatrixSizeError unless @height + 1 == @width
|
55
|
+
|
56
|
+
inverse = []
|
57
|
+
m = self.to_a
|
58
|
+
n = @height
|
59
|
+
|
60
|
+
n.times do |j|
|
61
|
+
# Find non-zero entry
|
62
|
+
row = nil
|
63
|
+
j.upto(n - 1) do |i|
|
64
|
+
unless m[i][j].zero?
|
65
|
+
row = i
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return nil unless row
|
70
|
+
|
71
|
+
# Swap?
|
72
|
+
if j < row
|
73
|
+
m[row], m[j] = m[j], m[row]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Eliminate
|
77
|
+
inverse[j] = m[j][j].inverse
|
78
|
+
(j + 1).upto(n - 1) do |i|
|
79
|
+
c = m[i][j] * inverse[j]
|
80
|
+
|
81
|
+
(j + 1).upto(n) do |j2|
|
82
|
+
m[i][j2] -= m[j][j2] * c
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Solve triangular system
|
88
|
+
x = []
|
89
|
+
(n - 1).downto(0) do |i|
|
90
|
+
temp = m[i][n]
|
91
|
+
(i + 1).upto(n - 1) do |j|
|
92
|
+
temp -= m[i][j] * x[j]
|
93
|
+
end
|
94
|
+
x[i] = temp * inverse[i]
|
95
|
+
end
|
96
|
+
|
97
|
+
return Abst::Vector(self.class.coef_class, x)
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_a
|
101
|
+
return @coef.map{|row| row.to_a}
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
return "[#{@coef.map(&:to_s).join(', ')}]"
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
return "#{self.class}\n#{self}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class SquareMatrix < Matrix
|
114
|
+
include Abst::Ring
|
115
|
+
|
116
|
+
def trace
|
117
|
+
return @coef.map.with_index{|row, i| row[i]}.inject(&:+)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_matrix(coef_class, height, width = nil)
|
122
|
+
if height.kind_of?(Array)
|
123
|
+
elems = height
|
124
|
+
width = height[0].size
|
125
|
+
height = height.size
|
126
|
+
end
|
127
|
+
|
128
|
+
super_class = height == width ? SquareMatrix : Matrix
|
129
|
+
matrix = Class.new(super_class) do
|
130
|
+
@coef_class = coef_class
|
131
|
+
@height = height
|
132
|
+
@width = width
|
133
|
+
@coef_vector = Abst::Vector(coef_class, width)
|
134
|
+
end
|
135
|
+
|
136
|
+
return matrix.new(elems) if elems
|
137
|
+
return matrix
|
138
|
+
end
|
139
|
+
alias Matrix create_matrix
|
140
|
+
module_function :Matrix
|
141
|
+
|
142
|
+
class MatrixSizeError < Exception; end
|
143
|
+
end
|