Skip to content

Commit d76d40b

Browse files
Merge pull request #228 from Jake-Moss/main
Add some mpoly context util functions
2 parents 9fb7f2a + d1a1438 commit d76d40b

File tree

8 files changed

+280
-194
lines changed

8 files changed

+280
-194
lines changed

src/flint/flint_base/flint_base.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from flint.flintlib.types.mpoly cimport ordering_t
2+
from flint.flintlib.types.flint cimport slong
23

34
cdef class flint_ctx:
45
pass
@@ -53,6 +54,7 @@ cdef class flint_mpoly(flint_elem):
5354
cdef _isub_mpoly_(self, other)
5455
cdef _imul_mpoly_(self, other)
5556

57+
cdef _compose_gens_(self, ctx, slong *mapping)
5658

5759
cdef class flint_mat(flint_elem):
5860
pass

src/flint/flint_base/flint_base.pyx

Lines changed: 156 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ from flint.flintlib.types.flint cimport (
22
FLINT_BITS as _FLINT_BITS,
33
FLINT_VERSION as _FLINT_VERSION,
44
__FLINT_RELEASE as _FLINT_RELEASE,
5+
slong,
56
)
67
from flint.utils.flint_exceptions import DomainError
78
from flint.flintlib.types.mpoly cimport ordering_t
@@ -344,13 +345,20 @@ cdef class flint_mpoly_context(flint_elem):
344345
return tuple(self.gen(i) for i in range(self.nvars()))
345346

346347
def variable_to_index(self, var: Union[int, str]) -> int:
347-
"""Convert a variable name string or possible index to its index in the context."""
348+
"""
349+
Convert a variable name string or possible index to its index in the context.
350+
351+
If ``var`` is negative, return the index of the ``self.nvars() + var``
352+
"""
348353
if isinstance(var, str):
349354
try:
350355
i = self.names().index(var)
351356
except ValueError:
352357
raise ValueError("variable not in context")
353358
elif isinstance(var, int):
359+
if var < 0:
360+
var = self.nvars() + var
361+
354362
if not 0 <= var < self.nvars():
355363
raise IndexError("generator index out of range")
356364
i = var
@@ -379,7 +387,7 @@ cdef class flint_mpoly_context(flint_elem):
379387
names = (names,)
380388

381389
for name in names:
382-
if isinstance(name, str):
390+
if isinstance(name, (str, bytes)):
383391
res.append(name)
384392
else:
385393
base, num = name
@@ -415,10 +423,14 @@ cdef class flint_mpoly_context(flint_elem):
415423
return ctx
416424

417425
@classmethod
418-
def from_context(cls, ctx: flint_mpoly_context):
426+
def from_context(cls, ctx: flint_mpoly_context, names=None, ordering=None):
427+
"""
428+
Get a new context from an existing one. Optionally override ``names`` or
429+
``ordering``.
430+
"""
419431
return cls.get(
420-
ordering=ctx.ordering(),
421-
names=ctx.names(),
432+
names=ctx.names() if names is None else names,
433+
ordering=ctx.ordering() if ordering is None else ordering,
422434
)
423435

424436
def _any_as_scalar(self, other):
@@ -451,6 +463,62 @@ cdef class flint_mpoly_context(flint_elem):
451463
exp_vec = (0,) * self.nvars()
452464
return self.from_dict({tuple(exp_vec): coeff})
453465

