hensel_code 0.2.1 → 0.4.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,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