hensel_code 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HenselCode
4
+ # truncated finite g-adic expansion hensel code class
5
+ class FiniteGadicExpansion < GAdicBase
6
+ def modululi
7
+ primes.map { |prime| prime**exponent }
8
+ end
9
+
10
+ def to_a
11
+ hensel_code.map(&:to_a)
12
+ end
13
+
14
+ def to_s
15
+ hensel_code.map(&:to_s).to_s
16
+ end
17
+
18
+ def inspect
19
+ "<HenselCode: #{self}>"
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::FinitePadicExpansion]
38
+ @hensel_code = number
39
+ decode
40
+ else
41
+ message = "number must be a Rational or an\
42
+ Array of finite p-adic Hensel codes and it was a #{number.class}"
43
+ raise WrongHenselCodeInputType, message
44
+ end
45
+ end
46
+
47
+ def valid_hensel_code?(new_hensel_code)
48
+ condition = new_hensel_code.is_a?(Array) && new_hensel_code.map(&:class).uniq == [HenselCode::FPE]
49
+ message = "must be an array of finite p-adic Hensel codes"
50
+ raise WrongHenselCodeInputType, message unless condition
51
+ end
52
+
53
+ def encode
54
+ @g = primes.inject(:*)
55
+ @hensel_code = primes.map do |prime|
56
+ FinitePadicExpansion.new prime, exponent, rational
57
+ end
58
+ end
59
+
60
+ def decode
61
+ hs = hensel_code.map { |h| h.to_truncated.hensel_code }
62
+ h = TruncatedFinitePadicExpansion.new g, exponent, crt(modululi, hs)
63
+ @rational = h.to_r
64
+ end
65
+ end
66
+ end
@@ -3,6 +3,8 @@
3
3
  module HenselCode
4
4
  # finite p-adic expansion hensel code class
5
5
  class FinitePadicExpansion < PAdicBase
6
+ attr_accessor :polynomial
7
+
6
8
  def modulus
7
9
  prime
8
10
  end
@@ -22,26 +24,25 @@ module HenselCode
22
24
  end
23
25
 
24
26
  def inspect
25
- "[HenselCode: #{polynomial_form}, prime: #{prime}, exponent: #{exponent}, modulus: #{modulus}]"
27
+ "<HenselCode: #{polynomial_form}>"
26
28
  end
27
29
 
28
- private
29
-
30
- def evaluate(operation, other)
31
- h_ = to_truncated.send(operation, other.to_truncated).hensel_code
32
- new_hensel_code = (0..exponent - 1).map { |i| h_ / (prime**i) % prime }
30
+ def inverse
31
+ new_hensel_code = polynomial.inverse.coefficients
33
32
  self.class.new prime, exponent, new_hensel_code
34
33
  end
35
34
 
36
- def polynomial_variable(index)
37
- i = index > 1 ? 2 : index
38
- ["", "p", "p^#{index}"][i]
39
- end
35
+ private
40
36
 
41
37
  def polynomial_form
42
38
  to_s
43
39
  end
44
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
+
45
46
  def valid_number?(number)
46
47
  if number.is_a?(Rational)
47
48
  @rational = number
@@ -66,8 +67,8 @@ module HenselCode
66
67
  end
67
68
 
68
69
  def encode
69
- h_ = TruncatedFinitePadicExpansion.new(prime, exponent, rational).hensel_code
70
- @hensel_code = (0..exponent - 1).map { |i| h_ / (prime**i) % prime }
70
+ @hensel_code = rational_to_padic_digits
71
+ @polynomial = Polynomial.new prime, hensel_code
71
72
  end
72
73
 
73
74
  def decode
@@ -75,5 +76,31 @@ module HenselCode
75
76
  hensel_code.each_with_index { |d, i| number += d * (prime**i) }
76
77
  @rational = TruncatedFinitePadicExpansion.new(prime, exponent, number).to_r
77
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
78
105
  end
79
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
@@ -10,11 +10,13 @@ module HenselCode
10
10
  private :prime=, :exponent=, :rational=, :hensel_code=
