Skip to content

Commit 67e3997

Browse files
Merge pull request #191 from Jake-Moss/master
Add Groebner basis related functions for fmpz_mpoly_vec
2 parents 9a0327f + e95013a commit 67e3997

File tree

3 files changed

+293
-5
lines changed

3 files changed

+293
-5
lines changed

src/flint/test/test_all.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3279,8 +3279,11 @@ def _all_mpoly_vecs():
32793279

32803280
def test_fmpz_mpoly_vec():
32813281
for context, mpoly_vec in _all_mpoly_vecs():
3282+
has_groebner_functions = mpoly_vec is flint.fmpz_mpoly_vec
3283+
32823284
ctx = context.get_context(nvars=2)
32833285
ctx1 = context.get_context(nvars=4)
3286+
x, y = ctx.gens()
32843287

32853288
vec = mpoly_vec(3, ctx)
32863289

@@ -3296,13 +3299,66 @@ def test_fmpz_mpoly_vec():
32963299
assert raises(lambda: vec[None], TypeError)
32973300
assert raises(lambda: vec[-1], IndexError)
32983301

3299-
vec[1] = ctx.from_dict({(1, 1): 1})
3300-
assert vec.to_tuple() == mpoly_vec([ctx.from_dict({}), ctx.from_dict({(1, 1): 1}), ctx.from_dict({})], ctx).to_tuple()
3302+
vec[1] = x * y
3303+
assert vec == mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx)
3304+
assert vec != mpoly_vec([x, x * y, ctx.from_dict({})], ctx)
3305+
assert vec != mpoly_vec([ctx.from_dict({})], ctx)
3306+
assert vec != mpoly_vec([ctx1.from_dict({})], ctx1)
3307+
assert vec.to_tuple() == mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx).to_tuple()
33013308
assert raises(lambda: vec.__setitem__(None, 0), TypeError)
33023309
assert raises(lambda: vec.__setitem__(-1, 0), IndexError)
33033310
assert raises(lambda: vec.__setitem__(0, 0), TypeError)
33043311
assert raises(lambda: vec.__setitem__(0, ctx1.from_dict({})), IncompatibleContextError)
33053312

3313+
if has_groebner_functions:
3314+
ctx = context.get_context(3, flint.Ordering.lex, nametup=('x', 'y', 'z'))
3315+
3316+
# Examples here cannibalised from
3317+
# https://en.wikipedia.org/wiki/Gr%C3%B6bner_basis#Example_and_counterexample
3318+
x, y, z = ctx.gens()
3319+
f = x**2 - y
3320+
f2 = 3 * x**2 - y
3321+
g = x**3 - x
3322+
g2 = x**3 * y - x
3323+
k = x * y - x
3324+
k2 = 3 * x * y - 3 * x
3325+
h = y**2 - y
3326+
3327+
vec = mpoly_vec([f, k, h], ctx)
3328+
assert vec.is_groebner()
3329+
assert vec.is_groebner(mpoly_vec([f, g], ctx))
3330+
assert not vec.is_groebner(mpoly_vec([f, x**3], ctx))
3331+
3332+
assert mpoly_vec([f2, k, h], ctx).is_autoreduced()
3333+
assert not mpoly_vec([f2, k2, h], ctx).is_autoreduced()
3334+
3335+
vec = mpoly_vec([f2, k2, h], ctx)
3336+
vec2 = vec.autoreduction()
3337+
assert not vec.is_autoreduced()
3338+
assert vec2.is_autoreduced()
3339+
assert vec2 == mpoly_vec([3 * x**2 - y, x * y - x, y**2 - y], ctx)
3340+
3341+
vec = mpoly_vec([f, g2], ctx)
3342+
assert not vec.is_groebner()
3343+
assert vec.buchberger_naive() == mpoly_vec([x**2 - y, x**3 * y - x, x * y**2 - x, y**3 - y], ctx)
3344+
assert vec.buchberger_naive(limits=(2, 2, 512)) == (mpoly_vec([x**2 - y, x**3 * y - x], ctx), False)
3345+
3346+
unreduced_basis = mpoly_vec([x**2 - 2, y**2 - 3, z - x - y], ctx).buchberger_naive()
3347+
assert list(unreduced_basis) == [
3348+
x**2 - 2,
3349+
y**2 - 3,
3350+
x + y - z,
3351+
2*y*z - z**2 - 1,
3352+
2*y + z**3 - 11*z,
3353+
z**4 - 10*z**2 + 1
3354+
]
3355+
3356+
assert list(unreduced_basis.autoreduction()) == [
3357+
z**4 - 10 * z**2 + 1,
3358+
2*y + z**3 - 11 * z,
3359+
2 * x - z**3 + 9 * z
3360+
]
3361+
33063362

