hensel_code 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3141783b56aaeef35545764e8016b975917e6da6a4000c741c8a4974741181a3
4
- data.tar.gz: f45277dbbcef5551ca0ee0e756a6494dc26ade17906e5cc9692b197b1a597a57
3
+ metadata.gz: 5eaefbf55c5b467595cb52c0034036d2133f802fbd20ca63b17edef4c3ec159a
4
+ data.tar.gz: c1b9c4e619c032d11d1d68ca2a858b6da7286a67ac40e81c9c19b9f606f1385f
5
5
  SHA512:
6
- metadata.gz: e96d107ee37379ab95ff1daa3cea566dbc0383f2513db5c6054bbf93c8e74c82ce30b994325713085289b39337b8993639322b059bc26ddd180f3b367b510336
7
- data.tar.gz: 7dfe989626ceb077f9b9115e94a6f8cf6f8aa4816bc255822edb1aaba81f258b48cd9642c6b3662f4b1ee9cf913b050bd098e92313ebfb738f90f5b0d7298310
6
+ metadata.gz: 3f55c6f2e2ec23a4eac8d098227fc630109c8d3c605b148e281f910111bbe68fbcc79e3437275639c95f808aa63612e97cac44509219431b89bb79e9164321ba
7
+ data.tar.gz: fd298edc25d8ea23c731727109622895a93804a39b8546598e95e19ec8b9235aba384e90c312f7215e175cb1fb0ff26d9bab8198d69554fe12cfd0e998be1986
data/.codecov.yml ADDED
@@ -0,0 +1,6 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ target: 99% # the required coverage value
6
+ threshold: 1% # the leniency in hitting the target
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
- ## [Unreleased]
1
+ ## [0.3.0] - 2022-03-12
2
2
 
3
- ## [0.1.0] - 2022-03-01
3
+ - Add the `Polynomial` class for arithmetic with fixed-length polynomials
4
+ - Change the class `TruncatedFinitePadicExpansion` for carrying all computaitons over fixed-length polynomials reduced modulo `p`
5
+ - Add the third type of supported Hensel code: the truncated finite-segment g-adic Hensel code.
6
+ - Add `GAdicBase` as the parent class of `TruncatedFiniteGadicExpansion`.
7
+ - Allow converting a finite-segment p-adic Hensel code into a truncated p-adic Hensel code.
8
+ - Add the Chinese Remainder Theorem algorithm (CRT) to the module `Tools`
9
+ - Improve helpers for generating distinct random numbers (integers, primes, and rationals)
10
+ - Add the inverse function for all three currently supported types of Hensel codes
11
+ - Overall improvements in the code
12
+
13
+ ## [0.2.1] - 2022-03-05
14
+ - Fix version build
15
+
16
+ ## [0.2.0] - 2022-03-05
17
+
18
+ - Add the second type of supported Hensel code: the finite-segment p-adic Hensel code.
19
+ - Allow converting a finite-segment p-adic Hensel code into a truncated p-adic Hensel code.
20
+ - Support all four arithemtic operations.
21
+ - Add `PAdicBase` as the parent class of `FinitePadicExpansion` and `TruncatedFinitePadicExpansion`.
22
+
23
+
24
+ ## [0.1.0] - 2022-03-03
4
25
 
5
26
  - Initial release
27
+ - Contain general tools for integer manipulation such us random number generation, random integer generation, extended gcd, and modular multiplicative inverse.
28
+ - Add the first type of supported Hensel code: the truncated finite-segment p-adic expansion Hensel code or simply truncated p-adic Hensel code.
29
+ - Allow encoding rational numbers with the classs `TruncatedFinitePadicExpansion` and perform all four basic arithmetic operations on truncated p-adic Hensel codes: addition, subtraction, multiplication, and division.
data/Gemfile CHANGED
@@ -17,6 +17,4 @@ gem "rubocop-rake", "~> 0.6"
17
17
 
18
18
  gem "minitest-reporters", "~> 1.5"
19
19
 
20
- gem "prime", "~> 0.1.2"
21
-
22
20
  gem "codecov", require: false, group: :test
data/Gemfile.lock CHANGED
@@ -1,7 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hensel_code (0.1.0)
4
+ hensel_code (0.3.0)
5
+ openssl (~> 3.0.0)
6
+ prime (~> 0.1.2)
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
@@ -19,6 +21,7 @@ GEM
19
21
  builder
20
22
  minitest (>= 5.0)
21
23
  ruby-progressbar
24
+ openssl (3.0.0)
22
25
  parallel (1.21.0)
23
26
  parser (3.1.1.0)
24
27
  ast (~> 2.4.1)
@@ -63,7 +66,6 @@ DEPENDENCIES
63
66
  hensel_code!
64
67
  minitest (~> 5.0)
65
68
  minitest-reporters (~> 1.5)
66
- prime (~> 0.1.2)
67
69
  rake (~> 13.0)