11
11
 
12
12
  def initialize(prime, exponent, number)
13
+ can_initilize?
13
14
  @prime = prime
14
15
  @exponent = exponent
15
16
  @n = Integer.sqrt(((prime**exponent) - 1) / 2)
16
17
  valid_number?(number)
17
18
  encode
19
+ decode
18
20
  end
19
21
 
20
22
  def numerator
@@ -69,6 +71,11 @@ module HenselCode
69
71
 
70
72
  private
71
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
+
72
79
  def replace_attribute(attribute, new_value, order)
73
80
  send(attribute, new_value)
74
81
  order.zero? ? [encode, decode] : [decode, encode]
@@ -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 unless numbers.include?(number)
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,65 @@
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
+ message = "number must be a Rational or an\
42
+ Array of truncated p-adic Hensel codes and it was a #{number.class}"
43
+ raise WrongHenselCodeInputType, message
44
+ end
45
+ end
46
+
47
+ def valid_hensel_code?(new_hensel_code)
48
+ condition = new_hensel_code.is_a?(Array) && new_hensel_code.map(&:class).uniq == [HenselCode::TFPE]
49
+ message = "must be an array of truncated p-adic Hensel codes"
50
+ raise WrongHenselCodeInputType, message unless condition
51
+ end
52
+
53
+ def encode
54
+ @g = primes.inject(:*)
55
+ @hensel_code = primes.map do |prime|
56
+ TruncatedFinitePadicExpansion.new prime, exponent, rational
57
+ end
58
+ end
59
+
60
+ def decode
61
+ h = TruncatedFinitePadicExpansion.new g, exponent, crt(modululi, hensel_code.map(&:to_i))
62
+ @rational = h.to_r
63
+ end
64
+ end
65
+ end
@@ -16,7 +16,12 @@ module HenselCode
16
16
  end
17
17
 
18
18
  def inspect
19
- "[HenselCode: #{hensel_code}, prime: #{prime}, exponent: #{exponent}, modulus: #{modulus}]"
19
+ "<HenselCode: #{hensel_code}>"
20
+ end
21
+
22
+ def inverse
23
+ new_hensel_code = mod_inverse(hensel_code, modulus)
24
+ self.class.new prime, exponent, new_hensel_code
20
25
  end
21
26
 
22
27
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HenselCode
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/hensel_code.rb CHANGED
@@ -13,15 +13,23 @@ module HenselCode
13
13
  class HenselCodesWithDifferentPrimesAndExponents < StandardError; end
14
14
  class HenselCodesWithDifferentExponents < StandardError; end
15
15
  class IncompatibleOperandTypes < StandardError; end
16
+ class NonInitializableClass < StandardError; end
16
17
 
17
18
  autoload :Tools, "hensel_code/tools"
18
19
  autoload :PAdicBase, "hensel_code/padic_base"
20
+ autoload :GAdicBase, "hensel_code/gadic_base"
21
+ autoload :Polynomial, "hensel_code/polynomial"
19
22
  autoload :PAdicVerifier, "hensel_code/padic_verifier"
23
+ autoload :GAdicVerifier, "hensel_code/gadic_verifier"
24
+ autoload :ModularArithmetic, "hensel_code/modular_arithmetic"
20
25
  autoload :FinitePadicExpansion, "hensel_code/finite_padic_expansion"
26
+ autoload :FiniteGadicExpansion, "hensel_code/finite_gadic_expansion"
21
27
  autoload :TruncatedFinitePadicExpansion, "hensel_code/truncated_finite_padic_expansion"
28
+ autoload :TruncatedFiniteGadicExpansion, "hensel_code/truncated_finite_gadic_expansion"
22
29
 
23
30
  # aliases for classes with long names
24
31
  TFPE = TruncatedFinitePadicExpansion
32
+ FPE = TruncatedFinitePadicExpansion
25
33
  HCWDPAE = HenselCodesWithDifferentPrimesAndExponents
26
34
  WHIT = WrongHenselCodeInputType
27
35
  end