33073363
def _all_polys_mpolys():
33083364

src/flint/types/fmpq_mpoly.pyx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,5 +991,16 @@ cdef class fmpq_mpoly_vec:
991991
def __repr__(self):
992992
return f"fmpq_mpoly_vec({self}, ctx={self.ctx})"
993993

994+
def __richcmp__(self, other, int op):
995+
if not (op == Py_EQ or op == Py_NE):
996+
return NotImplemented
997+
elif typecheck(other, fmpq_mpoly_vec):
998+
if (<fmpq_mpoly_vec>self).ctx is (<fmpq_mpoly_vec>other).ctx and len(self) == len(other):
999+
return (op == Py_NE) ^ all(x == y for x, y in zip(self, other))
1000+
else:
1001+
return op == Py_NE
1002+
else:
1003+
return NotImplemented
1004+
9941005
def to_tuple(self):
9951006
return tuple(self[i] for i in range(self.length))

src/flint/types/fmpz_mpoly.pyx

Lines changed: 224 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ from flint.flintlib.fmpz cimport fmpz_set
1515
from flint.flintlib.fmpz_mpoly cimport (
1616
fmpz_mpoly_add,
1717
fmpz_mpoly_add_fmpz,
18+
fmpz_mpoly_buchberger_naive,
19+
fmpz_mpoly_buchberger_naive_with_limits,
1820
fmpz_mpoly_clear,
1921
fmpz_mpoly_compose_fmpz_mpoly,
2022
fmpz_mpoly_ctx_init,
@@ -41,20 +43,26 @@ from flint.flintlib.fmpz_mpoly cimport (
4143
fmpz_mpoly_neg,
4244
fmpz_mpoly_pow_fmpz,
4345
fmpz_mpoly_push_term_fmpz_ffmpz,
46+
fmpz_mpoly_reduction_primitive_part,
4447
fmpz_mpoly_scalar_divides_fmpz,
4548
fmpz_mpoly_scalar_mul_fmpz,
4649
fmpz_mpoly_set,
4750
fmpz_mpoly_set_coeff_fmpz_fmpz,
4851
fmpz_mpoly_set_fmpz,
4952
fmpz_mpoly_set_str_pretty,
5053
fmpz_mpoly_sort_terms,
54+
fmpz_mpoly_spoly,
5155
fmpz_mpoly_sqrt_heap,
5256
fmpz_mpoly_sub,
5357
fmpz_mpoly_sub_fmpz,
5458
fmpz_mpoly_total_degree_fmpz,
59+
fmpz_mpoly_vec_autoreduction,
60+
fmpz_mpoly_vec_autoreduction_groebner,
5561
fmpz_mpoly_vec_clear,
5662
fmpz_mpoly_vec_entry,
5763
fmpz_mpoly_vec_init,
64+
fmpz_mpoly_vec_is_autoreduced,
65+
fmpz_mpoly_vec_is_groebner,
5866
)
5967
from flint.flintlib.fmpz_mpoly_factor cimport (
6068
fmpz_mpoly_factor,
@@ -924,13 +932,61 @@ cdef class fmpz_mpoly(flint_mpoly):
924932
fmpz_mpoly_integral(res.val, scale.val, self.val, i, self.ctx.val)
925933
return scale, res
926934

935+
def spoly(self, g):
936+
"""
937+
Compute the S-polynomial of `self` and `g`, scaled to an integer polynomial
938+
by computing the LCM of the leading coefficients.
939+
940+
>>> from flint import Ordering
941+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
942+
>>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1})
943+
>>> g = ctx.from_dict({(3, 0): 1, (1, 0): -1})
944+
>>> f.spoly(g)
945+
-x*y + x
946+
"""
947+
cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx)
948+
949+
if not typecheck(g, fmpz_mpoly):
950+
raise TypeError(f"expected fmpz_mpoly, got {type(g)}")
951+
952+
self.ctx.compatible_context_check((<fmpz_mpoly>g).ctx)
953+
fmpz_mpoly_spoly(res.val, self.val, (<fmpz_mpoly>g).val, self.ctx.val)
954+
return res
955+
956+
def reduction_primitive_part(self, vec):
957+
"""
958+
Compute the the primitive part of the reduction (remainder of multivariate
959+
quasi-division with remainder) with respect to the polynomials `vec`.
960+
961+
>>> from flint import Ordering
962+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
963+
>>> x, y = ctx.gens()
964+
>>> f = 2 * x**3 -x**2 * y + y**3 + 3 * y
965+
>>> g1 = x**2 + y**2 + 1
966+
>>> g2 = x * y - 2
967+
>>> vec = fmpz_mpoly_vec([g1, g2], ctx)
968+
>>> vec
969+
fmpz_mpoly_vec([x^2 + y^2 + 1, x*y - 2], ctx=fmpz_mpoly_ctx(2, '<Ordering.lex: 0>', ('x', 'y')))
970+
>>> f.reduction_primitive_part(vec)
971+
x - y^3
972+
"""
973+
cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx)
974+
if not typecheck(vec, fmpz_mpoly_vec):
975+
raise TypeError(f"expected fmpz_mpoly, got {type(vec)}")
976+
977+
self.ctx.compatible_context_check((<fmpz_mpoly_vec>vec).ctx)
978+
fmpz_mpoly_reduction_primitive_part(res.val, self.val, (<fmpz_mpoly_vec>vec).val, self.ctx.val)
979+
return res
980+
927981

