ampzip-engine 1.0.0b1__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.
@@ -0,0 +1,200 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "ampzip_engine"
7
+ version = "1.0.0"
8
+ dependencies = [
9
+ "blake2",
10
+ "pyo3",
11
+ ]
12
+
13
+ [[package]]
14
+ name = "blake2"
15
+ version = "0.10.6"
16
+ source = "registry+https://github.com/rust-lang/crates.io-index"
17
+ checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
18
+ dependencies = [
19
+ "digest",
20
+ ]
21
+
22
+ [[package]]
23
+ name = "block-buffer"
24
+ version = "0.10.4"
25
+ source = "registry+https://github.com/rust-lang/crates.io-index"
26
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
27
+ dependencies = [
28
+ "generic-array",
29
+ ]
30
+
31
+ [[package]]
32
+ name = "crypto-common"
33
+ version = "0.1.7"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
36
+ dependencies = [
37
+ "generic-array",
38
+ "typenum",
39
+ ]
40
+
41
+ [[package]]
42
+ name = "digest"
43
+ version = "0.10.7"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
46
+ dependencies = [
47
+ "block-buffer",
48
+ "crypto-common",
49
+ "subtle",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "generic-array"
54
+ version = "0.14.7"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
57
+ dependencies = [
58
+ "typenum",
59
+ "version_check",
60
+ ]
61
+
62
+ [[package]]
63
+ name = "heck"
64
+ version = "0.5.0"
65
+ source = "registry+https://github.com/rust-lang/crates.io-index"
66
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
67
+
68
+ [[package]]
69
+ name = "libc"
70
+ version = "0.2.186"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
73
+
74
+ [[package]]
75
+ name = "once_cell"
76
+ version = "1.21.4"
77
+ source = "registry+https://github.com/rust-lang/crates.io-index"
78
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
79
+
80
+ [[package]]
81
+ name = "portable-atomic"
82
+ version = "1.13.1"
83
+ source = "registry+https://github.com/rust-lang/crates.io-index"
84
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
85
+
86
+ [[package]]
87
+ name = "proc-macro2"
88
+ version = "1.0.106"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
91
+ dependencies = [
92
+ "unicode-ident",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "pyo3"
97
+ version = "0.29.0"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
100
+ dependencies = [
101
+ "libc",
102
+ "once_cell",
103
+ "portable-atomic",
104
+ "pyo3-build-config",
105
+ "pyo3-ffi",
106
+ "pyo3-macros",
107
+ ]
108
+
109
+ [[package]]
110
+ name = "pyo3-build-config"
111
+ version = "0.29.0"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
114
+ dependencies = [
115
+ "target-lexicon",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "pyo3-ffi"
120
+ version = "0.29.0"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
123
+ dependencies = [
124
+ "libc",
125
+ "pyo3-build-config",
126
+ ]
127
+
128
+ [[package]]
129
+ name = "pyo3-macros"
130
+ version = "0.29.0"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
133
+ dependencies = [
134
+ "proc-macro2",
135
+ "pyo3-macros-backend",
136
+ "quote",
137
+ "syn",
138
+ ]
139
+
140
+ [[package]]
141
+ name = "pyo3-macros-backend"
142
+ version = "0.29.0"
143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
144
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
145
+ dependencies = [
146
+ "heck",
147
+ "proc-macro2",
148
+ "quote",
149
+ "syn",
150
+ ]
151
+
152
+ [[package]]
153
+ name = "quote"
154
+ version = "1.0.45"
155
+ source = "registry+https://github.com/rust-lang/crates.io-index"
156
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
157
+ dependencies = [
158
+ "proc-macro2",
159
+ ]
160
+
161
+ [[package]]
162
+ name = "subtle"
163
+ version = "2.6.1"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
166
+
167
+ [[package]]
168
+ name = "syn"
169
+ version = "2.0.118"
170
+ source = "registry+https://github.com/rust-lang/crates.io-index"
171
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
172
+ dependencies = [
173
+ "proc-macro2",
174
+ "quote",
175
+ "unicode-ident",
176
+ ]
177
+
178
+ [[package]]
179
+ name = "target-lexicon"
180
+ version = "0.13.5"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
183
+
184
+ [[package]]
185
+ name = "typenum"
186
+ version = "1.20.1"
187
+ source = "registry+https://github.com/rust-lang/crates.io-index"
188
+ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
189
+
190
+ [[package]]
191
+ name = "unicode-ident"
192
+ version = "1.0.24"
193
+ source = "registry+https://github.com/rust-lang/crates.io-index"
194
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
195
+
196
+ [[package]]
197
+ name = "version_check"
198
+ version = "0.9.5"
199
+ source = "registry+https://github.com/rust-lang/crates.io-index"
200
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "ampzip_engine"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+ description = "Spinor-helicity move-execution engine for AmpZip (PyO3)"
6
+
7
+ [lib]
8
+ name = "ampzip_engine"
9
+ # rlib (alongside cdylib) lets `cargo test` build a test harness against the crate.
10
+ crate-type = ["cdylib", "rlib"]
11
+
12
+ [dependencies]
13
+ pyo3 = { version = "0.29.0" }
14
+ # Dedup keys: blake2b-128 of the canonical string, byte-identical to what the search used to
15
+ # compute in Python with hashlib (so checkpoints and run records stay comparable).
16
+ blake2 = "0.10"
17
+
18
+ [features]
19
+ # `extension-module` tells pyo3 not to link libpython, since the host Python provides its
20
+ # symbols. The wheel requires it and maturin enables it through pyproject.toml. It is left
21
+ # off for `cargo test`, so the test binary links libpython and the pure-Rust unit tests run.
22
+ extension-module = ["pyo3/extension-module"]
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: ampzip-engine
3
+ Version: 1.0.0b1
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3 :: Only
6
+ Classifier: Topic :: Scientific/Engineering :: Physics
7
+ Summary: Move-execution engine of AmpLens (used by AmpZip): spinor-helicity identities, canonical rational functions, finite-field evaluation
8
+ Author: Yifan Fei, Zhen Liu
9
+ License-Expression: MIT
10
+ Requires-Python: >=3.12
11
+ Project-URL: Homepage, https://github.com/AmpLens/AmpLens
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.5,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "ampzip-engine"
7
+ version = "1.0.0b1"
8
+ description = "Move-execution engine of AmpLens (used by AmpZip): spinor-helicity identities, canonical rational functions, finite-field evaluation"
9
+ license = "MIT"
10
+ requires-python = ">=3.12"
11
+ authors = [
12
+ { name = "Yifan Fei" },
13
+ { name = "Zhen Liu" },
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Rust",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Topic :: Scientific/Engineering :: Physics",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/AmpLens/AmpLens"
23
+
24
+ [tool.maturin]
25
+ # The import name is the [lib] name in Cargo.toml, `ampzip_engine`. extension-module is
26
+ # enabled for the wheel only and kept off for `cargo test`. abi3 gives one wheel per platform
27
+ # for CPython 3.12 and later.
28
+ features = ["pyo3/extension-module", "pyo3/abi3-py312"]
29
+ exclude = ["target/**"]
@@ -0,0 +1,245 @@
1
+ //! Exact evaluation of an `Ast` or `RatFn` over F_p at an on-shell point.
2
+ //! Brackets are 2x2 minors of the spinors `lam`/`lamt`, matching
3
+ //! `ampzip.verify.vals_from_point`. The finite-field verifier uses `eval_pair`
4
+ //! through `Ast.eval_pair` to compare expressions without division.
5
+ use crate::parse::Ast;
6
+ use crate::poly::{Mono, Poly, RatFn};
7
+ use std::collections::HashMap;
8
+
9
+ /// Values of the named parameters at one evaluation point.
10
+ pub(crate) type Params = HashMap<String, u64>;
11
+
12
+ pub(crate) fn mulmod(a: u64, b: u64, p: u64) -> u64 {
13
+ ((a as u128 * b as u128) % p as u128) as u64
14
+ }
15
+ pub(crate) fn powmod(mut base: u64, mut e: u64, p: u64) -> u64 {
16
+ base %= p;
17
+ let mut r = 1u64 % p;
18
+ while e > 0 {
19
+ if e & 1 == 1 {
20
+ r = mulmod(r, base, p);
21
+ }
22
+ base = mulmod(base, base, p);
23
+ e >>= 1;
24
+ }
25
+ r
26
+ }
27
+ pub(crate) fn modinv(a: u64, p: u64) -> Result<u64, String> {
28
+ if a.is_multiple_of(p) {
29
+ return Err("inverse of 0 (degenerate point)".to_string());
30
+ }
31
+ Ok(powmod(a, p - 2, p))
32
+ }
33
+ /// A decimal integer of any length reduced mod p.
34
+ pub(crate) fn big_mod(digits: &str, p: u64) -> u64 {
35
+ digits.bytes().fold(0u64, |acc, b| (mulmod(acc, 10, p) + (b - b'0') as u64) % p)
36
+ }
37
+
38
+ /// A square root of `a` mod the odd prime `p` by Tonelli-Shanks. The result depends only on
39
+ /// `a` and `p`, so every occurrence of the same root agrees. Err when `a` has no root mod `p`.
40
+ pub(crate) fn sqrt_mod(a: u64, p: u64) -> Result<u64, String> {
41
+ let a = a % p;
42
+ if a == 0 {
43
+ return Ok(0);
44
+ }
45
+ if p == 2 {
46
+ return Ok(a);
47
+ }
48
+ if powmod(a, (p - 1) / 2, p) != 1 {
49
+ return Err(format!("{} has no square root mod {}", a, p));
50
+ }
51
+ if p % 4 == 3 {
52
+ return Ok(powmod(a, (p + 1) / 4, p));
53
+ }
54
+ // p - 1 = q * 2^s with q odd; z is the smallest non-residue.
55
+ let mut q = p - 1;
56
+ let mut s_ = 0u32;
57
+ while q % 2 == 0 {
58
+ q /= 2;
59
+ s_ += 1;
60
+ }
61
+ let mut z = 2u64;
62
+ while powmod(z, (p - 1) / 2, p) != p - 1 {
63
+ z += 1;
64
+ }
65
+ let mut m = s_;
66
+ let mut c = powmod(z, q, p);
67
+ let mut t = powmod(a, q, p);
68
+ let mut r = powmod(a, (q + 1) / 2, p);
69
+ while t != 1 {
70
+ let mut i = 0u32;
71
+ let mut t2 = t;
72
+ while t2 != 1 {
73
+ t2 = mulmod(t2, t2, p);
74
+ i += 1;
75
+ }
76
+ let b = powmod(c, 1u64 << (m - i - 1), p);
77
+ m = i;
78
+ c = mulmod(b, b, p);
79
+ t = mulmod(t, c, p);
80
+ r = mulmod(r, b, p);
81
+ }
82
+ Ok(r)
83
+ }
84
+
85
+ fn param_value(name: &str, params: &Params) -> Result<u64, String> {
86
+ params.get(name).copied().ok_or_else(|| format!("no value supplied for parameter '{}'", name))
87
+ }
88
+
89
+ pub(crate) fn minor(square: bool, i: usize, j: usize, lam: &[Vec<u64>], lamt: &[Vec<u64>], p: u64) -> Result<u64, String> {
90
+ let v = if square { lamt } else { lam };
91
+ if i > v.len() || j > v.len() {
92
+ return Err(format!("index out of range ({},{})", i, j));
93
+ }
94
+ let (a0, a1) = (v[i - 1][0] % p, v[i - 1][1] % p);
95
+ let (b0, b1) = (v[j - 1][0] % p, v[j - 1][1] % p);
96
+ Ok((mulmod(a0, b1, p) + p - mulmod(a1, b0, p)) % p)
97
+ }
98
+
99
+ pub(crate) fn eval_ast(a: &Ast, lam: &[Vec<u64>], lamt: &[Vec<u64>], p: u64, params: &Params) -> Result<u64, String> {
100
+ match a {
101
+ Ast::Int(n) => Ok((((n % p as i64) + p as i64) % p as i64) as u64),
102
+ Ast::Big(d) => Ok(big_mod(d, p)),
103
+ Ast::ImagUnit => sqrt_mod(p - 1, p).map_err(|_| format!("I needs a prime p = 1 (mod 4); {} is not", p)),
104
+ Ast::Sqrt(q) => sqrt_mod(*q, p).map_err(|_| format!("sqrt({}) needs a prime where {} is a square; {} is not", q, q, p)),
105
+ Ast::Param(name) => param_value(name, params),
106
+ Ast::Bracket { square, i, j } => minor(*square, *i, *j, lam, lamt, p),
107
+ Ast::Neg(x) => Ok((p - eval_ast(x, lam, lamt, p, params)?) % p),
108
+ Ast::Add(xs) => {
109
+ let mut s = 0u64;
110
+ for x in xs {
111
+ s = (s + eval_ast(x, lam, lamt, p, params)?) % p;
112
+ }
113
+ Ok(s)
114
+ }
115
+ Ast::Mul(xs) => {
116
+ let mut s = 1u64 % p;
117
+ for x in xs {
118
+ s = mulmod(s, eval_ast(x, lam, lamt, p, params)?, p);
119
+ }
120
+ Ok(s)
121
+ }
122
+ Ast::Pow(b, e) => {
123
+ let bv = eval_ast(b, lam, lamt, p, params)?;
124
+ if *e >= 0 {
125
+ Ok(powmod(bv, *e as u64, p))
126
+ } else {
127
+ Ok(powmod(modinv(bv, p)?, (-*e) as u64, p))
128
+ }
129
+ }
130
+ Ast::OpaqueDen(x) => Ok(modinv(eval_ast(x, lam, lamt, p, params)?, p)?),
131
+ }
132
+ }
133
+
134
+ /// Value of an `Ast` at an on-shell point as a fraction `(num, den)` in F_p, with no division
135
+ /// anywhere: sums cross-multiply, products multiply componentwise, a negative power or an
136
+ /// `opaque_den` swaps the two. A vanishing `den` is returned as it is, and the caller treats
137
+ /// the point as degenerate, as the verifier does.
138
+ pub(crate) fn eval_pair(a: &Ast, lam: &[Vec<u64>], lamt: &[Vec<u64>], p: u64, params: &Params) -> Result<(u64, u64), String> {
139
+ let one = 1u64 % p;
140
+ match a {
141
+ Ast::Int(n) => Ok(((((n % p as i64) + p as i64) % p as i64) as u64, one)),
142
+ Ast::Big(_) | Ast::ImagUnit | Ast::Sqrt(_) | Ast::Param(_) => Ok((eval_ast(a, lam, lamt, p, params)?, one)),
143
+ Ast::Bracket { square, i, j } => Ok((minor(*square, *i, *j, lam, lamt, p)?, one)),
144
+ Ast::Neg(x) => {
145
+ let (n, d) = eval_pair(x, lam, lamt, p, params)?;
146
+ Ok(((p - n) % p, d))
147
+ }
148
+ Ast::Add(xs) => {
149
+ let (mut n, mut d) = (0u64, one);
150
+ for x in xs {
151
+ let (n2, d2) = eval_pair(x, lam, lamt, p, params)?;
152
+ n = (mulmod(n, d2, p) + mulmod(n2, d, p)) % p;
153
+ d = mulmod(d, d2, p);
154
+ }
155
+ Ok((n, d))
156
+ }
157
+ Ast::Mul(xs) => {
158
+ let (mut n, mut d) = (one, one);
159
+ for x in xs {
160
+ let (n2, d2) = eval_pair(x, lam, lamt, p, params)?;
161
+ n = mulmod(n, n2, p);
162
+ d = mulmod(d, d2, p);
163
+ }
164
+ Ok((n, d))
165
+ }
166
+ Ast::Pow(b, e) => {
167
+ let (n, d) = eval_pair(b, lam, lamt, p, params)?;
168
+ if *e >= 0 {
169
+ Ok((powmod(n, *e as u64, p), powmod(d, *e as u64, p)))
170
+ } else {
171
+ Ok((powmod(d, (-*e) as u64, p), powmod(n, (-*e) as u64, p)))
172
+ }
173
+ }
174
+ Ast::OpaqueDen(x) => {
175
+ let (n, d) = eval_pair(x, lam, lamt, p, params)?;
176
+ Ok((d, n))
177
+ }
178
+ }
179
+ }
180
+
181
+ /// Product of bracket minors `Π minor(bk)^e` over a monomial, mod p.
182
+ fn eval_mono(m: &Mono, lam: &[Vec<u64>], lamt: &[Vec<u64>], p: u64) -> Result<u64, String> {
183
+ let mut acc = 1u64 % p;
184
+ for (bk, e) in m {
185
+ let v = minor(bk.square, bk.i as usize, bk.j as usize, lam, lamt, p)?;
186
+ acc = mulmod(acc, powmod(v, *e as u64, p), p);
187
+ }
188
+ Ok(acc)
189
+ }
190
+
191
+ /// `Σ coeff * eval_mono(mono)` over a polynomial, mod p.
192
+ fn eval_poly(poly: &Poly, lam: &[Vec<u64>], lamt: &[Vec<u64>], p: u64) -> Result<u64, String> {
193
+ let mut acc = 0u64;
194
+ for (m, c) in poly {
195
+ let cm = (((*c % p as i128) + p as i128) % p as i128) as u64;
196
+ if cm == 0 {
197
+ continue;
198
+ }
199
+ acc = (acc + mulmod(cm, eval_mono(m, lam, lamt, p)?, p)) % p;
200
+ }
201
+ Ok(acc)
202
+ }
203
+
204
+ /// Exact evaluation of a `RatFn` over F_p: `eval_poly(num)` multiplied by the
205
+ /// modular inverse of `eval_mono(den) * product(eval_poly(f) for f in den_opaque)`.
206
+ /// This evaluates a resident `Expr` without parsing its string representation
207
+ /// again and must agree with `eval_ast` on the same expression.
208
+ pub(crate) fn eval_ratfn_modp(
209
+ rf: &RatFn,
210
+ lam: &[Vec<u64>],
211
+ lamt: &[Vec<u64>],
212
+ p: u64,
213
+ ) -> Result<u64, String> {
214
+ let num = eval_poly(&rf.num, lam, lamt, p)?;
215
+ if num == 0 || (rf.den.is_empty() && rf.den_opaque.is_empty()) {
216
+ return Ok(num);
217
+ }
218
+ let mut den = if rf.den.is_empty() { 1 % p } else { eval_mono(&rf.den, lam, lamt, p)? };
219
+ for s in &rf.den_opaque {
220
+ den = mulmod(den, eval_poly(s, lam, lamt, p)?, p);
221
+ }
222
+ Ok(mulmod(num, modinv(den, p)?, p))
223
+ }
224
+
225
+ /// Whether `I` occurs, the primes under square roots and the parameter names, each list
226
+ /// sorted without duplicates.
227
+ pub(crate) fn needs(a: &Ast) -> (bool, Vec<u64>, Vec<String>) {
228
+ fn walk(a: &Ast, imag: &mut bool, roots: &mut Vec<u64>, names: &mut Vec<String>) {
229
+ match a {
230
+ Ast::ImagUnit => *imag = true,
231
+ Ast::Sqrt(q) => roots.push(*q),
232
+ Ast::Param(n) => names.push(n.clone()),
233
+ Ast::Neg(x) | Ast::Pow(x, _) | Ast::OpaqueDen(x) => walk(x, imag, roots, names),
234
+ Ast::Add(xs) | Ast::Mul(xs) => xs.iter().for_each(|x| walk(x, imag, roots, names)),
235
+ Ast::Int(_) | Ast::Big(_) | Ast::Bracket { .. } => {}
236
+ }
237
+ }
238
+ let (mut imag, mut roots, mut names) = (false, Vec::new(), Vec::new());
239
+ walk(a, &mut imag, &mut roots, &mut names);
240
+ roots.sort_unstable();
241
+ roots.dedup();
242
+ names.sort();
243
+ names.dedup();
244
+ (imag, roots, names)
245
+ }