rns-engine 0.5.0__tar.gz → 0.7.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.
Files changed (27) hide show
  1. rns_engine-0.7.0/PKG-INFO +429 -0
  2. rns_engine-0.7.0/README.md +406 -0
  3. {rns_engine-0.5.0 → rns_engine-0.7.0}/pyproject.toml +1 -1
  4. {rns_engine-0.5.0 → rns_engine-0.7.0}/setup.py +16 -8
  5. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine/__init__.py +7 -0
  6. rns_engine-0.7.0/src/rns_engine/_weighted.cpp +141 -0
  7. rns_engine-0.7.0/src/rns_engine/weighted.py +204 -0
  8. rns_engine-0.7.0/src/rns_engine.egg-info/PKG-INFO +429 -0
  9. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine.egg-info/SOURCES.txt +5 -1
  10. rns_engine-0.7.0/tests/test_weighted.py +209 -0
  11. rns_engine-0.7.0/tests/test_weighted_benchmark.py +71 -0
  12. rns_engine-0.5.0/PKG-INFO +0 -439
  13. rns_engine-0.5.0/README.md +0 -416
  14. rns_engine-0.5.0/src/rns_engine.egg-info/PKG-INFO +0 -439
  15. {rns_engine-0.5.0 → rns_engine-0.7.0}/LICENSE +0 -0
  16. {rns_engine-0.5.0 → rns_engine-0.7.0}/setup.cfg +0 -0
  17. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine/_core.cpp +0 -0
  18. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine/engine.py +0 -0
  19. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine/signed.py +0 -0
  20. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine.egg-info/dependency_links.txt +0 -0
  21. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine.egg-info/not-zip-safe +0 -0
  22. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine.egg-info/requires.txt +0 -0
  23. {rns_engine-0.5.0 → rns_engine-0.7.0}/src/rns_engine.egg-info/top_level.txt +0 -0
  24. {rns_engine-0.5.0 → rns_engine-0.7.0}/tests/test_packaging.py +0 -0
  25. {rns_engine-0.5.0 → rns_engine-0.7.0}/tests/test_rns.py +0 -0
  26. {rns_engine-0.5.0 → rns_engine-0.7.0}/tests/test_session.py +0 -0
  27. {rns_engine-0.5.0 → rns_engine-0.7.0}/tests/test_signed.py +0 -0
@@ -0,0 +1,429 @@
1
+ Metadata-Version: 2.4
2
+ Name: rns_engine
3
+ Version: 0.7.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.7 provides the fused 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 uses one fused native `_weighted` call to read signed INT32 partials, collect exact per-term magnitude bounds, apply positional weights modulo `M`, and accumulate all four rails. The pre-fusion staged encode → scale → add body remains available internally as an exact reference for A/B tests. Local AVX2 diagnostics measured roughly 2.1×–2.9× speedup at one million outputs and larger gains on tiny many-term cases; those figures are hardware-specific CPU evidence, not CUDA, Tensor Core, or universal performance claims.
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.7.0
408
+
409
+ - fused native weighted signed INT32 partial accumulation
410
+ - one compiled pass for magnitude collection, positional weighting, and four-rail accumulation
411
+ - staged reference body retained for exact fused-versus-staged A/B benchmarking
412
+ - exact arbitrary-precision positional weights
413
+ - per-term magnitude receipts and strict no-wrap certification
414
+ - canonical modular and uniqueness-gated signed decode paths
415
+ - canonical centered signed encoding and decoding
416
+ - exact dot-product and GEMM output-bound receipts
417
+ - `SignedSession` high-level API
418
+ - four-rail native engine (`127 × 8191 × 65536 × 524287`)
419
+ - AVX2, OpenMP, and auto-dispatch kernel families
420
+
421
+ ---
422
+
423
+ ## License
424
+
425
+ **AGPL-3.0-only**
426
+
427
+ 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`
428
+
429
+ Copyright 2026 Evan Wesley