928982
cdef class fmpz_mpoly_vec:
929983
"""
930984
A class representing a vector of fmpz_mpolys.
931985
"""
932986

933-
def __cinit__(self, iterable_or_len, fmpz_mpoly_ctx ctx, bint double_indirect = False):
987+
def __cinit__(
988+
self, iterable_or_len, fmpz_mpoly_ctx ctx, bint double_indirect = False
989+
):
934990
if isinstance(iterable_or_len, int):
935991
length = iterable_or_len
936992
else:
@@ -940,7 +996,9 @@ cdef class fmpz_mpoly_vec:
940996
fmpz_mpoly_vec_init(self.val, length, self.ctx.val)
941997

942998
if double_indirect:
943-
self.double_indirect = <fmpz_mpoly_struct **> libc.stdlib.malloc(length * sizeof(fmpz_mpoly_struct *))
999+
self.double_indirect = <fmpz_mpoly_struct **> libc.stdlib.malloc(
1000+
length * sizeof(fmpz_mpoly_struct *)
1001+
)
9441002
if self.double_indirect is NULL:
9451003
raise MemoryError("malloc returned a null pointer") # pragma: no cover
9461004

@@ -978,7 +1036,9 @@ cdef class fmpz_mpoly_vec:
9781036
elif (<fmpz_mpoly>y).ctx is not self.ctx:
9791037
raise IncompatibleContextError(f"{(<fmpz_mpoly>y).ctx} is not {self.ctx}")
9801038

981-
fmpz_mpoly_set(fmpz_mpoly_vec_entry(self.val, x), (<fmpz_mpoly>y).val, self.ctx.val)
1039+
fmpz_mpoly_set(
1040+
fmpz_mpoly_vec_entry(self.val, x), (<fmpz_mpoly>y).val, self.ctx.val
1041+
)
9821042

