rns-engine 0.4.1__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rns_engine-0.6.0/PKG-INFO +427 -0
- rns_engine-0.6.0/README.md +404 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/pyproject.toml +1 -1
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine/__init__.py +28 -2
- rns_engine-0.6.0/src/rns_engine/signed.py +211 -0
- rns_engine-0.6.0/src/rns_engine/weighted.py +195 -0
- rns_engine-0.6.0/src/rns_engine.egg-info/PKG-INFO +427 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine.egg-info/SOURCES.txt +5 -1
- rns_engine-0.6.0/tests/test_signed.py +138 -0
- rns_engine-0.6.0/tests/test_weighted.py +159 -0
- rns_engine-0.4.1/PKG-INFO +0 -345
- rns_engine-0.4.1/README.md +0 -322
- rns_engine-0.4.1/src/rns_engine.egg-info/PKG-INFO +0 -345
- {rns_engine-0.4.1 → rns_engine-0.6.0}/LICENSE +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/setup.cfg +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/setup.py +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine/_core.cpp +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine/engine.py +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine.egg-info/dependency_links.txt +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine.egg-info/not-zip-safe +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine.egg-info/requires.txt +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/src/rns_engine.egg-info/top_level.txt +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/tests/test_packaging.py +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/tests/test_rns.py +0 -0
- {rns_engine-0.4.1 → rns_engine-0.6.0}/tests/test_session.py +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rns_engine
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Exact RNS engine with session/cache API.
|
|
5
|
+
Author: Evan Wesley
|
|
6
|
+
License-Expression: AGPL-3.0-only
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: C++
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.23
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest; extra == "test"
|
|
21
|
+
Requires-Dist: numpy==2.2.6; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# rns_engine
|
|
25
|
+
|
|
26
|
+
**Fast, exact integer arithmetic on a four-rail Residue Number System.**
|
|
27
|
+
|
|
28
|
+
No floating point. No approximation. Arithmetic is exact modulo the engine's fixed dynamic range.
|
|
29
|
+
|
|
30
|
+
Recent Colab benchmark: https://colab.research.google.com/drive/18RIQSLf4Vf5xUnwEmDwYNPJDLSM_Stqa?usp=sharing
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
`rns_engine` decomposes each integer across four coprime rails:
|
|
37
|
+
|
|
38
|
+
- `127`
|
|
39
|
+
- `8191`
|
|
40
|
+
- `65536`
|
|
41
|
+
- `524287`
|
|
42
|
+
|
|
43
|
+
Arithmetic runs independently on the rails, then Garner-style CRT reconstructs the canonical result.
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
M = 127 × 8191 × 65536 × 524287
|
|
47
|
+
= 35,742,890,181,197,824
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Unsigned results live in:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
[0, M)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The centered signed interpretation lives in:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
[-M/2, M/2 - 1]
|
|
60
|
+
=
|
|
61
|
+
[-17,871,445,090,598,912, 17,871,445,090,598,911]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install rns_engine
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Supported Python versions: **3.10–3.13**.
|
|
73
|
+
|
|
74
|
+
### AVX2 note
|
|
75
|
+
|
|
76
|
+
`HAS_AVX2` is a build-time property of the compiled extension, not runtime CPU detection. Supported x86-64 wheels can use AVX2 acceleration. macOS wheels intentionally avoid an external OpenMP dependency.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Unsigned quick start
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import numpy as np
|
|
84
|
+
import rns_engine as rns
|
|
85
|
+
|
|
86
|
+
x = np.array([123456789, 999999999], dtype=np.uint64)
|
|
87
|
+
y = np.array([987654321, 111111111], dtype=np.uint64)
|
|
88
|
+
|
|
89
|
+
ex = rns.encode(x)
|
|
90
|
+
ey = rns.encode(y)
|
|
91
|
+
|
|
92
|
+
out_add = rns.decode(*rns.add(*ex, *ey))
|
|
93
|
+
out_mul = rns.decode(*rns.mul(*ex, *ey))
|
|
94
|
+
|
|
95
|
+
# Stay in residue space across several exact operations and decode once.
|
|
96
|
+
s1 = rns.add(*ex, *ey)
|
|
97
|
+
s2 = rns.mul(*s1, *ey)
|
|
98
|
+
s3 = rns.sub(*s2, *ex)
|
|
99
|
+
out = rns.decode(*s3)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Session API
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import numpy as np
|
|
108
|
+
import rns_engine as rns
|
|
109
|
+
|
|
110
|
+
session = rns.Session(cache_capacity=32)
|
|
111
|
+
x = np.array([1, 2, 3, 4], dtype=np.uint64)
|
|
112
|
+
|
|
113
|
+
encoded = session.encode(x)
|
|
114
|
+
result = session.mul(session.add(encoded, encoded), encoded)
|
|
115
|
+
out = session.decode(result)
|
|
116
|
+
|
|
117
|
+
one_step = session.one_shot_affine(
|
|
118
|
+
x,
|
|
119
|
+
multiplier=1_000_003,
|
|
120
|
+
addend=7,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
hot_loop = session.hot_loop_affine(
|
|
124
|
+
x,
|
|
125
|
+
multiplier=1_000_003,
|
|
126
|
+
addend=7,
|
|
127
|
+
iterations=1000,
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Centered signed arithmetic
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import numpy as np
|
|
137
|
+
import rns_engine as rns
|
|
138
|
+
|
|
139
|
+
values = np.array([-5, 0, 7], dtype=np.int64)
|
|
140
|
+
rails = rns.encode_signed(values)
|
|
141
|
+
round_trip = rns.decode_signed(*rails)
|
|
142
|
+
|
|
143
|
+
assert np.array_equal(round_trip, values)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`encode_signed(...)` accepts only integers in `[-M/2, M/2 - 1]`. It rejects out-of-range inputs instead of silently wrapping them modulo `M`.
|
|
147
|
+
|
|
148
|
+
`decode_signed(...)` returns the canonical centered representative. That representation alone does **not** prove that an unknown mathematical result did not wrap. Unique signed reconstruction requires independent range evidence:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
|result| < M/2
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
certificate = rns.certify_signed_bound(max_abs_bound=1_000_000)
|
|
156
|
+
certificate.require_unique()
|
|
157
|
+
|
|
158
|
+
print(certificate.headroom)
|
|
159
|
+
print(certificate.minimum_required_modulus)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For a signed dot product—or one output entry of a GEMM—the engine can construct the conservative exact bound directly:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
certificate = rns.certify_signed_dot_bound(
|
|
166
|
+
inner_dimension=5120,
|
|
167
|
+
left_abs_bound=127,
|
|
168
|
+
right_abs_bound=127,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
assert certificate.max_abs_bound == 82_580_480
|
|
172
|
+
assert certificate.unique
|
|
173
|
+
certificate.require_unique()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The law is:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
output bound
|
|
180
|
+
=
|
|
181
|
+
inner_dimension × left bound × right bound + addend bound
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`SignedSession` exposes the existing rail arithmetic with signed external values:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
session = rns.SignedSession()
|
|
188
|
+
a = session.encode_signed([-5, 7])
|
|
189
|
+
b = session.encode_signed([3, -10])
|
|
190
|
+
out = session.decode_signed(session.add(a, b))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Weighted INT32 partial accumulation
|
|
196
|
+
|
|
197
|
+
Version 0.6 adds the first direct bridge for digit-plane, chunked-GEMM, and Tensor Core pipelines.
|
|
198
|
+
|
|
199
|
+
Give the engine signed INT32 partial outputs with shape:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
(terms, *output_shape)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
and one exact integer positional weight per term:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
import numpy as np
|
|
209
|
+
import rns_engine as rns
|
|
210
|
+
|
|
211
|
+
partials = np.array(
|
|
212
|
+
[
|
|
213
|
+
[[1, -2], [3, -4]],
|
|
214
|
+
[[5, 6], [-7, -8]],
|
|
215
|
+
[[-9, 10], [11, -12]],
|
|
216
|
+
],
|
|
217
|
+
dtype=np.int32,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
receipt = rns.accumulate_weighted_int32(
|
|
221
|
+
partials,
|
|
222
|
+
weights=[1, -3, 8],
|
|
223
|
+
require_unique=True,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
exact = receipt.decode_signed()
|
|
227
|
+
print(receipt.certificate.max_abs_bound)
|
|
228
|
+
print(receipt.certificate.headroom)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
For partials `P[t]` and full integer weights `w[t]`, the receipt uses the exact conservative law:
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
max_abs_bound
|
|
235
|
+
=
|
|
236
|
+
sum(abs(w[t]) × max_abs(P[t]))
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Important details:
|
|
240
|
+
|
|
241
|
+
- The original arbitrary-precision Python weights are retained in the proof receipt.
|
|
242
|
+
- Only the execution weights are reduced modulo `M`, which is lawful for rail arithmetic.
|
|
243
|
+
- Negative weights and weights wider than `uint64` are supported.
|
|
244
|
+
- `INT32_MIN` is measured with its correct magnitude, `2,147,483,648`.
|
|
245
|
+
- `decode_signed()` requires a unique range certificate by default.
|
|
246
|
+
- `decode_modular()` always returns the canonical residue in `[0, M)`.
|
|
247
|
+
- Empty term sets produce an exact zero result.
|
|
248
|
+
|
|
249
|
+
The current implementation is an evidence-first baseline. Python schedules terms, while each per-element encode, scalar-weight multiply, and rail addition runs through the compiled native kernels. It is **not yet one fused C++ weighted-accumulation kernel**, and no fused-kernel speed claim is made.
|
|
250
|
+
|
|
251
|
+
A precomputed receipt can also be created without materializing partial arrays:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
certificate = rns.certify_weighted_sum_bound(
|
|
255
|
+
weights=[1, 128, 128**2],
|
|
256
|
+
term_abs_bounds=[100, 200, 300],
|
|
257
|
+
)
|
|
258
|
+
certificate.require_unique()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Core API
|
|
264
|
+
|
|
265
|
+
### Rail operations
|
|
266
|
+
|
|
267
|
+
- `encode(x)` / `decode(r0, r1, r2, r3)`
|
|
268
|
+
- `encode_signed(x)` / `decode_signed(r0, r1, r2, r3)`
|
|
269
|
+
- `add(...)`, `sub(...)`, `mul(...)`, `div_(...)`, `fma(...)`
|
|
270
|
+
- `op(..., code)` where `0=add`, `1=mul`, `2=sub`, `3=div`
|
|
271
|
+
|
|
272
|
+
### Signed range receipts
|
|
273
|
+
|
|
274
|
+
- `certify_signed_bound(max_abs_bound)`
|
|
275
|
+
- `certify_signed_dot_bound(inner_dimension, left_abs_bound, right_abs_bound, addend_abs_bound=0)`
|
|
276
|
+
- `SignedRangeCertificate.unique`
|
|
277
|
+
- `SignedRangeCertificate.headroom`
|
|
278
|
+
- `SignedRangeCertificate.minimum_required_modulus`
|
|
279
|
+
- `SignedRangeCertificate.require_unique()`
|
|
280
|
+
|
|
281
|
+
### Weighted partial reconstruction
|
|
282
|
+
|
|
283
|
+
- `accumulate_weighted_int32(partials, weights, require_unique=False)`
|
|
284
|
+
- `certify_weighted_sum_bound(weights, term_abs_bounds, addend_abs_bound=0)`
|
|
285
|
+
- `WeightedInt32Result.decode_signed(require_unique=True)`
|
|
286
|
+
- `WeightedInt32Result.decode_modular()`
|
|
287
|
+
|
|
288
|
+
### Scalar-broadcast encoded operations
|
|
289
|
+
|
|
290
|
+
- `mul_u64(...)`
|
|
291
|
+
- `fma_u64(...)`
|
|
292
|
+
- `affine_repeat_u64(...)`
|
|
293
|
+
|
|
294
|
+
### Fused raw uint64 operations
|
|
295
|
+
|
|
296
|
+
- `add_u64_io(...)`
|
|
297
|
+
- `sub_u64_io(...)`
|
|
298
|
+
- `mul_u64_io(...)`
|
|
299
|
+
- `fma_u64_io(...)`
|
|
300
|
+
- `affine_repeat_u64_io(...)`
|
|
301
|
+
|
|
302
|
+
OpenMP forms use the `_omp` suffix. Auto-dispatch forms use the `_auto` suffix.
|
|
303
|
+
|
|
304
|
+
### High-level objects
|
|
305
|
+
|
|
306
|
+
- `Session`
|
|
307
|
+
- `SignedSession`
|
|
308
|
+
- `SessionCache`
|
|
309
|
+
- `EncodedArray`
|
|
310
|
+
- `WeightedInt32Result`
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Division constraint
|
|
315
|
+
|
|
316
|
+
Division requires the divisor to be invertible on every rail:
|
|
317
|
+
|
|
318
|
+
- `b % 127 != 0`
|
|
319
|
+
- `b % 8191 != 0`
|
|
320
|
+
- `b` is odd for modulus `65536`
|
|
321
|
+
- `b % 524287 != 0`
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Data model
|
|
326
|
+
|
|
327
|
+
- Unsigned `encode(...)` treats inputs as `uint64` and reduces values modulo `M`.
|
|
328
|
+
- `encode_signed(...)` accepts only integers in the centered interval and refuses silent wrapping.
|
|
329
|
+
- `decode_signed(...)` returns the canonical centered `int64` representative.
|
|
330
|
+
- `accumulate_weighted_int32(...)` requires an array whose dtype is exactly `int32`.
|
|
331
|
+
- Encoded rails use:
|
|
332
|
+
- `r0`: `uint16`
|
|
333
|
+
- `r1`: `uint16`
|
|
334
|
+
- `r2`: `uint16`
|
|
335
|
+
- `r3`: `uint32`
|
|
336
|
+
- `EncodedArray` stores four read-only rails.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Existing verified benchmark
|
|
341
|
+
|
|
342
|
+
Verified on Google Colab Linux x86-64 with `AVX2=True`, two OpenMP processors, and two OpenMP threads. Workloads used the installed wheel.
|
|
343
|
+
|
|
344
|
+
Median throughput over five runs on 1,000,000 `uint64` values:
|
|
345
|
+
|
|
346
|
+
### Fused single-step affine
|
|
347
|
+
|
|
348
|
+
Workload: `fma_u64_io(x, 1_000_003, 7)`
|
|
349
|
+
|
|
350
|
+
- `fma_u64_io`: **47.8 million values/sec**
|
|
351
|
+
- `fma_u64_io_omp` with one thread: **80.7 million values/sec**
|
|
352
|
+
- `fma_u64_io_omp` with two threads: **84.6 million values/sec**
|
|
353
|
+
|
|
354
|
+
### Repeated affine loop
|
|
355
|
+
|
|
356
|
+
Workload: `affine_repeat_u64_io(x, 1_000_003, 7, iterations=1000)`
|
|
357
|
+
|
|
358
|
+
- scalar fused path: **61.19 billion ops/sec**
|
|
359
|
+
- OpenMP with one thread: **82.80 billion ops/sec**
|
|
360
|
+
- OpenMP with two threads: **94.86 billion ops/sec**
|
|
361
|
+
|
|
362
|
+
These measurements cover the existing affine kernels, not the new weighted INT32 orchestration path.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Build from source
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
git clone https://github.com/playfularchitect/rns_engine.git
|
|
370
|
+
cd rns_engine
|
|
371
|
+
pip install -e .
|
|
372
|
+
python -m pytest tests/ -v
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Requirements:
|
|
376
|
+
|
|
377
|
+
- Python 3.10–3.13
|
|
378
|
+
- C++17 compiler
|
|
379
|
+
- Access to the build dependencies declared in `pyproject.toml`
|
|
380
|
+
|
|
381
|
+
The release workflow compiles and tests the package on Linux, macOS, and Windows, then installs and tests every produced wheel before publication.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Introspection
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
import rns_engine as rns
|
|
389
|
+
|
|
390
|
+
rns.info()
|
|
391
|
+
|
|
392
|
+
rns.M
|
|
393
|
+
rns.HALF_M
|
|
394
|
+
rns.SIGNED_MIN
|
|
395
|
+
rns.SIGNED_MAX
|
|
396
|
+
rns.M0
|
|
397
|
+
rns.M1
|
|
398
|
+
rns.M2
|
|
399
|
+
rns.M3
|
|
400
|
+
rns.HAS_AVX2
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Current release
|
|
406
|
+
|
|
407
|
+
### v0.6.0
|
|
408
|
+
|
|
409
|
+
- weighted signed INT32 partial accumulation
|
|
410
|
+
- exact arbitrary-precision positional weights
|
|
411
|
+
- per-term magnitude receipts and strict no-wrap certification
|
|
412
|
+
- canonical modular and uniqueness-gated signed decode paths
|
|
413
|
+
- canonical centered signed encoding and decoding
|
|
414
|
+
- exact dot-product and GEMM output-bound receipts
|
|
415
|
+
- `SignedSession` high-level API
|
|
416
|
+
- four-rail native engine (`127 × 8191 × 65536 × 524287`)
|
|
417
|
+
- AVX2, OpenMP, and auto-dispatch kernel families
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## License
|
|
422
|
+
|
|
423
|
+
**AGPL-3.0-only**
|
|
424
|
+
|
|
425
|
+
If this software is used in a network service, the AGPL requires modified source to be made available to users. Commercial licensing for proprietary or closed-source use is available. Inquiries: `ewesley541@gmail.com`
|
|
426
|
+
|
|
427
|
+
Copyright 2026 Evan Wesley
|