phanerozoic commited on
Commit
bc94f1d
·
verified ·
1 Parent(s): 92f7d9a

Uploaded using `kernel-builder`.

Browse files
Files changed (45) hide show
  1. build/torch211-cxx11-cu126-x86_64-linux/__init__.py +267 -0
  2. build/torch211-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  3. build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  4. build/torch211-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
  5. build/torch211-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  6. build/torch211-cxx11-cu128-x86_64-linux/__init__.py +267 -0
  7. build/torch211-cxx11-cu128-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  8. build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
  9. build/torch211-cxx11-cu128-x86_64-linux/groebner/__init__.py +26 -0
  10. build/torch211-cxx11-cu128-x86_64-linux/metadata.json +38 -0
  11. build/torch211-cxx11-cu130-x86_64-linux/__init__.py +267 -0
  12. build/torch211-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  13. build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  14. build/torch211-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
  15. build/torch211-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  16. build/torch212-cxx11-cu126-x86_64-linux/__init__.py +267 -0
  17. build/torch212-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  18. build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  19. build/torch212-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
  20. build/torch212-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  21. build/torch212-cxx11-cu130-x86_64-linux/__init__.py +267 -0
  22. build/torch212-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  23. build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  24. build/torch212-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
  25. build/torch212-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  26. build/torch212-cxx11-cu132-x86_64-linux/__init__.py +267 -0
  27. build/torch212-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  28. build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  29. build/torch212-cxx11-cu132-x86_64-linux/groebner/__init__.py +26 -0
  30. build/torch212-cxx11-cu132-x86_64-linux/metadata.json +38 -0
  31. build/torch213-cxx11-cu126-x86_64-linux/__init__.py +267 -0
  32. build/torch213-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  33. build/torch213-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  34. build/torch213-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
  35. build/torch213-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  36. build/torch213-cxx11-cu130-x86_64-linux/__init__.py +267 -0
  37. build/torch213-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  38. build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  39. build/torch213-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
  40. build/torch213-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  41. build/torch213-cxx11-cu132-x86_64-linux/__init__.py +267 -0
  42. build/torch213-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
  43. build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  44. build/torch213-cxx11-cu132-x86_64-linux/groebner/__init__.py +26 -0
  45. build/torch213-cxx11-cu132-x86_64-linux/metadata.json +38 -0
