Uploaded using `kernel-builder`.
Browse files- build/torch211-cxx11-cu126-x86_64-linux/__init__.py +267 -0
- build/torch211-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
- build/torch211-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch211-cxx11-cu128-x86_64-linux/__init__.py +267 -0
- build/torch211-cxx11-cu128-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu128-x86_64-linux/groebner/__init__.py +26 -0
- build/torch211-cxx11-cu128-x86_64-linux/metadata.json +38 -0
- build/torch211-cxx11-cu130-x86_64-linux/__init__.py +267 -0
- build/torch211-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
- build/torch211-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch212-cxx11-cu126-x86_64-linux/__init__.py +267 -0
- build/torch212-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
- build/torch212-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch212-cxx11-cu130-x86_64-linux/__init__.py +267 -0
- build/torch212-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
- build/torch212-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch212-cxx11-cu132-x86_64-linux/__init__.py +267 -0
- build/torch212-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu132-x86_64-linux/groebner/__init__.py +26 -0
- build/torch212-cxx11-cu132-x86_64-linux/metadata.json +38 -0
- build/torch213-cxx11-cu126-x86_64-linux/__init__.py +267 -0
- build/torch213-cxx11-cu126-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch213-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu126-x86_64-linux/groebner/__init__.py +26 -0
- build/torch213-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch213-cxx11-cu130-x86_64-linux/__init__.py +267 -0
- build/torch213-cxx11-cu130-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu130-x86_64-linux/groebner/__init__.py +26 -0
- build/torch213-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch213-cxx11-cu132-x86_64-linux/__init__.py +267 -0
- build/torch213-cxx11-cu132-x86_64-linux/_groebner_cuda_0bb395a.abi3.so +3 -0
- build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu132-x86_64-linux/groebner/__init__.py +26 -0
- 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 |
+
}
|