466+
def drop_gens(self, gens: Iterable[str | int]):
467+
"""
468+
Get a context with the specified generators removed.
469+
470+
>>> from flint import fmpz_mpoly_ctx
471+
>>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
472+
>>> ctx.drop_gens(('x', -2))
473+
fmpz_mpoly_ctx(3, '<Ordering.lex: 'lex'>', ('y', 'z', 'b'))
474+
"""
475+
nvars = self.nvars()
476+
gen_idxs = set(self.variable_to_index(i) for i in gens)
477+
478+
if len(gens) > nvars:
479+
raise ValueError(f"expected at most {nvars} unique generators, got {len(gens)}")
480+
481+
names = self.names()
482+
remaining_gens = []
483+
for i in range(nvars):
484+
if i not in gen_idxs:
485+
remaining_gens.append(names[i])
486+
487+
return self.from_context(self, names=remaining_gens)
488+
489+
def append_gens(self, *gens: str):
490+
"""
491+
Get a context with the specified generators appended.
492+
493+
>>> from flint import fmpz_mpoly_ctx
494+
>>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z'))
495+
>>> ctx.append_gens('a', 'b')
496+
fmpz_mpoly_ctx(5, '<Ordering.lex: 'lex'>', ('x', 'y', 'z', 'a', 'b'))
497+
"""
498+
return self.from_context(self, names=self.names() + gens)
499+
500+
def infer_generator_mapping(self, ctx: flint_mpoly_context):
501+
"""
502+
Infer a mapping of generator indexes from this contexts generators, to the
503+
provided contexts generators. Inference is done based upon generator names.
504+
505+
>>> from flint import fmpz_mpoly_ctx
506+
>>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
507+
>>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a'))
508+
>>> mapping = ctx.infer_generator_mapping(ctx2)
509+
>>> mapping # doctest: +SKIP
510+
{3: 1, 4: 0}
511+
>>> list(sorted(mapping.items())) # Set ordering is not stable
512+
[(3, 1), (4, 0)]
513+
"""
514+
gens_to_idxs = {x: i for i, x in enumerate(self.names())}
515+
other_gens_to_idxs = {x: i for i, x in enumerate(ctx.names())}
516+
return {
517+
gens_to_idxs[k]: other_gens_to_idxs[k]
518+
for k in (gens_to_idxs.keys() & other_gens_to_idxs.keys())
519+
}
520+
521+
454522
cdef class flint_mod_mpoly_context(flint_mpoly_context):
455523
@classmethod
456524
def _new_(_, flint_mod_mpoly_context self, names, prime_modulus):
@@ -472,11 +540,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context):
472540
return *super().create_context_key(names, ordering), modulus
473541

474542
@classmethod
475-
def from_context(cls, ctx: flint_mod_mpoly_context):
543+
def from_context(cls, ctx: flint_mod_mpoly_context, names=None, ordering=None, modulus=None):
544+
"""
545+
Get a new context from an existing one. Optionally override ``names``,
546+
``modulus``, or ``ordering``.
547+
"""
476548
return cls.get(
477-
names=ctx.names(),
478-
modulus=ctx.modulus(),
479-
ordering=ctx.ordering(),
549+
names=ctx.names() if names is None else names,
550+
modulus=ctx.modulus() if modulus is None else modulus,
551+
ordering=ctx.ordering() if ordering is None else ordering,
480552
)
481553

482554
def is_prime(self):
@@ -869,6 +941,81 @@ cdef class flint_mpoly(flint_elem):
869941
"""
870942
return zip(self.monoms(), self.coeffs())
871943

944+
def unused_gens(self):
945+
"""
946+
Report the unused generators from this polynomial.
947+
948+
A generator is unused if it's maximum degree is 0.
949+
950+
>>> from flint import fmpz_mpoly_ctx
951+
>>> ctx = fmpz_mpoly_ctx.get(('x', 4))
952+
>>> ctx2 = fmpz_mpoly_ctx.get(('x1', 'x2'))
953+
>>> f = sum(ctx.gens()[1:3])
954+
>>> f
955+
x1 + x2
956+
>>> f.unused_gens()
957+
('x0', 'x3')
958+
"""
959+
names = self.context().names()
960+
return tuple(names[i] for i, x in enumerate(self.degrees()) if not x)
961+
962+
def project_to_context(self, other_ctx, mapping: dict[str | int, str | int] = None):
963+
"""
964+
Project this polynomial to a different context.
965+
966+
This is equivalent to composing this polynomial with the generators of another
967+
context. By default the mapping between contexts is inferred based on the name
968+
of the generators. Generators with names that are not found within the other
969+
context are mapped to 0. The mapping can be explicitly provided.
970+
971+
>>> from flint import fmpz_mpoly_ctx
972+
>>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'a', 'b'))
973+
>>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b'))
974+
>>> x, y, a, b = ctx.gens()
975+
>>> f = x + 2*y + 3*a + 4*b
976+
>>> f.project_to_context(ctx2)
977+
3*a + 4*b
978+
>>> f.project_to_context(ctx2, mapping={"x": "a", "b": 0})
979+
5*a
980+
"""
981+
cdef:
982+
slong *c_mapping
983+
slong i
984+
985+
ctx = self.context()
986+
if not typecheck(other_ctx, type(ctx)):
987+
raise ValueError(
988+
f"provided context is not a {ctx.__class__.__name__}"
989+
)
990+
elif ctx is other_ctx:
991+
return self
992+
993+
if mapping is None:
994+
mapping = ctx.infer_generator_mapping(other_ctx)
995+
else:
996+
mapping = {
997+
ctx.variable_to_index(k): other_ctx.variable_to_index(v)
998+
for k, v in mapping.items()
999+
}
1000+
1001+
try:
1002+
c_mapping = <slong *> libc.stdlib.malloc(ctx.nvars() * sizeof(slong *))
1003+
if c_mapping is NULL:
1004+
raise MemoryError("malloc returned a null pointer")
1005+
1006+
for i in range(ctx.nvars()):
1007+
c_mapping[i] = <slong>-1
1008+
1009+
for k, v in mapping.items():
1010+
c_mapping[k] = <slong>v
1011+
1012+
return self._compose_gens_(other_ctx, c_mapping)
1013+
finally:
1014+
libc.stdlib.free(c_mapping)
1015+
1016+
cdef _compose_gens_(self, other_ctx, slong *mapping):
1017+
raise NotImplementedError("abstract method")
1018+
8721019

8731020
cdef class flint_series(flint_elem):
8741021
"""