build/torch211-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch211-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:02499b3bf1c8ad4a4e1ab540e409f869174d6e710b0c55a5e2ffa330a2f51850
3
+ size 166200
build/torch211-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch211-cxx11-cu126-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
20
+ "_groebner_cuda_0bb395a.abi3.so": "AkmbO/HIrUpOGrVA5An4aRdNbnELDFWl4v+jMKL1GFA=",
21
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
22
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch211-cxx11-cu128-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch211-cxx11-cu128-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b257474cc92ab20316c00b8c65f403ea8a06b8d42b7d29f0fd69861b5404e240
3
+ size 224096
build/torch211-cxx11-cu128-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch211-cxx11-cu128-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu128-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "sldHTMkqsgMWwAuMZfQD6ooGuNQrfSnw/WmGG1QE4kA=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch211-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch211-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:33d5a334a57e555aa2e83e343b1915ab1eafef9bc70706bb9e9f1e192846e23b
3
+ size 252512
build/torch211-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch211-cxx11-cu130-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "M9WjNKV+VVqi6D40OxkVqx6v75vHBwa7np8eGShG4js=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch212-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:858c0fc1e15106567adf8ff66bacea5e5315b866b7b841585a8b4f8160036383
3
+ size 177112
build/torch212-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch212-cxx11-cu126-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
20
+ "_groebner_cuda_0bb395a.abi3.so": "hYwPweFRBlZ634/2a6zqXlMVuGa3uEFYWotPgWADY4M=",
21
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
22
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch212-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch212-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99ec4de9e56b9be851630cca7430c0688e88911c5144e395c6886b23eb9f4854
3
+ size 263312
build/torch212-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch212-cxx11-cu130-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "mexN6eVrm+hRYwzKdDDAaI6IkRxRROOVxohrI+ufSFQ=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch212-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:15421261f06297d516f06075b2cb7e31cadeac5163514efc36bf2a084e446eef
3
+ size 302224
build/torch212-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch212-cxx11-cu132-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "FUISYfBil9UW8GB1sst+McrerFFjUU78Nr8qCE5Ebu8=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch213-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7426cb9830ff96c24c8c5dda578e7afbf10279ca61245da9b90be651ba4a1f1b
3
+ size 177112
build/torch213-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch213-cxx11-cu126-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
20
+ "_groebner_cuda_0bb395a.abi3.so": "dCbLmDD/lsJMjF3aV456+/ECecphJF2puQvmUbpKHxs=",
21
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
22
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch213-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch213-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46f55345299d5c3337c035473b5293853dece7b25bd6c2deca2b493f087b2b56
3
+ size 263312
build/torch213-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch213-cxx11-cu130-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "RvVTRSmdXDM3wDVHO1KThT3s57Jb1sLeyitJPwh7K1Y=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """groebner: Groebner bases over F_p with GPU Macaulay matrix reduction.
2
+
3
+ Implements an F4-style algorithm in degree-reverse-lexicographic order. Critical
4
+ pairs of minimal lcm degree are processed as a batch: their S-polynomial rows
5
+ plus every reductor found by symbolic preprocessing form a Macaulay matrix whose
6
+ columns are monomials, and that matrix is reduced to reduced row echelon form on
7
+ the GPU. Rows whose leading monomial is not already divisible by a leading
8
+ monomial of the basis become new basis elements.
9
+
10
+ groebner_basis(polys, nvars, prime) -> reduced Groebner basis
11
+
12
+ Polynomials are dicts mapping exponent tuples to coefficients in F_p.
13
+
14
+ Reference baselines: msolve, Singular ``std``.
15
+ """
16
+
17
+ from itertools import combinations
18
+
19
+ import torch
20
+
21
+ from ._ops import ops
22
+
23
+ __all__ = ["groebner_basis", "monomial_key", "spoly", "normal_form", "is_groebner_basis"]
24
+
25
+
26
+ # ---------------------------------------------------------------- monomials
27
+
28
+ def monomial_key(exps):
29
+ """Sort key for degree-reverse-lexicographic order, ascending."""
30
+ return (sum(exps), tuple(-e for e in reversed(exps)))
31
+
32
+
33
+ def _mul(a, b):
34
+ return tuple(x + y for x, y in zip(a, b))
35
+
36
+
37
+ def _div(a, b):
38
+ """a / b when b divides a, else None."""
39
+ out = []
40
+ for x, y in zip(a, b):
41
+ if x < y:
42
+ return None
43
+ out.append(x - y)
44
+ return tuple(out)
45
+
46
+
47
+ def _lcm(a, b):
48
+ return tuple(max(x, y) for x, y in zip(a, b))
49
+
50
+
51
+ def _divides(b, a):
52
+ return all(y <= x for x, y in zip(a, b))
53
+
54
+
55
+ def _lead(poly):
56
+ """Leading monomial in degrevlex."""
57
+ return max(poly, key=monomial_key)
58
+
59
+
60
+ # ---------------------------------------------------------------- polynomials
61
+
62
+ def _normalize(poly, p):
63
+ out = {}
64
+ for m, c in poly.items():
65
+ c %= p
66
+ if c:
67
+ out[m] = c
68
+ return out
69
+
70
+
71
+ def _scale_shift(poly, mono, coeff, p):
72
+ return {_mul(m, mono): (c * coeff) % p for m, c in poly.items()}
73
+
74
+
75
+ def _sub(f, g, p):
76
+ out = dict(f)
77
+ for m, c in g.items():
78
+ v = (out.get(m, 0) - c) % p
79
+ if v:
80
+ out[m] = v
81
+ elif m in out:
82
+ del out[m]
83
+ return out
84
+
85
+
86
+ def spoly(f, g, p):
87
+ """S-polynomial of two nonzero polynomials over F_p."""
88
+ lf, lg = _lead(f), _lead(g)
89
+ L = _lcm(lf, lg)
90
+ a = _scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p)
91
+ b = _scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p)
92
+ return _sub(a, b, p)
93
+
94
+
95
+ def normal_form(f, basis, p):
96
+ """Fully reduce ``f`` modulo ``basis`` in degrevlex."""
97
+ f = _normalize(f, p)
98
+ out = {}
99
+ while f:
100
+ m = _lead(f)
101
+ c = f[m]
102
+ for g in basis:
103
+ lg = _lead(g)
104
+ q = _div(m, lg)
105
+ if q is not None:
106
+ factor = (c * pow(g[lg], -1, p)) % p
107
+ f = _sub(f, _scale_shift(g, q, factor, p), p)
108
+ break
109
+ else:
110
+ out[m] = c
111
+ del f[m]
112
+ return out
113
+
114
+
115
+ # ---------------------------------------------------------------- reduction
116
+
117
+ def _reduce_matrix(rows, monomials, prime, device):
118
+ """Reduce the Macaulay matrix on the GPU, returning its nonzero rows."""
119
+ index = {m: i for i, m in enumerate(monomials)}
120
+ m, n = len(rows), len(monomials)
121
+ dense = torch.zeros(m, n, dtype=torch.int32)
122
+ flat = dense.view(-1)
123
+ for r, poly in enumerate(rows):
124
+ base = r * n
125
+ for mono, coeff in poly.items():
126
+ flat[base + index[mono]] = coeff % prime
127
+ dense = dense.to(device).contiguous()
128
+
129
+ rank = torch.zeros(1, dtype=torch.int32, device=device)
130
+ piv = torch.full((m,), -1, dtype=torch.int32, device=device)
131
+ ops.groebner_reduce(dense, rank, piv, prime)
132
+
133
+ r = int(rank.item())
134
+ if r == 0:
135
+ return []
136
+ host = dense[:r].cpu().numpy()
137
+ out = []
138
+ for i in range(r):
139
+ row = host[i]
140
+ nz = row.nonzero()[0]
141
+ out.append({monomials[j]: int(row[j]) for j in nz})
142
+ return out
143
+
144
+
145
+ def _symbolic_preprocess(rows, basis, p):
146
+ """Close the row set under reduction by the basis leading monomials."""
147
+ monomials = set()
148
+ for poly in rows:
149
+ monomials.update(poly)
150
+ done = set()
151
+ rows = list(rows)
152
+ while True:
153
+ pending = monomials - done
154
+ if not pending:
155
+ break
156
+ for mono in sorted(pending, key=monomial_key, reverse=True):
157
+ done.add(mono)
158
+ for g in basis:
159
+ lg = _lead(g)
160
+ q = _div(mono, lg)
161
+ if q is not None:
162
+ row = _scale_shift(g, q, pow(g[lg], -1, p), p)
163
+ rows.append(row)
164
+ monomials.update(row)
165
+ break
166
+ return rows, monomials
167
+
168
+
169
+ # ---------------------------------------------------------------- driver
170
+
171
+ def groebner_basis(polys, nvars, prime, device=None):
172
+ """Reduced Groebner basis of the ideal generated by ``polys`` over F_p.
173
+
174
+ Args:
175
+ polys: iterable of dicts mapping length-``nvars`` exponent tuples to
176
+ integer coefficients.
177
+ nvars: number of variables.
178
+ prime: field characteristic, an odd prime below 2^31.
179
+ device: torch device; defaults to ``cuda``.
180
+
181
+ Returns:
182
+ the reduced Groebner basis in degree-reverse-lexicographic order, each
183
+ element monic, sorted by descending leading monomial.
184
+ """
185
+ device = torch.device("cuda") if device is None else device
186
+ p = int(prime)
187
+ basis = [_normalize(f, p) for f in polys]
188
+ basis = [f for f in basis if f]
189
+ if not basis:
190
+ return []
191
+ for f in basis:
192
+ if len(_lead(f)) != nvars:
193
+ raise ValueError("every exponent tuple must have length nvars")
194
+
195
+ pairs = {frozenset((i, j)) for i, j in combinations(range(len(basis)), 2)}
196
+
197
+ while pairs:
198
+ # Normal selection: take every pair whose lcm has minimal degree.
199
+ info = []
200
+ for pair in pairs:
201
+ i, j = tuple(pair) if len(pair) == 2 else (min(pair), min(pair))
202
+ L = _lcm(_lead(basis[i]), _lead(basis[j]))
203
+ info.append((sum(L), L, i, j, pair))
204
+ mindeg = min(x[0] for x in info)
205
+ batch = [x for x in info if x[0] == mindeg]
206
+ for x in batch:
207
+ pairs.discard(x[4])
208
+
209
+ rows = []
210
+ for _, L, i, j, _ in batch:
211
+ f, g = basis[i], basis[j]
212
+ lf, lg = _lead(f), _lead(g)
213
+ # Coprime leading monomials give an S-polynomial that reduces to
214
+ # zero by Buchberger's first criterion; the pair carries no
215
+ # information and is skipped.
216
+ if _mul(lf, lg) == L:
217
+ continue
218
+ rows.append(_scale_shift(f, _div(L, lf), pow(f[lf], -1, p), p))
219
+ rows.append(_scale_shift(g, _div(L, lg), pow(g[lg], -1, p), p))
220
+ if not rows:
221
+ continue
222
+
223
+ rows, monomials = _symbolic_preprocess(rows, basis, p)
224
+ ordered = sorted(monomials, key=monomial_key, reverse=True)
225
+ reduced = _reduce_matrix(rows, ordered, p, device)
226
+
227
+ leads = [_lead(g) for g in basis]
228
+ for row in reduced:
229
+ lm = _lead(row)
230
+ if any(_divides(l, lm) for l in leads):
231
+ continue
232
+ basis.append(row)
233
+ leads.append(lm)
234
+ new = len(basis) - 1
235
+ for k in range(new):
236
+ pairs.add(frozenset((k, new)))
237
+
238
+ return _interreduce(basis, p)
239
+
240
+
241
+ def _interreduce(basis, p):
242
+ """Drop redundant elements and fully reduce the survivors."""
243
+ leads = [_lead(g) for g in basis]
244
+ keep = []
245
+ for i, g in enumerate(basis):
246
+ if any(j != i and _divides(leads[j], leads[i]) and
247
+ (leads[j] != leads[i] or j < i) for j in range(len(basis))):
248
+ continue
249
+ keep.append(g)
250
+
251
+ out = []
252
+ for i, g in enumerate(keep):
253
+ others = keep[:i] + keep[i + 1:] + out
254
+ r = normal_form(g, [h for h in others if h], p)
255
+ if r:
256
+ lm = _lead(r)
257
+ out.append(_scale_shift(r, tuple([0] * len(lm)), pow(r[lm], -1, p), p))
258
+ return sorted(out, key=lambda g: monomial_key(_lead(g)), reverse=True)
259
+
260
+
261
+ def is_groebner_basis(basis, prime):
262
+ """True when every S-polynomial of the basis reduces to zero."""
263
+ p = int(prime)
264
+ for f, g in combinations(basis, 2):
265
+ if normal_form(spoly(f, g, p), basis, p):
266
+ return False
267
+ return True
build/torch213-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ae8748c0564ec772519295004771a64bd9c39407ed5235da7d93cd1b3ad8863
3
+ size 302224
build/torch213-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _groebner_cuda_0bb395a
3
+ ops = torch.ops._groebner_cuda_0bb395a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_groebner_cuda_0bb395a::{op_name}"
build/torch213-cxx11-cu132-x86_64-linux/groebner/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "groebner",
3
+ "id": "_groebner_cuda_0bb395a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "CpsyiOVGquYAg8QliZu4XdtavY7LBxkm/VqYtFWi+1o=",
22
+ "_groebner_cuda_0bb395a.abi3.so": "muh0jAVk7HclGSlQBHcaZL2cOUB+1SNdp9k80bOtiGM=",
23
+ "_ops.py": "HBf/A1FZVh5Vgeu7QDu0SfwcwNiTAPBSdQ0M9mx+p7M=",
24
+ "groebner/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "19aaa6421e674e9fecc352bbae6eab81d19a6bf4",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "0bb395ae2658cdd9257c1433b06492b34c1a1378",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }