hensel_code 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # finite p-adic expansion hensel code class
5
+ class FinitePadicExpansion < PAdicBase
6
+ attr_accessor :polynomial
7
+
8
+ def modulus
9
+ prime
10
+ end
11
+
12
+ def to_a
13
+ hensel_code
14
+ end
15
+
16
+ def to_truncated
17
+ TruncatedFinitePadicExpansion.new(prime, exponent, rational)
18
+ end
19
+
20
+ def to_s
21
+ hensel_code.map.with_index do |h, i|
22
+ "#{h}#{polynomial_variable(i)}"
23
+ end.join(" + ")
24
+ end
25
+
26
+ def inspect
27
+ "<HenselCode: #{polynomial_form}>"
28
+ end
29
+
30
+ def inverse
31
+ new_hensel_code = polynomial.inverse.coefficients
32
+ self.class.new prime, exponent, new_hensel_code
33
+ end
34
+
35
+ private
36
+
37
+ def polynomial_form
38
+ to_s
39
+ end
40
+
41
+ def evaluate(operation, other)
42
+ new_hensel_code = polynomial.send(operation, other.polynomial).coefficients
43
+ self.class.new prime, exponent, new_hensel_code
44
+ end
45
+
46
+ def valid_number?(number)
47
+ if number.is_a?(Rational)
48
+ @rational = number
49
+ elsif number.is_a?(Array) && number.map(&:class).uniq == [Integer] && number.size == exponent
50
+ @hensel_code = number
51
+ decode
52
+ else
53
+ message = "number must be a Rational or an\
54
+ Array of integers of size #{exponent}"
55
+ raise WrongHenselCodeInputType, message
56
+ end
57
+ end
58
+
59
+ def valid_hensel_code?(new_hensel_code)
60
+ conditions = [
61
+ new_hensel_code.is_a?(Array),
62
+ new_hensel_code.map(&:class).uniq == [Integer],
63
+ new_hensel_code.size == exponent
64
+ ]
65
+ message = "must be an array of integers of size #{exponent}"
66
+ raise WrongHenselCodeInputType, message unless conditions.uniq == [true]
67
+ end
68
+
69
+ def encode
70
+ @hensel_code = rational_to_padic_digits
71
+ @polynomial = Polynomial.new prime, hensel_code
72
+ end
73
+
74
+ def decode
75
+ number = 0
76
+ hensel_code.each_with_index { |d, i| number += d * (prime**i) }
77
+ @rational = TruncatedFinitePadicExpansion.new(prime, exponent, number).to_r
78
+ end
79
+
80
+ def rational_to_padic_digits
81
+ digits = [rational_to_integer(rational)]
82
+ alpha = rational - digits.last
83
+ (exponent - 1).times do
84
+ alpha = reduce_rational_in_terms_of_prime(alpha)
85
+ digits << rational_to_integer(alpha)
86
+ alpha -= digits.last
87
+ end
88
+ digits
89
+ end
90
+
91
+ def reduce_rational_in_terms_of_prime(alpha)
92
+ divisor_numerator = alpha.numerator.gcd(prime)
93
+ divisor_denominator = alpha.denominator.gcd(prime)
94
+ if divisor_numerator != 1
95
+ alpha /= divisor_numerator
96
+ elsif divisor_denominator != 1
97
+ alpha *= divisor_denominator
98
+ end
99
+ alpha
100
+ end
101
+
102
+ def rational_to_integer(rat)
103
+ (rat.numerator * mod_inverse(rat.denominator, prime)) % prime
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # base hensel code class
5
+ class GAdicBase
6
+ include Tools
7
+ include GAdicVerifier
8
+
9
+ attr_accessor :primes, :exponent, :rational, :hensel_code, :g, :n
10
+ private :primes=, :exponent=, :rational=, :hensel_code=
11
+
12
+ def initialize(primes, exponent, number)
13
+ can_initilize?
14
+ @primes = primes
15
+ @exponent = exponent
16
+ @g = primes.inject(:*)
17
+ @n = Integer.sqrt(((g**exponent) - 1) / 2)
18
+ valid_number?(number)
19
+ encode
20
+ decode
21
+ end
22
+
23
+ def numerator
24
+ rational.numerator
25
+ end
26
+
27
+ def denominator
28
+ rational.denominator
29
+ end
30
+
31
+ def to_r
32
+ decode
33
+ rational
34
+ end
35
+
36
+ def +(other)
37
+ valid?(other)
38
+ evaluate("+", other)
39
+ end
40
+
41
+ def -(other)
42
+ valid?(other)
43
+ evaluate("-", other)
44
+ end
45
+
46
+ def *(other)
47
+ valid?(other)
48
+ evaluate("*", other)
49
+ end
50
+
51
+ def /(other)
52
+ valid?(other)
53
+ evaluate("/", other)
54
+ end
55
+
56
+ def replace_primes(new_primes)
57
+ replace_attribute("primes=", new_primes, 0)
58
+ end
59
+
60
+ def replace_exponent(new_exponent)
61
+ replace_attribute("exponent=", new_exponent, 0)
62
+ end
63
+
64
+ def replace_rational(new_rational)
65
+ replace_attribute("rational=", new_rational, 0)
66
+ end
67
+
68
+ def replace_hensel_code(new_hensel_code)
69
+ valid_hensel_code?(new_hensel_code)
70
+ replace_attribute("hensel_code=", new_hensel_code, 1)
71
+ end
72
+
73
+ private
74
+
75
+ def can_initilize?
76
+ message = "#{self.class} can only be inherited."
77
+ raise NonInitializableClass, message if instance_of?(HenselCode::GAdicBase)
78
+ end
79
+
80
+ def replace_attribute(attribute, new_value, order)
81
+ send(attribute, new_value)
82
+ order.zero? ? [encode, decode] : [decode, encode]
83
+ self
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # verifications pre-evaluation of hensel codes
5
+ module GAdicVerifier
6
+ def valid?(other)
7
+ incompatible_operand_type?(other)
8
+ different_primes_and_same_exponent?(other)
9
+ different_primes_and_different_exponent?(other)
10
+ same_primes_and_different_exponent?(other)
11
+ end
12
+
13
+ def incompatible_operand_type?(other)
14
+ message = "#{self} is a #{self.class} while #{other} is a #{other.class}"
15
+ raise IncompatibleOperandTypes, message unless instance_of?(other.class)
16
+ end
17
+
18
+ def different_primes_and_same_exponent?(other)
19
+ message = "#{self} has primes #{primes} while #{other} has prime #{other.primes}"
20
+ raise HenselCodesWithDifferentPrimes, message if primes != other.primes && exponent == other.exponent
21
+ end
22
+
23
+ def different_primes_and_different_exponent?(other)
24
+ message = <<~MSG
25
+ "#{self} has prime #{primes} and exponent #{exponent}
26
+ while #{other} has prime #{other.primes} and exponent #{other.exponent}
27
+ MSG
28
+ raise HenselCodesWithDifferentPrimesAndExponents, message if primes != other.primes && exponent != other.exponent
29
+ end
30
+
31
+ def same_primes_and_different_exponent?(other)
32
+ message = "#{self} has exponent #{exponent} while #{other} has exponent #{other.exponent}"
33
+ raise HenselCodesWithDifferentExponents, message if primes == other.primes && exponent != other.exponent
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # modular arithmetic class
5
+ module ModularArithmetic
6
+ def cauchy_product(prime, coefficients1, coefficients2)
7
+ product = []
8
+ carry = 0
9
+ (0..coefficients1.size - 1).each do |i|
10
+ sum = 0
11
+ (0..i).each { |j| sum += (coefficients1[j] * coefficients2[i - j]) }
12
+ product << ((carry + sum) % prime)
13
+ carry = (carry + sum) / prime
14
+ end
15
+ product
16
+ end
17
+
18
+ def multiplication(prime, coefficients1, coefficients2)
19
+ partial_multiplications = []
20
+ coefficients2.each_with_index do |c1, i|
21
+ rows = multiplication_inner_loop(prime, coefficients1, coefficients2, c1, i)
22
+ partial_multiplications << rows
23
+ rows.append(*([0] * i))
24
+ end
25
+ sum_of_partial_multiplications(partial_multiplications)
26
+ end
27
+
28
+ private
29
+
30
+ def multiplication_inner_loop(prime, coefficients1, coefficients2, c1_, index)
31
+ rows = []
32
+ carry = 0
33
+ coefficients1.each_with_index do |c2, j|
34
+ rows << ((carry + (c1_ * c2)) % prime)
35
+ carry = (carry + (c1_ * c2)) / prime
36
+ (rows << carry).reverse!.insert(0, *([0] * (j - index))) if j == coefficients2.size - 1
37
+ end
38
+ rows
39
+ end
40
+
41
+ def sum_of_partial_multiplications(partial_multiplications)
42
+ carry = 0
43
+ sum = []
44
+ partial_multiplications.map(&:reverse).transpose.map do |x|
45
+ sum << ((carry + x.reduce(:+)) % prime)
46
+ carry = (carry + x.reduce(:+)) / prime
47
+ end
48
+ sum
49
+ end
50
+
51
+ def addition(prime, coefficients1, coefficients2)
52
+ carry = 0
53
+ result_coefficients = []
54
+ coefficients1.zip(coefficients2).each do |x|
55
+ result_coefficients << ((carry + x.reduce(:+)) % prime)
56
+ carry = (carry + x.reduce(:+)) / prime
57
+ end
58
+ result_coefficients
59
+ end
60
+
61
+ def subtraction(prime, coefficients1, coefficients2)
62
+ addition(prime, coefficients1, negation(prime, coefficients2))
63
+ end
64
+
65
+ def negation(prime, coefficients)
66
+ leading_zeros = coefficients.take_while(&:zero?)
67
+ coefficients_without_leading_zeros = coefficients.drop_while(&:zero?)
68
+ new_coefficients = [(prime - coefficients_without_leading_zeros[0]) % prime]
69
+ new_coefficients += coefficients[leading_zeros.size + 1..].map do |c|
70
+ ((prime - 1) - c) % prime
71
+ end
72
+ leading_zeros + new_coefficients
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # base hensel code class
5
+ class PAdicBase
6
+ include Tools
7
+ include PAdicVerifier
8
+
9
+ attr_accessor :prime, :exponent, :rational, :hensel_code, :n
10
+ private :prime=, :exponent=, :rational=, :hensel_code=
11
+
12
+ def initialize(prime, exponent, number)
13
+ can_initilize?
14
+ @prime = prime
15
+ @exponent = exponent
16
+ @n = Integer.sqrt(((prime**exponent) - 1) / 2)
17
+ valid_number?(number)
18
+ encode
19
+ decode
20
+ end
21
+
22
+ def numerator
23
+ rational.numerator
24
+ end
25
+
26
+ def denominator
27
+ rational.denominator
28
+ end
29
+
30
+ def to_r
31
+ decode
32
+ rational
33
+ end
34
+
35
+ def +(other)
36
+ valid?(other)
37
+ evaluate("+", other)
38
+ end
39
+
40
+ def -(other)
41
+ valid?(other)
42
+ evaluate("-", other)
43
+ end
44
+
45
+ def *(other)
46
+ valid?(other)
47
+ evaluate("*", other)
48
+ end
49
+
50
+ def /(other)
51
+ valid?(other)
52
+ evaluate("/", other)
53
+ end
54
+
55
+ def replace_prime(new_prime)
56
+ replace_attribute("prime=", new_prime, 0)
57
+ end
58
+
59
+ def replace_exponent(new_exponent)
60
+ replace_attribute("exponent=", new_exponent, 0)
61
+ end
62
+
63
+ def replace_rational(new_rational)
64
+ replace_attribute("rational=", new_rational, 0)
65
+ end
66
+
67
+ def replace_hensel_code(new_hensel_code)
68
+ valid_hensel_code?(new_hensel_code)
69
+ replace_attribute("hensel_code=", new_hensel_code, 1)
70
+ end
71
+
72
+ private
73
+
74
+ def can_initilize?
75
+ message = "#{self.class} can only be inherited."
76
+ raise NonInitializableClass, message if instance_of?(HenselCode::PAdicBase)
77
+ end
78
+
79
+ def replace_attribute(attribute, new_value, order)
80
+ send(attribute, new_value)
81
+ order.zero? ? [encode, decode] : [decode, encode]
82
+ self
83
+ end
84
+ end
85
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HenselCode
4
4
  # verifications pre-evaluation of hensel codes
5
- module TFPEVerifier
5
+ module PAdicVerifier
6
6
  def valid?(other)
7
7
  incompatible_operand_type?(other)
8
8
  different_prime_and_same_exponent?(other)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # polynomial class
5
+ class Polynomial
6
+ include HenselCode::Tools
7
+ include HenselCode::ModularArithmetic
8
+
9
+ attr_accessor :prime, :coefficients, :fixed_length
10
+
11
+ def initialize(prime, coefficients, fixed_length: true)
12
+ @prime = prime
13
+ @coefficients = coefficients
14
+ @fixed_length = fixed_length
15
+ valid_prime?
16
+ valid_coefficients?
17
+ end
18
+
19
+ def +(other)
20
+ valid_operands?(other)
21
+ new_coefficients = addition(prime, coefficients, other.coefficients)
22
+ self.class.new prime, new_coefficients
23
+ end
24
+
25
+ def -(other)
26
+ valid_operands?(other)
27
+ new_coefficients = subtraction(prime, coefficients, other.coefficients)
28
+ self.class.new prime, new_coefficients
29
+ end
30
+
31
+ def *(other)
32
+ valid_operands?(other)
33
+ new_coefficients = multiplication(prime, coefficients, other.coefficients)
34
+ self.class.new prime, new_coefficients[0..coefficients.size - 1]
35
+ end
36
+
37
+ def /(other)
38
+ valid_operands?(other)
39
+ new_coefficients = (self * other.inverse).coefficients
40
+ self.class.new prime, new_coefficients[0..coefficients.size - 1]
41
+ end
42
+
43
+ def inverse
44
+ x = generate_padic_x
45
+ two = generate_padic_constant_integer(2)
46
+ x = (two * x) - (self * x * x) while (x * self).coefficients != [1] + Array.new(coefficients.size - 1, 0)
47
+ x
48
+ end
49
+
50
+ def to_s
51
+ coefficients.map.with_index do |c, i|
52
+ "#{c}#{polynomial_variable(i)}"
53
+ end.join(" + ")
54
+ end
55
+
56
+ def inspect
57
+ "<Polynomial: #{polynomial_form}>"
58
+ end
59
+
60
+ def degree
61
+ coefficients.size - 1
62
+ end
63
+
64
+ private
65
+
66
+ def valid_prime?
67
+ raise ArgumentError, "prime can't be nil" if @prime.nil?
68
+ raise ArgumentError, "prime must be an integer" unless @prime.is_a?(Integer)
69
+ end
70
+
71
+ def valid_coefficients?
72
+ coefficients_condition = @coefficients.is_a?(Array) && @coefficients.map(&:class).uniq == [Integer]
73
+ raise ArgumentError, "coefficients can't be nil" if @coefficients.nil?
74
+ raise ArgumentError, "coefficients must be an array" unless @coefficients.is_a?(Array)
75
+ raise ArgumentError, "coefficients must be an array" unless coefficients_condition
76
+ end
77
+
78
+ def valid_operands?(other)
79
+ s1 = coefficients.size
80
+ s2 = other.coefficients.size
81
+ raise WrongHenselCodeInputType, "polynomials must have same degree" if s1 != s2
82
+ raise WrongHenselCodeInputType, "polynomials must have same prime" if prime != other.prime
83
+ end
84
+
85
+ def generate_padic_x
86
+ x_coefficients = [mod_inverse(coefficients[0], prime)] + Array.new(coefficients.size - 1) { rand(0..prime - 1) }
87
+ self.class.new prime, x_coefficients
88
+ end
89
+
90
+ def generate_padic_constant_integer(number)
91
+ self.class.new prime, [number] + Array.new(coefficients.size - 1, 0)
92
+ end
93
+
94
+ def mul(other)
95
+ new_coefficients = multiplication(prime, coefficients, other.coefficients)
96
+ self.class.new prime, new_coefficients
97
+ end
98
+
99
+ def polynomial_form
100
+ to_s
101
+ end
102
+ end
103
+ end
@@ -36,13 +36,13 @@ module HenselCode
36
36
  end
37
37
  end
38
38
 
39
- def random_distinct_primes(quantity, bits)
40
- primes = [random_prime(bits)]
41
- while primes.size < quantity
42
- prime = random_prime(bits)
43
- primes << prime if prime != primes.last
39
+ def random_distinct_numbers(type, quantity, bits)
40
+ numbers = [send("random_#{type}", bits)]
41
+ while numbers.size < quantity
42
+ number = send("random_#{type}", bits)
43
+ numbers << number if number != numbers.last
44
44
  end
45
- primes
45
+ numbers
46
46
  end
47
47
 
48
48
  def eea_core(num1, num2, bound = 0)
@@ -77,5 +77,23 @@ module HenselCode
77
77
 
78
78
  y % mod
79
79
  end
80
+
81
+ def crt(moduli, remainders)
82
+ g = moduli.inject(:*)
83
+ result = 0
84
+ moduli.zip(remainders) do |modulus, remainder|
85
+ g_prime = g / modulus
86
+ g_prime_inverse = mod_inverse(g_prime, modulus)
87
+ result += ((g_prime * g_prime_inverse * remainder))
88
+ end
89
+ result % g
90
+ end
91
+
92
+ private
93
+
94
+ def polynomial_variable(index)
95
+ i = index > 1 ? 2 : index
96
+ ["", "p", "p^#{index}"][i]
97
+ end
80
98
  end