9831043
def __len__(self):
9841044
return self.val.length
@@ -994,5 +1054,166 @@ cdef class fmpz_mpoly_vec:
9941054
def __repr__(self):
9951055
return f"fmpz_mpoly_vec({self}, ctx={self.ctx})"
9961056

1057+
def __richcmp__(self, other, int op):
1058+
if not (op == Py_EQ or op == Py_NE):
1059+
return NotImplemented
1060+
elif typecheck(other, fmpz_mpoly_vec):
1061+
if (
1062+
(<fmpz_mpoly_vec>self).ctx is (<fmpz_mpoly_vec>other).ctx
1063+
and len(self) == len(other)
1064+
):
1065+
return (op == Py_NE) ^ all(x == y for x, y in zip(self, other))
1066+
else:
1067+
return op == Py_NE
1068+
else:
1069+
return NotImplemented
1070+
9971071
def to_tuple(self):
9981072
return tuple(self[i] for i in range(self.val.length))
1073+
1074+
def is_groebner(self, other=None) -> bool:
1075+
"""
1076+
Check if self is a Gröbner basis. If `other` is not None then check if self
1077+
is a Gröbner basis for `other`.
1078+
1079+
>>> from flint import Ordering
1080+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
1081+
>>> x, y = ctx.gens()
1082+
>>> f = x**2 - y
1083+
>>> g = x**3 - x
1084+
>>> k = x*y - x
1085+
>>> h = y**2 - y
1086+
>>> vec = fmpz_mpoly_vec([f, k, h], ctx)
1087+
>>> vec.is_groebner()
1088+
True
1089+
>>> vec.is_groebner(fmpz_mpoly_vec([f, g], ctx))
1090+
True
1091+
>>> vec.is_groebner(fmpz_mpoly_vec([f, x**3], ctx))
1092+
False
1093+
"""
1094+
if other is None:
1095+
return <bint>fmpz_mpoly_vec_is_groebner(self.val, NULL, self.ctx.val)
1096+
elif typecheck(other, fmpz_mpoly_vec):
1097+
self.ctx.compatible_context_check((<fmpz_mpoly_vec>other).ctx)
1098+
return <bint>fmpz_mpoly_vec_is_groebner(
1099+
self.val, (<fmpz_mpoly_vec>other).val, self.ctx.val
1100+
)
1101+
else:
1102+
raise TypeError(
1103+
f"expected either None or a fmpz_mpoly_vec, got {type(other)}"
1104+
)
1105+
1106+
def is_autoreduced(self) -> bool:
1107+
"""
1108+
Check if self is auto-reduced (or inter-reduced).
1109+
1110+
>>> from flint import Ordering
1111+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
1112+
>>> x, y = ctx.gens()
1113+
>>> f2 = 3*x**2 - y
1114+
>>> k = x*y - x
1115+
>>> k2 = 3*x*y - 3*x
1116+
>>> h = y**2 - y
1117+
>>> vec = fmpz_mpoly_vec([f2, k, h], ctx)
1118+
>>> vec.is_autoreduced()
1119+
True
1120+
>>> vec = fmpz_mpoly_vec([f2, k2, h], ctx)
1121+
>>> vec.is_autoreduced()
1122+
False
1123+
1124+
"""
1125+
return <bint>fmpz_mpoly_vec_is_autoreduced(self.val, self.ctx.val)
1126+
1127+
def autoreduction(self, groebner=False) -> fmpz_mpoly_vec:
1128+
"""
1129+
Compute the autoreduction of `self`. If `groebner` is True and `self` is a
1130+
Gröbner basis, compute the reduced reduced Gröbner basis of `self`, throws an
1131+
`RuntimeError` otherwise.
1132+
1133+
>>> from flint import Ordering
1134+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
1135+
>>> x, y = ctx.gens()
1136+
>>> f2 = 3*x**2 - y
1137+
>>> k2 = 3*x*y - 3*x
1138+
>>> h = y**2 - y
1139+
>>> vec = fmpz_mpoly_vec([f2, k2, h], ctx)
1140+
>>> vec.is_autoreduced()
1141+
False
1142+
>>> vec2 = vec.autoreduction()
1143+
>>> vec2.is_autoreduced()
1144+
True
1145+
>>> vec2
1146+
fmpz_mpoly_vec([3*x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '<Ordering.lex: 0>', ('x', 'y')))
1147+
"""
1148+
1149+
cdef fmpz_mpoly_vec h = fmpz_mpoly_vec(0, self.ctx)
1150+
1151+
if groebner:
1152+
if not self.is_groebner():
1153+
raise RuntimeError(
1154+
"reduced Gröbner basis construction requires that `self` is a "
1155+
"Gröbner basis."
1156+
)
1157+
fmpz_mpoly_vec_autoreduction_groebner(h.val, self.val, self.ctx.val)
1158+
else:
1159+
fmpz_mpoly_vec_autoreduction(h.val, self.val, self.ctx.val)
1160+
1161+
return h
1162+
1163+
def buchberger_naive(self, limits=None):
1164+
"""
1165+
Compute the Gröbner basis of `self` using a naive implementation of
1166+
Buchberger’s algorithm.
1167+
1168+
Provide `limits` in the form of a tuple of `(ideal_len_limit, poly_len_limit,
1169+
poly_bits_limit)` to halt execution if the length of the ideal basis set exceeds
1170+
`ideal_len_limit`, the length of any polynomial exceeds `poly_len_limit`, or the
1171+
size of the coefficients of any polynomial exceeds `poly_bits_limit`.
1172+
1173+
If limits is provided return a tuple of `(result, success)`. If `success` is
1174+
False then `result` is a valid basis for `self`, but it may not be a Gröbner
1175+
basis.
1176+
1177+
NOTE: This function is exposed only for convenience, it is a naive
1178+
implementation and does not compute a reduced basis. To construct a reduced
1179+
basis use `autoreduce`.
1180+
1181+
>>> from flint import Ordering
1182+
>>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y'))
1183+
>>> x, y = ctx.gens()
1184+
>>> f = x**2 - y
1185+
>>> g = x**3*y - x
1186+
>>> vec = fmpz_mpoly_vec([f, g], ctx)
1187+
>>> vec.is_groebner()
1188+
False
1189+
>>> vec2 = vec.buchberger_naive()
1190+
>>> vec2
1191+
fmpz_mpoly_vec([x^2 - y, x^3*y - x, x*y^2 - x, y^3 - y], ctx=fmpz_mpoly_ctx(2, '<Ordering.lex: 0>', ('x', 'y')))
1192+
>>> vec.buchberger_naive(limits=(2, 2, 512))
1193+
(fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '<Ordering.lex: 0>', ('x', 'y'))), False)
1194+
>>> vec2.is_autoreduced()
1195+
False
1196+
>>> vec2.autoreduction()
1197+
fmpz_mpoly_vec([x^2 - y, y^3 - y, x*y^2 - x], ctx=fmpz_mpoly_ctx(2, '<Ordering.lex: 0>', ('x', 'y')))
1198+
"""
1199+
1200+
cdef:
1201+
fmpz_mpoly_vec g = fmpz_mpoly_vec(0, self.ctx)
1202+
slong ideal_len_limit, poly_len_limit, poly_bits_limit
1203+
1204+
if limits is not None:
1205+
ideal_len_limit, poly_len_limit, poly_bits_limit = limits
1206+
if fmpz_mpoly_buchberger_naive_with_limits(
1207+
g.val,
1208+
self.val,
1209+
ideal_len_limit,
1210+
poly_len_limit,
1211+
poly_bits_limit,
1212+
self.ctx.val
1213+
):
1214+
return g, True
1215+
else:
1216+
return g, False
1217+
else:
1218+
fmpz_mpoly_buchberger_naive(g.val, self.val, self.ctx.val)
1219+
return g

0 commit comments

Comments
 (0)