src/flint/flintlib/types/flint.pxd

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,26 @@ cdef extern from "flint/fmpz.h":
4545

4646
cdef extern from *:
4747
"""
48-
/*
49-
* Functions renamed in Flint 3.2.0
50-
*/
5148
#if __FLINT_RELEASE < 30200 /* Flint < 3.2.0 */
5249
50+
/* Functions renamed in Flint 3.2.0 */
5351
#define flint_rand_init flint_randinit
5452
#define flint_rand_clear flint_randclear
5553
5654
#endif
55+
56+
/* FIXME: add version guard when https://github.com/flintlib/flint/pull/2068 */
57+
/* is resolved */
58+
#define fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(...) (void)0
5759
"""
5860

5961
cdef extern from "flint/flint.h":
6062
"""
6163
#define SIZEOF_ULONG sizeof(ulong)
64+
#define SIZEOF_SLONG sizeof(slong)
6265
"""
6366
int SIZEOF_ULONG
67+
int SIZEOF_SLONG
6468

6569
ctypedef struct __FLINT_FILE:
6670
pass

src/flint/test/test_all.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2861,6 +2861,12 @@ def test_mpolys():
28612861

28622862
ctx = get_context(("x", 2))
28632863

2864+
def mpoly(x):
2865+
return ctx.from_dict(x)
2866+
2867+
def quick_poly():
2868+
return mpoly({(0, 0): 1, (0, 1): 2, (1, 0): 3, (2, 2): 4})
2869+
28642870
assert raises(lambda : ctx.__class__("x", flint.Ordering.lex), RuntimeError)
28652871
assert raises(lambda: get_context(("x", 2), ordering="bad"), ValueError)
28662872
assert raises(lambda: get_context(("x", -1)), ValueError)
@@ -2877,17 +2883,41 @@ def test_mpolys():
28772883
assert raises(lambda: P(val={"bad": 1}, ctx=None), ValueError)
28782884
assert raises(lambda: P(val="1", ctx=None), ValueError)
28792885

2886+
ctx1 = get_context(("x", 4))
2887+
ctx2 = get_context(("x", 4), ordering="deglex")
2888+
assert ctx1.drop_gens(ctx1.names()).names() == tuple()
2889+
assert ctx1.drop_gens((ctx1.name(1), ctx1.name(2))).names() == (ctx1.name(0), ctx1.name(3))
2890+
assert ctx1.drop_gens(tuple()).names() == ctx1.names()
2891+
assert ctx1.drop_gens((-1,)).names() == ctx1.names()[:-1]
2892+
2893+
assert ctx.infer_generator_mapping(ctx) == {i: i for i in range(ctx.nvars())}
2894+
assert ctx1.infer_generator_mapping(ctx) == {0: 0, 1: 1}
2895+
assert ctx1.drop_gens(ctx.names()).infer_generator_mapping(ctx) == {}
2896+
2897+
assert quick_poly().project_to_context(ctx1) == \
2898+
ctx1.from_dict(
2899+
{(0, 0, 0, 0): 1, (0, 1, 0, 0): 2, (1, 0, 0, 0): 3, (2, 2, 0, 0): 4}
2900+
)
2901+
new_poly = quick_poly().project_to_context(ctx1)
2902+
assert ctx1.drop_gens(new_poly.unused_gens()) == ctx
2903+
assert new_poly.project_to_context(ctx) == quick_poly()
2904+
2905+
new_poly = quick_poly().project_to_context(ctx2)
2906+
new_ctx = ctx2.drop_gens(new_poly.unused_gens())
2907+
assert new_ctx != ctx
2908+
assert new_poly != quick_poly()
2909+
2910+
new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering())
2911+
assert new_ctx == ctx
2912+
assert new_poly.project_to_context(new_ctx) == quick_poly()
2913+
2914+
assert ctx.append_gens(*ctx1.names()[-2:]) == ctx1
2915+
28802916
assert P(val={(0, 0): 1}, ctx=ctx) == ctx.from_dict({(0, 0): 1})
28812917
assert P(ctx=ctx).context() == ctx
28822918
assert P(1, ctx=ctx).is_one()
28832919
assert ctx.gen(1) == ctx.from_dict({(0, 1): 1})
28842920