81
99
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # truncated finite g-adic expansion hensel code class
5
+ class TruncatedFiniteGadicExpansion < GAdicBase
6
+ def modululi
7
+ primes.map { |prime| prime**exponent }
8
+ end
9
+
10
+ def to_a
11
+ hensel_code.map(&:to_i)
12
+ end
13
+
14
+ def to_s
15
+ hensel_code.map(&:to_i).to_s
16
+ end
17
+
18
+ def inspect
19
+ "<HenselCode: #{to_a}>"
20
+ end
21
+
22
+ def inverse
23
+ new_hensel_code = hensel_code.map(&:inverse)
24
+ self.class.new primes, exponent, new_hensel_code
25
+ end
26
+
27
+ private
28
+
29
+ def evaluate(operation, other)
30
+ new_hensel_code = hensel_code.zip(other.hensel_code).map { |pair| pair[0].send(operation, pair[1]) }
31
+ self.class.new primes, exponent, new_hensel_code
32
+ end
33
+
34
+ def valid_number?(number)
35
+ if number.is_a?(Rational)
36
+ @rational = number
37
+ elsif number.is_a?(Array) && number.map(&:class).uniq == [HenselCode::TruncatedFinitePadicExpansion]
38
+ @hensel_code = number
39
+ decode
40
+ else
41
+ raise WrongHenselCodeInputType, "number must be a Rational or an\
42
+ Array of truncated p-adic Hensel codes and it was a #{number.class}"
43
+ end
44
+ end
45
+
46
+ def valid_hensel_code?(new_hensel_code)
47
+ condition = new_hensel_code.is_a?(Array) && new_hensel_code.map(&:class).uniq == [HenselCode::TFPE]
48
+ message = "must be an array of truncated p-adic Hensel codes"
49
+ raise WrongHenselCodeInputType, message unless condition
50
+ end
51
+
52
+ def encode
53
+ @g = primes.inject(:*)
54
+ @hensel_code = primes.map do |prime|
55
+ TruncatedFinitePadicExpansion.new prime, exponent, rational
56
+ end
57
+ end
58
+
59
+ def decode
60
+ h = TruncatedFinitePadicExpansion.new g, exponent, crt(modululi, hensel_code.map(&:to_i))
61
+ @rational = h.to_r
62
+ end
63
+ end
64
+ end