hensel_code 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codecov.yml +6 -0
- data/CHANGELOG.md +26 -2
- data/Gemfile.lock +1 -1
- data/README.md +290 -42
- data/lib/hensel_code/finite_padic_expansion.rb +39 -12
- data/lib/hensel_code/gadic_base.rb +86 -0
- data/lib/hensel_code/gadic_verifier.rb +36 -0
- data/lib/hensel_code/modular_arithmetic.rb +75 -0
- data/lib/hensel_code/padic_base.rb +7 -0
- data/lib/hensel_code/polynomial.rb +103 -0
- data/lib/hensel_code/tools.rb +24 -6
- data/lib/hensel_code/truncated_finite_gadic_expansion.rb +64 -0
- data/lib/hensel_code/truncated_finite_padic_expansion.rb +6 -1
- data/lib/hensel_code/version.rb +1 -1
- data/lib/hensel_code.rb +6 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50f6a13e6cba426fca07fa46a610b85ef7fae043a4a380481cb20ab6df14de3a
|
4
|
+
data.tar.gz: 89ac88e608472e67fe00238d1e974462747870bf13aa6f85d2391c559656cfcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d67e6bdb32a0a1c70fecc6b00a6d5a3e7b7a6ca426446b02c9b44e3c84f059dccf79e17d6567013ec61f2ddb2641fdc41f34b0887dc36bc287cba89452329398
|
7
|
+
data.tar.gz: 7f4c9d9deb9edb968e99ad1c67e7cb597789169af91c6c83c9c1288990c531d7fcd732065cd71ce4a7f3cb6fd364667d2cd92932a5c20a4b0dad89bdf0b088d7
|
data/.codecov.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
|
-
## [
|
1
|
+
## [0.3.0] - 2022-03-12
|
2
2
|
|
3
|
-
|
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.lock
CHANGED
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
# =>
|
67
|
+
# => <HenselCode: 13579675>
|
49
68
|
h2 = HenselCode::TruncatedFinitePadicExpansion.new(p, r, rat1)
|
50
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
171
|
+
# => <HenselCode: 17>
|
135
172
|
h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat2
|
136
|
-
# =>
|
173
|
+
# => <HenselCode: 13>
|
137
174
|
h1_plus_h2 = h1 + h2
|
138
|
-
# =>
|
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
|
-
=>
|
186
|
+
# => <HenselCode: 122212127593296>
|
150
187
|
h2 = HenselCode::TruncatedFinitePadicExpansion.new p, r, rat2
|
151
|
-
=>
|
188
|
+
# => <HenselCode: 137488643542458>
|
152
189
|
h1_plus_h2 = h1 + h2
|
153
|
-
=>
|
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
|
-
# =>
|
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
|
-
# =>
|
232
|
+
# => <HenselCode: 5599009>
|
196
233
|
h2 = HenselCode::TruncatedFinitePadicExpansion.new(p1, r2, rat1)
|
197
|
-
# =>
|
234
|
+
# => <HenselCode: 1349361025>
|
198
235
|
h3 = HenselCode::TruncatedFinitePadicExpansion.new(p2, r1, rat1)
|
199
|
-
# =>
|
236
|
+
# => <HenselCode: 6325301>
|
200
237
|
h4 = HenselCode::TruncatedFinitePadicExpansion.new(p2, r2, rat1)
|
201
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
270
|
+
# => <HenselCode: 731710629>
|
234
271
|
h.replace_exponent(r)
|
235
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
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
|
-
# =>
|
460
|
+
# => <HenselCode: 137 + 136p + 136p^2 + 136p^3 + 136p^4>
|
341
461
|
h2 = HenselCode::FinitePadicExpansion.new p, r, rat2
|
342
|
-
# =>
|
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
|
-
# =>
|
468
|
+
h1_plus_h2 = h1 + h2
|
469
|
+
# => <HenselCode: 197 + 19p + 370p^2 + 77p^3 + 253p^4>
|
350
470
|
h1_minus_h2 = h1 - h2
|
351
|
-
# =>
|
471
|
+
# => <HenselCode: 77 + 253p + 311p^2 + 194p^3 + 19p^4>
|
352
472
|
h1_times_h2 = h1 * h2
|
353
|
-
# =>
|
473
|
+
# => <HenselCode: 40 + 331p + 155p^2 + 97p^3 + 214p^4>
|
354
474
|
h1_div_h2 = h1 / h2
|
355
|
-
# =>
|
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
|
-
|
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
|
-
|
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).
|
@@ -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
|
-
"
|
27
|
+
"<HenselCode: #{polynomial_form}>"
|
26
28
|
end
|
27
29
|
|
28
|
-
|
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
|
-
|
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
|
-
|
70
|
-
@
|
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
|
data/lib/hensel_code/tools.rb
CHANGED
@@ -36,13 +36,13 @@ module HenselCode
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
40
|
-
|
41
|
-
while
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
"
|
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
|
data/lib/hensel_code/version.rb
CHANGED
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,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hensel_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
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-
|
11
|
+
date: 2022-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A Ruby library for homomorphically representing rational numbers as integers.
|
14
14
|
email:
|
@@ -17,6 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".codecov.yml"
|
20
21
|
- ".rubocop.yml"
|
21
22
|
- CHANGELOG.md
|
22
23
|
- Gemfile
|
@@ -27,9 +28,14 @@ files:
|
|
27
28
|
- codecov
|
28
29
|
- lib/hensel_code.rb
|
29
30
|
- lib/hensel_code/finite_padic_expansion.rb
|
31
|
+
- lib/hensel_code/gadic_base.rb
|
32
|
+
- lib/hensel_code/gadic_verifier.rb
|
33
|
+
- lib/hensel_code/modular_arithmetic.rb
|
30
34
|
- lib/hensel_code/padic_base.rb
|
31
35
|
- lib/hensel_code/padic_verifier.rb
|
36
|
+
- lib/hensel_code/polynomial.rb
|
32
37
|
- lib/hensel_code/tools.rb
|
38
|
+
- lib/hensel_code/truncated_finite_gadic_expansion.rb
|
33
39
|
- lib/hensel_code/truncated_finite_padic_expansion.rb
|
34
40
|
- lib/hensel_code/version.rb
|
35
41
|
- sig/hensel_code.rbs
|