2885-
def mpoly(x):
2886-
return ctx.from_dict(x)
2887-
2888-
def quick_poly():
2889-
return mpoly({(0, 0): 1, (0, 1): 2, (1, 0): 3, (2, 2): 4})
2890-
28912921
assert ctx.nvars() == 2
28922922
assert ctx.ordering() == flint.Ordering.lex
28932923

src/flint/types/fmpq_mpoly.pyx

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ from flint.flintlib.functions.fmpq_mpoly cimport (
2121
fmpq_mpoly_add_fmpq,
2222
fmpq_mpoly_clear,
2323
fmpq_mpoly_compose_fmpq_mpoly,
24+
fmpq_mpoly_compose_fmpq_mpoly_gen,
2425
fmpq_mpoly_ctx_init,
2526
fmpq_mpoly_degrees_fmpz,
2627
fmpq_mpoly_derivative,
@@ -547,28 +548,6 @@ cdef class fmpq_mpoly(flint_mpoly):
547548

548549
return res
549550

550-
# def terms(self):
551-
# """
552-
# Return the terms of this polynomial as a list of fmpq_mpolys.
553-
554-
# >>> ctx = fmpq_mpoly_ctx.get(('x', 2), 'lex')
555-
# >>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4})
556-
# >>> f.terms()
557-
# [4*x0*x1, 2*x0, 3*x1, 1]
558-
559-
# """
560-
# cdef:
561-
# fmpq_mpoly term
562-
# slong i
563-
564-
# res = []
565-
# for i in range(len(self)):
566-
# term = create_fmpq_mpoly(self.ctx)
567-
# fmpq_mpoly_get_term(term.val, self.val, i, self.ctx.val)
568-
# res.append(term)
569-
570-
# return res
571-
572551
def subs(self, dict_args) -> fmpq_mpoly:
573552
"""
574553
Partial evaluate this polynomial with select constants. Keys must be generator names or generator indices,
@@ -699,9 +678,11 @@ cdef class fmpq_mpoly(flint_mpoly):
699678
Return a dictionary of variable name to degree.
700679
701680
>>> ctx = fmpq_mpoly_ctx.get(('x', 4), 'lex')
702-
>>> p = ctx.from_dict({(1, 0, 0, 0): 1, (0, 2, 0, 0): 2, (0, 0, 3, 0): 3})
681+
>>> p = sum(x**i for i, x in enumerate(ctx.gens()))
682+
>>> p
683+
x1 + x2^2 + x3^3 + 1
703684
>>> p.degrees()
704-
(1, 2, 3, 0)
685+
(0, 1, 2, 3)
705686
"""
706687
cdef:
707688
slong nvars = self.ctx.nvars()
@@ -1119,6 +1100,18 @@ cdef class fmpq_mpoly(flint_mpoly):
11191100
fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx)
11201101
return list(stride), list(shift)
11211102

1103+
cdef _compose_gens_(self, ctx, slong *mapping):
1104+
cdef fmpq_mpoly res = create_fmpq_mpoly(ctx)
1105+
fmpq_mpoly_compose_fmpq_mpoly_gen(
1106+
res.val,
1107+
self.val,
1108+
mapping,
1109+
self.ctx.val,
1110+
(<fmpq_mpoly_ctx>ctx).val
1111+
)
1112+
1113+
return res
1114+
11221115

11231116
cdef class fmpq_mpoly_vec:
11241117
"""

0 commit comments

Comments
 (0)