68
70
  rubocop (~> 1.21)
69
71
  rubocop-minitest (~> 0.17.2)
data/README.md CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  ![example workflow](https://github.com/davidwilliam/hensel_code/actions/workflows/main.yml/badge.svg) [![codecov](https://codecov.io/gh/davidwilliam/hensel_code/branch/main/graph/badge.svg?token=XJ0C0U7P2M)](https://codecov.io/gh/davidwilliam/hensel_code) [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop) ![GitHub](https://img.shields.io/github/license/davidwilliam/hensel_code) ![Gem](https://img.shields.io/gem/v/hensel_code) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/davidwilliam/hensel_code)
4
4
 
5
+ ***NOTICE:*** this README is beign constantly updated. I am currently focused on coding since I want to release as many different types of Hensel codes as possible. At the same time, I want this README to be as informative as possible even for those completely unfamiliar with p-adic numbers and Hensel codes. Therefore, even not in the pace I would want, I will continue to add information to the README to facilitate the use of the gem and to give some practical ideas of the computational possibilities enabled by it.
6
+
5
7
  Hensel Code allows you to homomorphically encode rational numbers as integers using the finite-segment p-adic arithmetic, also known as Hensel codes.
6
8
 
7
9
  T. M. Rao describes the use of finite-segment p-adic arithmetic as a practical method for performing error-free computation and the term *Hensel code* as the first `r` digits of the infinite p-adic expansion of a rational number `x/y`. The use of Hensel codes allows us to replace the arithmetic opertations on rational numbers by corresponding aritmetic operation over the integers under certain conditions.
8
10
 
9
11
  Rao also remarks that the theory of Hensel codes, although lifted from the p-adic number theory, can be introduced without the need of a complete undertanding of the theoretical aspects of p-adic numbers if the goal is to work with Hensel codes alone. This is due to the fact that the finite-segment p-adic arithmetic is well-defined, self-contained, and it has immediate pratical applications in a wide range of real-world scenarios.
10
12
 
13
+ Ç. K. Koç remarks that the p-adic arithmetic allows error-free representation of fractions and error-ree arithmetic using fractions where infinite-precision p-adic arithemtic is more suitable for software implementation and finite-precision p-adic arithmetic is more suitable for hardware implementations.
14
+
15
+ A p-adic number can be uniquely written as a inifite p-adic expansion, for `p` prime, where the associated coefficients are integers between `0` and `p - 1`. When this p-adic expansion is finite in length, then we have a finite-segment p-adic expansion. When we only consider the constant term of a p-adic expansion, then we have a truncated finite-segment p-adic expansion. There many types of representations of rationals lifted from the p-adic number theory, and therefore many types of Hensel codes.
16
+
11
17
  ## Mathematical Background
12
18
 
13
19
  In our Wiki, you can find a brief [introduction to the mathematical background on Hensel codes](https://github.com/davidwilliam/hensel_code/wiki/Mathematical-Background). We will continue to update that area as we update the gem.
@@ -32,22 +38,43 @@ Or install it yourself as:
32
38
 
33
39
  $ gem install hensel_code
34
40
 
35
- # Usage
41
+ # HenselCode
42
+
43
+ There are several types of Hensel codes in the finite-segment p-adic number theory. There are currently three of them available in the gem HenselCode:
36
44
 
37
- There are several types of Hensel codes. There are currently two of the available in the gem HenselCode:
45
+ 1. Truncated finite-segment p-adic Hensel codes
46
+ 2. Finite-segment p-adic Hensel codes
47
+ 3. Truncated finite-segment g-adic Hensel codes
38
48
 
39
- 1. Truncated finite-segment p-adic Hensel codes (added in v0.1.0)
40
- 2. Finite-segment p-adic Hensel codes (added in v0.2.0)
49
+ For each type of supported Hensel code I will briefly discuss their properties and capabilities as well as unique features that make each type of Hensel code distinct from each other.
41
50
 
42
51
  ## Truncated finite-segment p-adic Hensel codes
43
52
 
53
+ ### Description
54
+
55
+ The truncated finite-segment p-adic Hensel codes are integer representations of rationals with respect to a prime `p` and a positive exponent `r`. This integer representation of any given rational is equivalent to a constant term of the finite-segment p-adic expansion that represents that rational.
56
+
57
+ ### Unique Benefits
58
+
59
+ The truncated finite-segment p-adic Hensel codes are the simplest type of Hensel codes and the easiest ones to perform computations on it. Given a prime `p` and an exponent `r`, Hensel codes are integers between `0` and `p^r - 1`. In Ruby, as in many other modern scripting languages, integers can be arbitarily large, and therefore `p` can be as large as computationally affordable. Addition, subtraction, multiplication, and division on Hensel codes are simply these operations modulo `p^r`. Encoding and decoding are also straightforward. Its use is ideal for applications with very large numbers and/or many consecutive homomorphic computations on Hensel codes, which can be achieved with the efficincy of computations over the integers.
60
+
61
+ ### Usage
62
+
44
63
  Let `p=257` and `r=3`. Given two rational numbers `rat1 = Rational(3,5)` and `rat2 = Rational(4,3)`, we encode `rat1` and `rat2` as follows:
45
64
 
46
65
  ```ruby
47
66
  h1 = HenselCode::TruncatedFinitePadicExpansion.new(p, r, rat1)
48
- # => [HenselCode: 13579675, prime: 257, exponent: 3, modulus: 16974593]
67
+ # => <HenselCode: 13579675>
49
68
  h2 = HenselCode::TruncatedFinitePadicExpansion.new(p, r, rat1)
50
- # => [HenselCode: 5658199, prime: 257, exponent: 3, modulus: 16974593]
69
+ # => <HenselCode: 5658199>
70
+ h1.n
71
+ # => 2913
72
+ h1.prime
73
+ # => 257
74
+ h1.exponent
75
+ # => 3
76
+ h1.modulus
77
+ # => 16974593
51
78
  h1.class
52
79
  # => HenselCode::TruncatedFinitePadicExpansion
53
80
  h2.class
@@ -62,9 +89,19 @@ Now we can carry arithmetic computations on the `h1` and `h2` objects as if we w
62
89
 
63
90
  ```ruby
64
91
  h1_plus_h2 = h1 + h2
92
+ # => <HenselCode: 2263281>
65
93
  h1_minus_h2 = h1 - h2
94
+ # => <HenselCode: 7921476>
66
95
  h1_times_h2 = h1 * h2
96
+ # => <HenselCode: 6789838>
67
97
  h1_div_h2 = h1 / h2
98
+ # => <HenselCode: 5941108>
99
+ h2.inverse
100
+ # => <HenselCode: 4243649>
101
+ h1 * h2.inverse
102
+ # => <HenselCode: 5941108>
103
+ h2 * h2.inverse
104
+ # => <HenselCode: 1>
68
105
  ```
69
106
 
70
107
  All the computations are reduced modulo `p^r`.
@@ -106,7 +143,7 @@ To help you in this decision, given any Hensel code object `h`, you can check `h
106
143
 
107
144
  ```ruby
108
145
  h = HenselCode::TruncatedFinitePadicExpansion.new p, r, Rational(2,3)
109
- # => [HenselCode: 11316396, prime: 257, exponent: 3, modulus: 16974593]
146
+ # => <HenselCode: 11316396>
110
147
  h.n
111
148
  # => 2913
112
149
  ```
@@ -120,7 +157,7 @@ If `p = 7` and `r = 2`, then `n = 4`. If I try to encode a rational number `rat
120
157
  ```ruby
121
158
  rat = Rational(11,23)
122
159
  h = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat
123
- # => [HenselCode: 9, prime: 7, exponent: 2, modulus: 49]
160
+ # => <HenselCode: 9>
124
161
  h.to_r
125
162
  # => (-4/5)
126
163
  ```
@@ -131,11 +168,11 @@ The same occurs with computation on Hensel codes. If `rat1 = Rational(2,3)` and
131
168
 
132
169
  ```ruby
133
170
  h1 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat1
134
- # => [HenselCode: 17, prime: 7, exponent: 2, modulus: 49]
171
+ # => <HenselCode: 17>
135
172
  h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat2
136
- # => [HenselCode: 13, prime: 7, exponent: 2, modulus: 49]
173
+ # => <HenselCode: 13>
137
174
  h1_plus_h2 = h1 + h2
138
- # => [HenselCode: 30, prime: 7, exponent: 2, modulus: 49]
175
+ # => <HenselCode: 30>
139
176
  h1_plus_h2.to_r
140
177
  # => (3/5)
141
178
  ```
@@ -146,11 +183,11 @@ If instead we define `p = 56807` and `r = 3`, we have:
146
183
 
147
184
  ```ruby
148
185
  h1 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat1
149
- => [HenselCode: 122212127593296, prime: 56807, exponent: 3, modulus: 183318191389943]
186
+ # => <HenselCode: 122212127593296>
150
187
  h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat2
151
- => [HenselCode: 137488643542458, prime: 56807, exponent: 3, modulus: 183318191389943]
188
+ # => <HenselCode: 137488643542458>
152
189
  h1_plus_h2 = h1 + h2
153
- => [HenselCode: 76382579745811, prime: 56807, exponent: 3, modulus: 183318191389943]
190
+ # => <HenselCode: 76382579745811>
154
191
  h1_plus_h2.to_r
155
192
  # => (17/12)
156
193
  ```
@@ -170,7 +207,7 @@ p = 25
170
207
  r = 3
171
208
  rat1 = Rational(1,2)
172
209
  h1 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat1
173
- # => [HenselCode: 7813, prime: 25, exponent: 3, modulus: 15625]
210
+ # => <HenselCode: 7813>
174
211
  h1.to_r
175
212
  # => (1/2)
176
213
  ```
@@ -192,13 +229,13 @@ In order to operate on two or more Hensel codes, they all must be of the same ob
192
229
 
193
230
  ```ruby
194
231
  h1 = HenselCode::TruncatedFinitePadicExpansion.new(p1, r1, rat1)
195
- # => [HenselCode: 5599009, prime: 241, exponent: 3, modulus: 13997521]
232
+ # => <HenselCode: 5599009>
196
233
  h2 = HenselCode::TruncatedFinitePadicExpansion.new(p1, r2, rat1)
197
- # => [HenselCode: 1349361025, prime: 241, exponent: 4, modulus: 3373402561]
234
+ # => <HenselCode: 1349361025>
198
235
  h3 = HenselCode::TruncatedFinitePadicExpansion.new(p2, r1, rat1)
199
- # => [HenselCode: 6325301, prime: 251, exponent: 3, modulus: 15813251]
236
+ # => <HenselCode: 6325301>
200
237
  h4 = HenselCode::TruncatedFinitePadicExpansion.new(p2, r2, rat1)
201
- # => [HenselCode: 1587650401, prime: 251, exponent: 4, modulus: 3969126001]
238
+ # => <HenselCode: 1587650401>
202
239
  ```
203
240
 
204
241
  The following operations will raise exceptions:
@@ -221,7 +258,7 @@ Let `p = 541`, `r = 3`, `rat = Rational(11,5)`. We create a Hensel code as befor
221
258
 
222
259
  ```ruby
223
260
  h = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat
224
- # => [HenselCode: 126672339, prime: 541, exponent: 3, modulus: 158340421]
261
+ # => <HenselCode: 126672339>
225
262
  ```
226
263
 
227
264
  We can change the prime and the exponent:
@@ -230,9 +267,9 @@ We can change the prime and the exponent:
230
267
  p = 1223
231
268
  r = 4
232
269
  h.replace_prime(p)
233
- # => [HenselCode: 731710629, prime: 1223, exponent: 3, modulus: 1829276567]
270
+ # => <HenselCode: 731710629>
234
271
  h.replace_exponent(r)
235
- # => [HenselCode: 1789764193155, prime: 1223, exponent: 4, modulus: 2237205241441]
272
+ # => <HenselCode: 1789764193155>
236
273
  ```
237
274
 
238
275
  Any change in the prime and/or the exponent of a Hensel code object will change the Hensel code value and the modulus as well, however, the Hensel code object continues to refer to represent the same rational number:
@@ -247,7 +284,7 @@ We can also change the rational number:
247
284
  ```ruby
248
285
  rat = Rational(13,7)
249
286
  h.replace_rational(rat)
250
- # => [HenselCode: 1278402995111, prime: 1223, exponent: 4, modulus: 2237205241441]
287
+ # => <HenselCode: 1278402995111>
251
288
  h.to_r
252
289
  # => (13/7)
253
290
  ```
@@ -256,6 +293,7 @@ We can initiate a Hensel code object with its Hensel code value, instead of a ra
256
293
 
257
294
  ```ruby
258
295
  h = HenselCode::TruncatedFinitePadicExpansion.new p, r, 53673296543
296
+ # => <HenselCode: 53673296543>
259
297
  ```
260
298
 
261
299
  and then we can check what is the rational number represented by the resulting object:
@@ -269,18 +307,100 @@ We can update the Hensel code value of an existing Hensel code object:
269
307
 
270
308
  ```ruby
271
309
  h.replace_hensel_code(38769823656)
272
- # => (-685859/94809)
310
+ # => <HenselCode: 38769823656>
311
+ ```
312
+
313
+ ## Polynomials
314
+
315
+ In order to support finite-segment p-adic Hensel codes, the HenselCode gem offers an engine for computing fixed-length polynomials where all the computations ared reduced modulo `p`.
316
+
317
+ Let `p = 257`. We intantiate polynomials as follows:
318
+
319
+ ```ruby
320
+ f = HenselCode::Polynomial.new p, [29, 102, 232]
321
+ # => <Polynomial: 29 + 102p + 232p^2>
322
+ g = HenselCode::Polynomial.new p, [195, 83, 244]
323
+ # => <Polynomial: 195 + 83p + 244p^2>
324
+ f.prime
325
+ # => 257
326
+ f.coefficients
327
+ # => [29, 102, 232]
328
+ f.degree
329
+ # => 2
330
+ puts f
331
+ # => 29 + 102p + 232p^2
332
+ ```
333
+
334
+ ### Arithmetic
335
+
336
+ All the mathematical oeprations objects of the `Polynomial` are over fixed-length single-variable polynomails in non-standard form (the terms are in ascending order with respect to their degrees). All the computation on the polynomial coefficients are reduced modulo `p` in every single step of unitary calculations, that is, no indivudal step of computation exceeds an addition followed by a binary multiplication in which the operands are bounded by `p` (a carry + a product of two integers). Therefore, if `p` has bit length `b`, the maximum space required for expanding the result of each step of computation is `1 + 2b` bits.
337
+
338
+ ```ruby
339
+ f + g
340
+ # => <Polynomial: 224 + 185p + 219p^2>
341
+ f - g
342
+ # => <Polynomial: 91 + 18p + 245p^2>
343
+ f * g
344
+ # => <Polynomial: 1 + 217p + 216p^2>
345
+ f / g
346
+ # => <Polynomial: 70 + 238p + 233p^2>
347
+ g.inverse
348
+ # => <Polynomial: 29 + 234p + 219p^2
349
+ f * g.inverse
350
+ # => <Polynomial: 70 + 238p + 233p^2>
351
+ g * g.inverse
352
+ # => <Polynomial: 1 + 0p + 0p^2>
353
+ ```
354
+
355
+ ### Constraints
356
+
357
+ Operations with fixed-length polynomials require operands with the same degree:
358
+
359
+ ```ruby
360
+ f = HenselCode::Polynomial.new p, [29, 102, 232]
361
+ # => <Polynomial: 29 + 102p + 232p^2>
362
+ g = HenselCode::Polynomial.new p, [195, 83, 244, 99]
363
+ # => <Polynomial: 195 + 83p + 244p^2 + 99p^3>
364
+ f.degree
365
+ # => 2
366
+ g.degree
367
+ # => 3
368
+ f + g
369
+ # => polynomials must have same degree (HenselCode::WrongHenselCodeInputType)
370
+ ```
371
+
372
+ Operations with fixed-length polynomials also require operands with the same prime:
373
+
374
+ ```ruby
375
+ f = HenselCode::Polynomial.new 251, [133, 206, 58]
376
+ # => <Polynomial: 133 + 206p + 58p^2>
377
+ g = HenselCode::Polynomial.new 257, [105, 129, 238]
378
+ # => <Polynomial: 105 + 129p + 238p^2>
379
+ f + g
380
+ # => polynomials must have same prime (HenselCode::WrongHenselCodeInputType)
381
+ g.prime = 251
382
+ # => 251
383
+ f + g
384
+ # => <Polynomial: 238 + 84p + 46p^2>
273
385
  ```
274
386
 
275
387
  ## Finite-segment p-adic Hensel codes
276
388
 
277
- The truncated finite-segment p-adic Hensel codes can be described as a polynomial truncated to its leading coefficient/monomial in non-standard form (increasing degree order). The finite-segment p-adic Hensel codes are expressed with a polynomial of degree `r-1`.
389
+ ### Description
390
+ The finite-segment p-adic Hensel code is a p-adic integer that can be seen as a polynomial of degree `r - 1` in non-standard form (increasing degree order). Each coefficient of such polynomials are called *p-adic digits* ranging from `0` to `p - 1`. Computations on p-adic digits reduced modulo `p` must take the *carry* into consideration so we can guarantee that the results of addition, subtraction, multiplication, and division also range from `0` to `p - 1`.
391
+
392
+ ### Unique Benefits
393
+ The finite-segment p-adic Hensel code takes advantage of the finite-segmenet p-adic number system in which we can compute all four basic arithemtic operations (and consequently, any function) without requiring a substantial expansion in space for each individual computation. In fact, as mentioned in the section on Polynomials, given a prime `p` of bit length `b`, all unitary computations will take at most `1 + 2b` bits.
394
+
395
+ In Ruby, as is several other scripting languages, we can work with arbitrarily large integers and therefore the truncated p-adic Hensel code can be a good choice for representing large rational numbers. However, Ruby can run in a variety of systems, some of which will have limited resources, such as many instances in the IoT world. Additionally, a Ruby application can be one amongts many other components that together compose a larger application. Some of these other components might run in systems with integers limited to small bit lengths, say, 16. This is where finite-segment p-adic Hensel codes can be intrumental by allowing arbitrarily large p-adic expansions with coefficients bounded to a small prime.
396
+
397
+ ### Usage
278
398
 
279
399
  Let `p = 359`, `r = 3`, and `rat = Rational(2,3)`:
280
400
 
281
401
  ```ruby
282
402
  h1 = HenselCode::FinitePadicExpansion.new p, r, rat
283
- # => [HenselCode: 240 + 119p + 239p^2, prime: 359, exponent: 3, modulus: 359]
403
+ # => <HenselCode: 240 + 119p + 239p^2>
284
404
  puts h1
285
405
  # => 240 + 119p + 239p^2
286
406
  ```
@@ -289,26 +409,26 @@ We say that `h` is a p-adic number with `r` digits. We clearly see the correspon
289
409
 
290
410
  ```ruby
291
411
  h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat
292
- # => [HenselCode: 240, prime: 359, exponent: 1, modulus: 359]
412
+ # => <HenselCode: 240>
293
413
  ```
294
414
 
295
415
  Notice that the truncated Hensel code `h2` equals the first digit of the finite-segment Hensel code `h1`, which is also the same of computing
296
416
 
297
417
  ```ruby
298
418
  HenselCode::FinitePadicExpansion.new p, 1, rat
299
- # => [HenselCode: 240, prime: 359, exponent: 1, modulus: 359]
419
+ # => <HenselCode: 240>
300
420
  ```
301
421
 
302
- The following expressions are equivalent:
422
+ The following expressions are equivalent (they represent the same quantity):
303
423
 
304
424
  ```ruby
305
425
  r = 3
306
426
  h1 = HenselCode::FinitePadicExpansion.new p, r, rat
307
- # => [HenselCode: 240 + 119p + 239p^2, prime: 359, exponent: 3, modulus: 359]
427
+ # => <HenselCode: 240 + 119p + 239p^2>
308
428
  h1.to_r
309
429
  # => (2/3)
310
430
  h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat
311
- # => [HenselCode: 30845520, prime: 359, exponent: 3, modulus: 46268279]
431
+ # => <HenselCode: 30845520>
312
432
  h2.to_r
313
433
  # => (2/3)
314
434
  ```
@@ -326,7 +446,7 @@ We can obtain the truncated version of `h1` as follows:
326
446
 
327
447
  ```ruby
328
448
  h1.to_truncated
329
- # => [HenselCode: 30845520, prime: 359, exponent: 3, modulus: 46268279]
449
+ # => <HenselCode: 30845520>
330
450
  h1.to_truncated.class
331
451
  # => HenselCode::TruncatedFinitePadicExpansion
332
452
  ```
@@ -337,22 +457,28 @@ Let `p = 409`, `r = 5`, `rat1 = Rational(2,3)` and `rat2 = Rational(11,7)` such
337
457
 
338
458
  ```ruby
339
459
  h1 = HenselCode::FinitePadicExpansion.new p, r, rat1
340
- # => [HenselCode: 137 + 136p + 136p^2 + 136p^3 + 136p^4, prime: 409, exponent: 5, modulus: 409]
460
+ # => <HenselCode: 137 + 136p + 136p^2 + 136p^3 + 136p^4>
341
461
  h2 = HenselCode::FinitePadicExpansion.new p, r, rat2
342
- # => [HenselCode: 60 + 292p + 233p^2 + 350p^3 + 116p^4, prime: 409, exponent: 5, modulus: 409]
462
+ # => <HenselCode: 60 + 292p + 233p^2 + 350p^3 + 116p^4>
343
463
  ```
344
464
 
345
465
  We compute addition, subtraction, multiplication, and division as follows:
346
466
 
347
467
  ```ruby
348
- h1_plus_h2 =h1 + h2
349
- # => [HenselCode: 197 + 19p + 370p^2 + 77p^3 + 253p^4, prime: 409, exponent: 5, modulus: 409]
468
+ h1_plus_h2 = h1 + h2
469
+ # => <HenselCode: 197 + 19p + 370p^2 + 77p^3 + 253p^4>
350
470
  h1_minus_h2 = h1 - h2
351
- # => [HenselCode: 77 + 253p + 311p^2 + 194p^3 + 19p^4, prime: 409, exponent: 5, modulus: 409]
471
+ # => <HenselCode: 77 + 253p + 311p^2 + 194p^3 + 19p^4>
352
472
  h1_times_h2 = h1 * h2
353
- # => [HenselCode: 40 + 331p + 155p^2 + 97p^3 + 214p^4, prime: 409, exponent: 5, modulus: 409]
473
+ # => <HenselCode: 40 + 331p + 155p^2 + 97p^3 + 214p^4>
354
474
  h1_div_h2 = h1 / h2
355
- # => [HenselCode: 50 + 161p + 12p^2 + 347p^3 + 309p^4, prime: 409, exponent: 5, modulus: 409]
475
+ # => <HenselCode: 50 + 161p + 12p^2 + 347p^3 + 309p^4>
476
+ h2.inverse
477
+ # => <HenselCode: 75 + 37p + 223p^2 + 111p^3 + 260p^4>
478
+ h1 * h2.inverse
479
+ # => <HenselCode: 50 + 161p + 12p^2 + 347p^3 + 309p^4>
480
+ h2 * h2.inverse
481
+ # => <HenselCode: 1 + 0p + 0p^2 + 0p^3 + 0p^4>
356
482
  ```
357
483
 
358
484
  And we can verify that
@@ -376,9 +502,131 @@ rat1 / rat2
376
502
  # => (14/33)
377
503
  ```
378
504
 
379
- ### Class Alias
505
+ ## Truncated finite-segment g-adic Hensel codes
506
+ ### Description
507
+ Kurt Mahler refers to p-adic numbers based on multiple distinct primes as `g-adic numbers` (Lectures on Diophantine Approximations, 1961 and Introduction to p-adic Numbers and Their Functions, 1973). John F. Morrison, 1988, remarks that `g` is the product of `k` distinct primes, which are used to generate a g-adic number with a unique g-adic representation.
508
+
509
+ ### Unique Benefits
510
+
511
+ Since each digit of a truncated finite-segment g-adic Expansion (or simply truncated g-adic Hensel codes) is independently computed with respect to their corresponding prime, we can carry computations on each digit also independetly. This makes the truncated g-adic Hensel codes ideal for parallel/distributed processing, that is, given a rational number, several g-adic digits of that rational are independently computed and computations can be carried on those digits, also indpendently.
512
+
513
+ ### Usage
514
+
515
+ Let `primes = [241, 251, 257]`, `r = 3`, `rat1 = Rational(2,3)`, and `rat2 = Rational(5,4)`:
516
+
517
+ ```ruby
518
+ h1 = HenselCode::TruncatedFiniteGadicExpansion.new primes, r, rat1
519
+ # => <HenselCode: [4665841, 10542168, 11316396]>
520
+ h2 = HenselCode::TruncatedFiniteGadicExpansion.new primes, r, rat2
521
+ # => <HenselCode: [10498142, 3953314, 12730946]>
522
+ h1.primes
523
+ # => [241, 251, 257]
524
+ h1.exponent
525
+ # => 3
526
+ h1.g
527
+ # => 15546187
528
+ h1.n
529
+ # => 43343186168
530
+ h1.hensel_code
531
+ # => [<HenselCode: 4665841>, <HenselCode: 10542168>, <HenselCode: 11316396>]
532
+ h1.to_r
533
+ # => (2/3)
534
+ h2.to_r
535
+ # => (5/4)
536
+ ```
537
+
538
+ ### Arithmetic
539
+
540
+ We compute addition, subtraction, multiplication, and division as follows:
541
+
542
+ ```ruby
543
+ h1_plus_h2 = h1 + h2
544
+ # => <HenselCode: [1166462, 14495482, 7072749]>
545
+ h1_minus_h2 = h1 - h2
546
+ # => <HenselCode: [8165220, 6588854, 15560043]>
547
+ h1_times_h2 = h1 * h2
548
+ # => <HenselCode: [2332921, 13177710, 14145495]>
549
+ h1_div_h2 = h1 / h2
550
+ # => <HenselCode: [6532177, 2108434, 15842954]>
551
+ h2.inverse
552
+ # => <HenselCode: [2799505, 3162651, 6789838]>
553
+ h1 * h2.inverse
554
+ # => <HenselCode: [6532177, 2108434, 15842954]>
555
+ h2 * h2.inverse
556
+ # => <HenselCode: [1, 1, 1]>
557
+ ```
558
+
559
+ And we can verify that
560
+
561
+ ```ruby
562
+ h1_plus_h2.to_r
563
+ # => (23/12)
564
+ rat1 + rat2
565
+ # => (23/12)
566
+ rat1 - rat2
567
+ # => (-7/12)
568
+ h1_minus_h2.to_r
569
+ # => (-7/12)
570
+ h1_times_h2.to_r
571
+ # => (5/6)
572
+ rat1 * rat2
573
+ # => (5/6)
574
+ h1_div_h2.to_r
575
+ # => (8/15)
576
+ rat1 / rat2
577
+ # => (8/15)
578
+ ```
579
+
580
+ ### Relatable, and yet, Unique
581
+
582
+ When we execute the following:
583
+
584
+ ```ruby
585
+ h1.hensel_code
586
+ # => [<HenselCode: 4665841>, <HenselCode: 10542168>, <HenselCode: 11316396>]
587
+ ```
588
+
589
+ it is clear that the truncated g-adic Hensel code is a collection of individual Hensel codes, each one computed for the same rational but with distinct primes. One can be tempted to think that these multiple independent Hensel codes are "extra" material, not really needed for representing the given rational. This is far from the truth. Besides enabling parallel/distributed computaitons over Hensel codes (which is already a great benefit to have), to illustrate another aspect of working with truncated g-adic Hensel codes, consider the rational `rat3 = Rational(37897,52234)`:
590
+
591
+ ```ruby
592
+ h3 = HenselCode::TruncatedFiniteGadicExpansion.new primes, r, rat3
593
+ # => <HenselCode: [3698890, 5577355, 7406440]>
594
+ h3.hensel_code
595
+ # => [<HenselCode: 3698890>, <HenselCode: 5577355>, <HenselCode: 7406440>]
596
+ ```
597
+
598
+ We can clearly see that each Hensel code in the truncated g-adic Hensel code is only a partial representation of `rat3` when we decode each individual Hensel code to see which rational they are representing:
599
+
600
+ ```ruby
601
+ h3.hensel_code.map(&:to_r)
602
+ # => [(1471/4613), (409/981), (-207/3298)]
603
+ ```
604
+
605
+ None of the above rationals equals `rat3`. The reason is that each individual prime in `primes` are insufficient for representing `rat3` and therefore they can only, individually, partially represent `rat3`. However, when considered as part of the same g-adic number system, they can jointly represent much larger rationals, yet independently:
606
+
607
+ ```ruby
608
+ h3.to_r
609
+ => (37897/52234)
610
+ ```
611
+
612
+ Therefore, even without increasing the size of each individual prime, we can homomorphically represent very large rationals by considering more primes in the g-adic system:
613
+
614
+ ```ruby
615
+ rat4 = Rational(84245698732457344123,198437243845987593234524
616
+ primes = [349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409]
617
+ h4 = HenselCode::TruncatedFiniteGadicExpansion.new primes, r, rat4
618
+ # => HenselCode: [16442637, 10524943, 2432723, 10742241, 37389750, 10164016, 7690494, 32341841, 26459590, 50463786, 28362831]>
619
+ h4.to_r
620
+ # => (84245698732457344123/198437243845987593234524)
621
+ ```
622
+
623
+ ### Class Aliases
624
+
625
+ Since some classes can have long names, here are some aliases that can be used for keeping the lines of code shorter:
380
626
 
381
- Since `HenselCode::TruncatedFinitePadicExpansion` is a bit long, the alias `HenselCode::TFPE` can be used instead.
627
+ - `HenselCode::TruncatedFinitePadicExpansion` => `HenselCode::TFPE`
628
+ - `HenselCode::HenselCodesWithDifferentPrimesAndExponents` => `HenselCode::HCWDPAE`
629
+ - `HenselCode::WrongHenselCodeInputType` => `HenselCode::WHIT`
382
630
 
383
631
  ## Coming Soon
384
632
 
@@ -396,4 +644,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/davidw
396
644
 
397
645
  ## License
398
646
 
399
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
647
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/hensel_code/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hensel_code"
7
+ spec.version = HenselCode::VERSION
8
+ spec.authors = ["David William Silva"]
9
+ spec.email = ["contact@davidwsilva.com"]
10
+
11
+ spec.summary = "Homomorphic encoding of rational numbers."
12
+ spec.description = "A Ruby library for homomorphically representing rational numbers as integers."
13
+ spec.homepage = "https://github.com/davidwilliam/hensel_code"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/davidwilliam/hensel_code"
19
+ spec.metadata["changelog_uri"] = "https://github.com/davidwilliam/hensel_code/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ spec.add_dependency "openssl", "~> 3.0.0"
34
+ spec.add_dependency "prime", "~> 0.1.2"
35
+
36
+ # For more information and examples about making a new gem, check out our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ spec.metadata["rubygems_mfa_required"] = "true"
39
+ 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,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
@@ -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.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/hensel_code.rb CHANGED
@@ -13,12 +13,18 @@ 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"
21
26
  autoload :TruncatedFinitePadicExpansion, "hensel_code/truncated_finite_padic_expansion"
27
+ autoload :TruncatedFiniteGadicExpansion, "hensel_code/truncated_finite_gadic_expansion"
22
28
 
23
29
  # aliases for classes with long names
24
30
  TFPE = TruncatedFinitePadicExpansion
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hensel_code
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David William Silva
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-05 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-03-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: openssl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: prime
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.2
13
41
  description: A Ruby library for homomorphically representing rational numbers as integers.
14
42
  email:
15
43
  - contact@davidwsilva.com
@@ -17,6 +45,7 @@ executables: []
17
45
  extensions: []
18
46
  extra_rdoc_files: []
19
47
  files:
48
+ - ".codecov.yml"
20
49
  - ".rubocop.yml"
21
50
  - CHANGELOG.md
22
51
  - Gemfile
@@ -25,11 +54,17 @@ files:
25
54
  - README.md
26
55
  - Rakefile
27
56
  - codecov
57
+ - hensel_code.gemspec
28
58
  - lib/hensel_code.rb
29
59
  - lib/hensel_code/finite_padic_expansion.rb
60
+ - lib/hensel_code/gadic_base.rb
61
+ - lib/hensel_code/gadic_verifier.rb
62
+ - lib/hensel_code/modular_arithmetic.rb
30
63
  - lib/hensel_code/padic_base.rb
31
64
  - lib/hensel_code/padic_verifier.rb
65
+ - lib/hensel_code/polynomial.rb
32
66
  - lib/hensel_code/tools.rb
67
+ - lib/hensel_code/truncated_finite_gadic_expansion.rb
33
68
  - lib/hensel_code/truncated_finite_padic_expansion.rb
34
69
  - lib/hensel_code/version.rb
35
70
  - sig/hensel_code.rbs