Skip to content

Commit 33f3eeb

Browse files
committed
Add drop_gens, coerce_to_context, and infer_context_mapping functions
WIp wip
1 parent 3e1c60c commit 33f3eeb

7 files changed

Lines changed: 230 additions & 36 deletions

File tree

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_elem:
45
pass
@@ -50,6 +51,7 @@ cdef class flint_mpoly(flint_elem):
5051
cdef _isub_mpoly_(self, other)
5152
cdef _imul_mpoly_(self, other)
5253

54+
cdef _compose_gens_(self, ctx, slong *mapping)
5355

5456
cdef class flint_mat(flint_elem):
5557
pass

src/flint/flint_base/flint_base.pyx

Lines changed: 148 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 names,
422434
)
423435

424436
def _any_as_scalar(self, other):
@@ -451,6 +463,47 @@ 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: 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+
remaining_gens = []
482+
for i in range(nvars):
483+
if i not in gen_idxs:
484+
remaining_gens.append(self.py_names[i])
485+
486+
return self.from_context(self, names=remaining_gens)
487+
488+
def infer_generator_mapping(self, ctx: flint_mpoly_context):
489+
"""
490+
Infer a mapping of generator indexes from this contexts generators, to the
491+
provided contexts generators. Inference is done based upon generator names.
492+
493+
>>> from flint import fmpz_mpoly_ctx
494+
>>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b'))
495+
>>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a'))
496+
>>> ctx.infer_generator_mapping(ctx2)
497+
{3: 1, 4: 0}
498+
"""
499+
gens_to_idxs = {x: i for i, x in enumerate(self.names())}
500+
other_gens_to_idxs = {x: i for i, x in enumerate(ctx.names())}
501+
return {
502+
gens_to_idxs[k]: other_gens_to_idxs[k]
503+
for k in (gens_to_idxs.keys() & other_gens_to_idxs.keys())
504+
}
505+
506+
454507
cdef class flint_mod_mpoly_context(flint_mpoly_context):
455508
@classmethod
456509
def _new_(_, flint_mod_mpoly_context self, names, prime_modulus):
@@ -472,11 +525,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context):
472525
return *super().create_context_key(names, ordering), modulus
473526

474527
@classmethod
475-
def from_context(cls, ctx: flint_mod_mpoly_context):
528+
def from_context(cls, ctx: flint_mod_mpoly_context, names=None, ordering=None, modulus=None):
529+
"""
530+
Get a new context from an existing one. Optionally override ``names``,
531+
``modulus``, or ``ordering``.
532+
"""
476533
return cls.get(
477-
names=ctx.names(),
478-
modulus=ctx.modulus(),
479-
ordering=ctx.ordering(),
534+
names=ctx.names() if names is None else names,
535+
modulus=ctx.modulus() if modulus is None else modulus,
536+
ordering=ctx.ordering() if ordering is None else ordering,
480537
)
481538

482539
def is_prime(self):
@@ -869,6 +926,88 @@ cdef class flint_mpoly(flint_elem):
869926
"""
870927
return zip(self.monoms(), self.coeffs())
871928

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

8731012
cdef class flint_series(flint_elem):
8741013
"""

src/flint/flintlib/types/flint.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ cdef extern from *:
5959
cdef extern from "flint/flint.h":
6060
"""
6161
#define SIZEOF_ULONG sizeof(ulong)
62+
#define SIZEOF_SLONG sizeof(slong)
6263
"""
6364
int SIZEOF_ULONG
65+
int SIZEOF_SLONG
6466
const char * FLINT_VERSION
6567
const int __FLINT_RELEASE
6668
const int FLINT_BITS

src/flint/types/fmpq_mpoly.pyx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ from flint.flintlib.functions.fmpq_mpoly cimport (
2020
fmpq_mpoly_add,
2121
fmpq_mpoly_add_fmpq,
2222
fmpq_mpoly_clear,
23+
fmpq_mpoly_combine_like_terms,
2324
fmpq_mpoly_compose_fmpq_mpoly,
25+
fmpq_mpoly_compose_fmpq_mpoly_gen,
2426
fmpq_mpoly_ctx_init,
2527
fmpq_mpoly_degrees_fmpz,
2628
fmpq_mpoly_derivative,
@@ -40,6 +42,7 @@ from flint.flintlib.functions.fmpq_mpoly cimport (
4042
fmpq_mpoly_get_term_coeff_fmpq,
4143
fmpq_mpoly_get_term_exp_fmpz,
4244
fmpq_mpoly_integral,
45+
fmpq_mpoly_is_canonical,
4346
fmpq_mpoly_is_one,
4447
fmpq_mpoly_is_zero,
4548
fmpq_mpoly_length,
@@ -1119,6 +1122,22 @@ cdef class fmpq_mpoly(flint_mpoly):
11191122
fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx)
11201123
return list(stride), list(shift)
11211124

1125+
cdef _compose_gens_(self, ctx, slong *mapping):
1126+
cdef fmpq_mpoly res = create_fmpq_mpoly(ctx)
1127+
fmpq_mpoly_compose_fmpq_mpoly_gen(
1128+
res.val,
1129+
self.val,
1130+
mapping,
1131+
self.ctx.val,
1132+
(<fmpq_mpoly_ctx>ctx).val
1133+
)
1134+
1135+
if not fmpq_mpoly_is_canonical(res.val, res.ctx.val):
1136+
fmpq_mpoly_sort_terms(res.val, res.ctx.val)
1137+
fmpq_mpoly_combine_like_terms(res.val, res.ctx.val)
1138+
1139+
return res
1140+
11221141

11231142
cdef class fmpq_mpoly_vec:
11241143
"""

src/flint/types/fmpz_mod_mpoly.pyx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport (
1818
fmpz_mod_mpoly_add,
1919
fmpz_mod_mpoly_add_fmpz,
2020
fmpz_mod_mpoly_clear,
21+
fmpz_mod_mpoly_combine_like_terms,
2122
fmpz_mod_mpoly_compose_fmpz_mod_mpoly,
23+
fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen,
2224
fmpz_mod_mpoly_ctx_get_modulus,
2325
fmpz_mod_mpoly_ctx_init,
2426
fmpz_mod_mpoly_deflate,
@@ -40,6 +42,7 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport (
4042
fmpz_mod_mpoly_get_term_coeff_fmpz,
4143
fmpz_mod_mpoly_get_term_exp_fmpz,
4244
fmpz_mod_mpoly_inflate,
45+
fmpz_mod_mpoly_is_canonical,
4346
fmpz_mod_mpoly_is_one,
4447
fmpz_mod_mpoly_is_zero,
4548
fmpz_mod_mpoly_length,
@@ -1136,6 +1139,22 @@ cdef class fmpz_mod_mpoly(flint_mpoly):
11361139
fmpz_mod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val)
11371140
return list(stride), list(shift)
11381141

1142+
cdef _compose_gens_(self, ctx, slong *mapping):
1143+
cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx)
1144+
fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(
1145+
res.val,
1146+
self.val,
1147+
mapping,
1148+
self.ctx.val,
1149+
(<fmpz_mod_mpoly_ctx>ctx).val
1150+
)
1151+
1152+
if not fmpz_mod_mpoly_is_canonical(res.val, res.ctx.val):
1153+
fmpz_mod_mpoly_sort_terms(res.val, res.ctx.val)
1154+
fmpz_mod_mpoly_combine_like_terms(res.val, res.ctx.val)
1155+
1156+
return res
1157+
11391158

11401159
cdef class fmpz_mod_mpoly_vec:
11411160
"""

0 commit comments

Comments
 (0)