From 33f3eebcf07ad6e6ac0263a24142e7873878f70d Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 16 Sep 2024 11:51:10 +1000 Subject: [PATCH 01/15] Add drop_gens, coerce_to_context, and infer_context_mapping functions WIp wip --- src/flint/flint_base/flint_base.pxd | 2 + src/flint/flint_base/flint_base.pyx | 157 ++++++++++++++++++++++++++-- src/flint/flintlib/types/flint.pxd | 2 + src/flint/types/fmpq_mpoly.pyx | 19 ++++ src/flint/types/fmpz_mod_mpoly.pyx | 19 ++++ src/flint/types/fmpz_mpoly.pyx | 48 ++++----- src/flint/types/nmod_mpoly.pyx | 19 ++++ 7 files changed, 230 insertions(+), 36 deletions(-) diff --git a/src/flint/flint_base/flint_base.pxd b/src/flint/flint_base/flint_base.pxd index daf22a96..99e50c23 100644 --- a/src/flint/flint_base/flint_base.pxd +++ b/src/flint/flint_base/flint_base.pxd @@ -1,4 +1,5 @@ from flint.flintlib.types.mpoly cimport ordering_t +from flint.flintlib.types.flint cimport slong cdef class flint_elem: pass @@ -50,6 +51,7 @@ cdef class flint_mpoly(flint_elem): cdef _isub_mpoly_(self, other) cdef _imul_mpoly_(self, other) + cdef _compose_gens_(self, ctx, slong *mapping) cdef class flint_mat(flint_elem): pass diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index cfe57fc9..68aa1c5f 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -2,6 +2,7 @@ from flint.flintlib.types.flint cimport ( FLINT_BITS as _FLINT_BITS, FLINT_VERSION as _FLINT_VERSION, __FLINT_RELEASE as _FLINT_RELEASE, + slong, ) from flint.utils.flint_exceptions import DomainError from flint.flintlib.types.mpoly cimport ordering_t @@ -344,13 +345,20 @@ cdef class flint_mpoly_context(flint_elem): return tuple(self.gen(i) for i in range(self.nvars())) def variable_to_index(self, var: Union[int, str]) -> int: - """Convert a variable name string or possible index to its index in the context.""" + """ + Convert a variable name string or possible index to its index in the context. + + If ``var`` is negative, return the index of the ``self.nvars() + var`` + """ if isinstance(var, str): try: i = self.names().index(var) except ValueError: raise ValueError("variable not in context") elif isinstance(var, int): + if var < 0: + var = self.nvars() + var + if not 0 <= var < self.nvars(): raise IndexError("generator index out of range") i = var @@ -379,7 +387,7 @@ cdef class flint_mpoly_context(flint_elem): names = (names,) for name in names: - if isinstance(name, str): + if isinstance(name, (str, bytes)): res.append(name) else: base, num = name @@ -415,10 +423,14 @@ cdef class flint_mpoly_context(flint_elem): return ctx @classmethod - def from_context(cls, ctx: flint_mpoly_context): + def from_context(cls, ctx: flint_mpoly_context, names=None, ordering=None): + """ + Get a new context from an existing one. Optionally override ``names`` or + ``ordering``. + """ return cls.get( - ordering=ctx.ordering(), - names=ctx.names(), + names=ctx.names() if names is None else names, + ordering=ctx.ordering() if ordering is None else names, ) def _any_as_scalar(self, other): @@ -451,6 +463,47 @@ cdef class flint_mpoly_context(flint_elem): exp_vec = (0,) * self.nvars() return self.from_dict({tuple(exp_vec): coeff}) + def drop_gens(self, *gens: str | int): + """ + Get a context with the specified generators removed. + + >>> from flint import fmpz_mpoly_ctx + >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b')) + >>> ctx.drop_gens('x', -2) + fmpz_mpoly_ctx(3, '', ('y', 'z', 'b')) + """ + nvars = self.nvars() + gen_idxs = set(self.variable_to_index(i) for i in gens) + + if len(gens) > nvars: + raise ValueError(f"expected at most {nvars} unique generators, got {len(gens)}") + + remaining_gens = [] + for i in range(nvars): + if i not in gen_idxs: + remaining_gens.append(self.py_names[i]) + + return self.from_context(self, names=remaining_gens) + + def infer_generator_mapping(self, ctx: flint_mpoly_context): + """ + Infer a mapping of generator indexes from this contexts generators, to the + provided contexts generators. Inference is done based upon generator names. + + >>> from flint import fmpz_mpoly_ctx + >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b')) + >>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a')) + >>> ctx.infer_generator_mapping(ctx2) + {3: 1, 4: 0} + """ + gens_to_idxs = {x: i for i, x in enumerate(self.names())} + other_gens_to_idxs = {x: i for i, x in enumerate(ctx.names())} + return { + gens_to_idxs[k]: other_gens_to_idxs[k] + for k in (gens_to_idxs.keys() & other_gens_to_idxs.keys()) + } + + cdef class flint_mod_mpoly_context(flint_mpoly_context): @classmethod def _new_(_, flint_mod_mpoly_context self, names, prime_modulus): @@ -472,11 +525,15 @@ cdef class flint_mod_mpoly_context(flint_mpoly_context): return *super().create_context_key(names, ordering), modulus @classmethod - def from_context(cls, ctx: flint_mod_mpoly_context): + def from_context(cls, ctx: flint_mod_mpoly_context, names=None, ordering=None, modulus=None): + """ + Get a new context from an existing one. Optionally override ``names``, + ``modulus``, or ``ordering``. + """ return cls.get( - names=ctx.names(), - modulus=ctx.modulus(), - ordering=ctx.ordering(), + names=ctx.names() if names is None else names, + modulus=ctx.modulus() if modulus is None else modulus, + ordering=ctx.ordering() if ordering is None else ordering, ) def is_prime(self): @@ -869,6 +926,88 @@ cdef class flint_mpoly(flint_elem): """ return zip(self.monoms(), self.coeffs()) + def drop_unused_gens(self): + """ + Remove unused generators from this polynomial. Returns a potentially new + context, and potentially new polynomial. + + A generator is unused if it's maximum degree is 0. + + >>> from flint import fmpz_mpoly_ctx + >>> ctx = fmpz_mpoly_ctx.get(('x', 4)) + >>> ctx2 = fmpz_mpoly_ctx.get(('x1', 'x2')) + >>> f = sum(ctx.gens()[1:3]) + >>> f + x1 + x2 + >>> new_ctx, new_f = f.drop_unused_gens() + >>> new_ctx + fmpz_mpoly_ctx(2, '', ('x1', 'x2')) + >>> new_f + x1 + x2 + """ + new_ctx = self.context().drop_gens( + *(i for i, x in enumerate(self.degrees()) if not x) + ) + return new_ctx, self.coerce_to_context(new_ctx) + + def coerce_to_context(self, ctx, mapping: dict[str | int, str | int] = None): + """ + Coerce this polynomial to a different context. + + This is equivalent to composing this polynomial with the generators of another + context. By default the mapping between contexts is inferred based on the name + of the generators. Generators with names that are not found within the other + context are mapped to 0. The mapping can be explicitly provided. + + The resulting polynomial is also normalised. + + >>> from flint import fmpz_mpoly_ctx + >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'a', 'b')) + >>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b')) + >>> x, y, a, b = ctx.gens() + >>> f = x + 2*y + 3*a + 4*b + >>> f.coerce_to_context(ctx2) + 3*a + 4*b + >>> f.coerce_to_context(ctx2, mapping={"x": "a", "b": 0}) + 5*a + """ + cdef: + slong *c_mapping + slong i + + if not typecheck(ctx, type(self.ctx)): + raise ValueError( + f"provided context is not a {self.ctx.__class__.__name__}" + ) + elif self.ctx is ctx: + return self + + if mapping is None: + mapping = self.ctx.infer_generator_mapping(ctx) + else: + mapping = { + self.ctx.variable_to_index(k): ctx.variable_to_index(v) + for k, v in mapping.items() + } + + try: + c_mapping = libc.stdlib.malloc(self.ctx.nvars() * sizeof(slong *)) + if c_mapping is NULL: + raise MemoryError("malloc returned a null pointer") + + for i in range(self.ctx.nvars()): + c_mapping[i] = -1 + + for k, v in mapping.items(): + c_mapping[k] = v + + return self._compose_gens_(ctx, c_mapping) + finally: + libc.stdlib.free(c_mapping) + + cdef _compose_gens_(self, ctx, slong *mapping): + raise NotImplementedError("abstract method") + cdef class flint_series(flint_elem): """ diff --git a/src/flint/flintlib/types/flint.pxd b/src/flint/flintlib/types/flint.pxd index 8db56127..120bf1da 100644 --- a/src/flint/flintlib/types/flint.pxd +++ b/src/flint/flintlib/types/flint.pxd @@ -59,8 +59,10 @@ cdef extern from *: cdef extern from "flint/flint.h": """ #define SIZEOF_ULONG sizeof(ulong) + #define SIZEOF_SLONG sizeof(slong) """ int SIZEOF_ULONG + int SIZEOF_SLONG const char * FLINT_VERSION const int __FLINT_RELEASE const int FLINT_BITS diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index 0e86a3c8..80ec0009 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -20,7 +20,9 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_add, fmpq_mpoly_add_fmpq, fmpq_mpoly_clear, + fmpq_mpoly_combine_like_terms, fmpq_mpoly_compose_fmpq_mpoly, + fmpq_mpoly_compose_fmpq_mpoly_gen, fmpq_mpoly_ctx_init, fmpq_mpoly_degrees_fmpz, fmpq_mpoly_derivative, @@ -40,6 +42,7 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_get_term_coeff_fmpq, fmpq_mpoly_get_term_exp_fmpz, fmpq_mpoly_integral, + fmpq_mpoly_is_canonical, fmpq_mpoly_is_one, fmpq_mpoly_is_zero, fmpq_mpoly_length, @@ -1119,6 +1122,22 @@ cdef class fmpq_mpoly(flint_mpoly): fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx) return list(stride), list(shift) + cdef _compose_gens_(self, ctx, slong *mapping): + cdef fmpq_mpoly res = create_fmpq_mpoly(ctx) + fmpq_mpoly_compose_fmpq_mpoly_gen( + res.val, + self.val, + mapping, + self.ctx.val, + (ctx).val + ) + + if not fmpq_mpoly_is_canonical(res.val, res.ctx.val): + fmpq_mpoly_sort_terms(res.val, res.ctx.val) + fmpq_mpoly_combine_like_terms(res.val, res.ctx.val) + + return res + cdef class fmpq_mpoly_vec: """ diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index de20cad9..13effe20 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -18,7 +18,9 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_add, fmpz_mod_mpoly_add_fmpz, fmpz_mod_mpoly_clear, + fmpz_mod_mpoly_combine_like_terms, fmpz_mod_mpoly_compose_fmpz_mod_mpoly, + fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen, fmpz_mod_mpoly_ctx_get_modulus, fmpz_mod_mpoly_ctx_init, fmpz_mod_mpoly_deflate, @@ -40,6 +42,7 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_get_term_coeff_fmpz, fmpz_mod_mpoly_get_term_exp_fmpz, fmpz_mod_mpoly_inflate, + fmpz_mod_mpoly_is_canonical, fmpz_mod_mpoly_is_one, fmpz_mod_mpoly_is_zero, fmpz_mod_mpoly_length, @@ -1136,6 +1139,22 @@ cdef class fmpz_mod_mpoly(flint_mpoly): fmpz_mod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) return list(stride), list(shift) + cdef _compose_gens_(self, ctx, slong *mapping): + cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx) + fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen( + res.val, + self.val, + mapping, + self.ctx.val, + (ctx).val + ) + + if not fmpz_mod_mpoly_is_canonical(res.val, res.ctx.val): + fmpz_mod_mpoly_sort_terms(res.val, res.ctx.val) + fmpz_mod_mpoly_combine_like_terms(res.val, res.ctx.val) + + return res + cdef class fmpz_mod_mpoly_vec: """ diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index e5cb1962..d2bc70d6 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -21,7 +21,9 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_buchberger_naive, fmpz_mpoly_buchberger_naive_with_limits, fmpz_mpoly_clear, + fmpz_mpoly_combine_like_terms, fmpz_mpoly_compose_fmpz_mpoly, + fmpz_mpoly_compose_fmpz_mpoly_gen, fmpz_mpoly_ctx_init, fmpz_mpoly_deflate, fmpz_mpoly_deflation, @@ -43,6 +45,7 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_get_term_exp_fmpz, fmpz_mpoly_inflate, fmpz_mpoly_integral, + fmpz_mpoly_is_canonical, fmpz_mpoly_is_one, fmpz_mpoly_is_zero, fmpz_mpoly_length, @@ -686,12 +689,14 @@ cdef class fmpz_mpoly(flint_mpoly): def degrees(self): """ - Return a dictionary of variable name to degree. + Return a tuple of degrees. >>> ctx = fmpz_mpoly_ctx.get(('x', 4), 'lex') - >>> p = ctx.from_dict({(1, 0, 0, 0): 1, (0, 2, 0, 0): 2, (0, 0, 3, 0): 3}) + >>> p = sum(x**(2 * i) for i, x in enumerate(ctx.gens())) + >>> p + x1 + x2^2 + x3^3 + 1 >>> p.degrees() - (1, 2, 3, 0) + (0, 1, 2, 3) """ cdef: slong nvars = self.ctx.nvars() @@ -946,32 +951,21 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_factor_clear(fac, self.ctx.val) return c, res - # TODO: Rethink context conversions, particularly the proposed methods in #132 - # def coerce_to_context(self, ctx): - # cdef: - # fmpz_mpoly res - # slong *C - # slong i - - # if not typecheck(ctx, fmpz_mpoly_ctx): - # raise ValueError("provided context is not a fmpz_mpoly_ctx") - - # if self.ctx is ctx: - # return self - - # C = libc.stdlib.malloc(self.ctx.val.minfo.nvars * sizeof(slong *)) - # if C is NULL: - # raise MemoryError("malloc returned a null pointer") - # res = create_fmpz_mpoly(self.ctx) - - # vars = {x: i for i, x in enumerate(ctx.py_names)} - # for i, var in enumerate(self.ctx.py_names): - # C[i] = vars[var] + cdef _compose_gens_(self, ctx, slong *mapping): + cdef fmpz_mpoly res = create_fmpz_mpoly(ctx) + fmpz_mpoly_compose_fmpz_mpoly_gen( + res.val, + self.val, + mapping, + self.ctx.val, + (ctx).val + ) - # fmpz_mpoly_compose_fmpz_mpoly_gen(res.val, self.val, C, self.ctx.val, (ctx).val) + if not fmpz_mpoly_is_canonical(res.val, res.ctx.val): + fmpz_mpoly_sort_terms(res.val, res.ctx.val) + fmpz_mpoly_combine_like_terms(res.val, res.ctx.val) - # libc.stdlib.free(C) - # return res + return res def derivative(self, var): """ diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index d9de8791..f9b3c98e 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -21,6 +21,8 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_add, nmod_mpoly_add_ui, nmod_mpoly_clear, + nmod_mpoly_combine_like_terms, + nmod_mpoly_compose_nmod_mpoly_gen, nmod_mpoly_compose_nmod_mpoly, nmod_mpoly_ctx_init, nmod_mpoly_ctx_modulus, @@ -40,6 +42,7 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_get_str_pretty, nmod_mpoly_get_term_coeff_ui, nmod_mpoly_get_term_exp_fmpz, + nmod_mpoly_is_canonical, nmod_mpoly_is_one, nmod_mpoly_is_zero, nmod_mpoly_length, @@ -1114,6 +1117,22 @@ cdef class nmod_mpoly(flint_mpoly): nmod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) return list(stride), list(shift) + cdef _compose_gens_(self, ctx, slong *mapping): + cdef nmod_mpoly res = create_nmod_mpoly(ctx) + nmod_mpoly_compose_nmod_mpoly_gen( + res.val, + self.val, + mapping, + self.ctx.val, + (ctx).val + ) + + if not nmod_mpoly_is_canonical(res.val, res.ctx.val): + nmod_mpoly_sort_terms(res.val, res.ctx.val) + nmod_mpoly_combine_like_terms(res.val, res.ctx.val) + + return res + cdef class nmod_mpoly_vec: """ From 446503613dff1d365ba077f7a7ff83ab57681dc0 Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Tue, 17 Sep 2024 00:28:35 +1000 Subject: [PATCH 02/15] Add flint version guard to fmpz_mod_mpoly._compose_gen --- src/flint/flint_base/flint_base.pyx | 19 ++++++++++--------- src/flint/flintlib/types/flint.pxd | 7 ++++--- src/flint/types/fmpz_mod_mpoly.pyx | 7 +++++++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 68aa1c5f..bb5bfb12 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -950,7 +950,7 @@ cdef class flint_mpoly(flint_elem): ) return new_ctx, self.coerce_to_context(new_ctx) - def coerce_to_context(self, ctx, mapping: dict[str | int, str | int] = None): + def coerce_to_context(self, other_ctx, mapping: dict[str | int, str | int] = None): """ Coerce this polynomial to a different context. @@ -975,23 +975,24 @@ cdef class flint_mpoly(flint_elem): slong *c_mapping slong i - if not typecheck(ctx, type(self.ctx)): + ctx = self.context() + if not typecheck(other_ctx, type(ctx)): raise ValueError( - f"provided context is not a {self.ctx.__class__.__name__}" + f"provided context is not a {ctx.__class__.__name__}" ) - elif self.ctx is ctx: + elif ctx is other_ctx: return self if mapping is None: - mapping = self.ctx.infer_generator_mapping(ctx) + mapping = ctx.infer_generator_mapping(other_ctx) else: mapping = { - self.ctx.variable_to_index(k): ctx.variable_to_index(v) + ctx.variable_to_index(k): ctx.variable_to_index(v) for k, v in mapping.items() } try: - c_mapping = libc.stdlib.malloc(self.ctx.nvars() * sizeof(slong *)) + c_mapping = libc.stdlib.malloc(ctx.nvars() * sizeof(slong *)) if c_mapping is NULL: raise MemoryError("malloc returned a null pointer") @@ -1001,11 +1002,11 @@ cdef class flint_mpoly(flint_elem): for k, v in mapping.items(): c_mapping[k] = v - return self._compose_gens_(ctx, c_mapping) + return self._compose_gens_(other_ctx, c_mapping) finally: libc.stdlib.free(c_mapping) - cdef _compose_gens_(self, ctx, slong *mapping): + cdef _compose_gens_(self, other_ctx, slong *mapping): raise NotImplementedError("abstract method") diff --git a/src/flint/flintlib/types/flint.pxd b/src/flint/flintlib/types/flint.pxd index 120bf1da..feb1a848 100644 --- a/src/flint/flintlib/types/flint.pxd +++ b/src/flint/flintlib/types/flint.pxd @@ -45,14 +45,15 @@ cdef extern from "flint/fmpz.h": cdef extern from *: """ - /* - * Functions renamed in Flint 3.2.0 - */ #if __FLINT_RELEASE < 30200 /* Flint < 3.2.0 */ + /* Functions renamed in Flint 3.2.0 */ #define flint_rand_init flint_randinit #define flint_rand_clear flint_randclear + /* Functions added in Flint 3.2.0 */ + #define fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(...) (void)0 + #endif """ diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index 13effe20..85b05cb5 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -4,6 +4,7 @@ from flint.flint_base.flint_base cimport ( ordering_py_to_c, ordering_c_to_py, ) +from flint.flint_base.flint_base import FLINT_RELEASE, FLINT_VERSION from flint.utils.typecheck cimport typecheck from flint.utils.flint_exceptions import DomainError, IncompatibleContextError @@ -1140,6 +1141,12 @@ cdef class fmpz_mod_mpoly(flint_mpoly): return list(stride), list(shift) cdef _compose_gens_(self, ctx, slong *mapping): + if FLINT_RELEASE < 30200: + raise NotImplementedError( + "this function is not supported below FLINT 3.2.0, " + f"current version is {FLINT_VERSION}" + ) + cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx) fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen( res.val, From 8119b563d41b18f2897f3cc7513f63de2fd46aad Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 14:48:57 +1000 Subject: [PATCH 03/15] Doc fixes and typos in `coerce_to_context` --- src/flint/flint_base/flint_base.pyx | 4 ++-- src/flint/types/fmpq_mpoly.pyx | 6 ++++-- src/flint/types/fmpz_mod_mpoly.pyx | 6 ++++-- src/flint/types/fmpz_mpoly.pyx | 6 +----- src/flint/types/nmod_mpoly.pyx | 6 ++++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index bb5bfb12..1a84d592 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -987,7 +987,7 @@ cdef class flint_mpoly(flint_elem): mapping = ctx.infer_generator_mapping(other_ctx) else: mapping = { - ctx.variable_to_index(k): ctx.variable_to_index(v) + ctx.variable_to_index(k): other_ctx.variable_to_index(v) for k, v in mapping.items() } @@ -996,7 +996,7 @@ cdef class flint_mpoly(flint_elem): if c_mapping is NULL: raise MemoryError("malloc returned a null pointer") - for i in range(self.ctx.nvars()): + for i in range(ctx.nvars()): c_mapping[i] = -1 for k, v in mapping.items(): diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index 80ec0009..9431e1ad 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -702,9 +702,11 @@ cdef class fmpq_mpoly(flint_mpoly): Return a dictionary of variable name to degree. >>> ctx = fmpq_mpoly_ctx.get(('x', 4), 'lex') - >>> p = ctx.from_dict({(1, 0, 0, 0): 1, (0, 2, 0, 0): 2, (0, 0, 3, 0): 3}) + >>> p = sum(x**i for i, x in enumerate(ctx.gens())) + >>> p + x1 + x2^2 + x3^3 + 1 >>> p.degrees() - (1, 2, 3, 0) + (0, 1, 2, 3) """ cdef: slong nvars = self.ctx.nvars() diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index 85b05cb5..ac21611e 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -707,9 +707,11 @@ cdef class fmpz_mod_mpoly(flint_mpoly): Return a dictionary of variable name to degree. >>> ctx = fmpz_mod_mpoly_ctx.get(('x', 4), 11, 'lex') - >>> p = ctx.from_dict({(1, 0, 0, 0): 1, (0, 2, 0, 0): 2, (0, 0, 3, 0): 3}) + >>> p = sum(x**i for i, x in enumerate(ctx.gens())) + >>> p + x1 + x2^2 + x3^3 + 1 >>> p.degrees() - (1, 2, 3, 0) + (0, 1, 2, 3) """ cdef: slong nvars = self.ctx.nvars() diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index d2bc70d6..770a470d 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -692,7 +692,7 @@ cdef class fmpz_mpoly(flint_mpoly): Return a tuple of degrees. >>> ctx = fmpz_mpoly_ctx.get(('x', 4), 'lex') - >>> p = sum(x**(2 * i) for i, x in enumerate(ctx.gens())) + >>> p = sum(x**i for i, x in enumerate(ctx.gens())) >>> p x1 + x2^2 + x3^3 + 1 >>> p.degrees() @@ -961,10 +961,6 @@ cdef class fmpz_mpoly(flint_mpoly): (ctx).val ) - if not fmpz_mpoly_is_canonical(res.val, res.ctx.val): - fmpz_mpoly_sort_terms(res.val, res.ctx.val) - fmpz_mpoly_combine_like_terms(res.val, res.ctx.val) - return res def derivative(self, var): diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index f9b3c98e..849b6cc3 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -687,9 +687,11 @@ cdef class nmod_mpoly(flint_mpoly): Return a dictionary of variable name to degree. >>> ctx = nmod_mpoly_ctx.get(('x', 4), 11, 'lex') - >>> p = ctx.from_dict({(1, 0, 0, 0): 1, (0, 2, 0, 0): 2, (0, 0, 3, 0): 3}) + >>> p = sum(x**i for i, x in enumerate(ctx.gens())) + >>> p + x1 + x2^2 + x3^3 + 1 >>> p.degrees() - (1, 2, 3, 0) + (0, 1, 2, 3) """ cdef: slong nvars = self.ctx.nvars() From d4a0a745e6020dce277972fecd3f57410f01a7ad Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 15:00:00 +1000 Subject: [PATCH 04/15] Flint does the normalisation itself, not sure why it didn't earlier --- src/flint/flint_base/flint_base.pyx | 2 -- src/flint/types/fmpq_mpoly.pyx | 6 ------ src/flint/types/fmpz_mod_mpoly.pyx | 6 ------ src/flint/types/fmpz_mpoly.pyx | 2 -- src/flint/types/nmod_mpoly.pyx | 6 ------ 5 files changed, 22 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 1a84d592..82e2808d 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -959,8 +959,6 @@ cdef class flint_mpoly(flint_elem): of the generators. Generators with names that are not found within the other context are mapped to 0. The mapping can be explicitly provided. - The resulting polynomial is also normalised. - >>> from flint import fmpz_mpoly_ctx >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'a', 'b')) >>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b')) diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index 9431e1ad..ced7c625 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -20,7 +20,6 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_add, fmpq_mpoly_add_fmpq, fmpq_mpoly_clear, - fmpq_mpoly_combine_like_terms, fmpq_mpoly_compose_fmpq_mpoly, fmpq_mpoly_compose_fmpq_mpoly_gen, fmpq_mpoly_ctx_init, @@ -42,7 +41,6 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_get_term_coeff_fmpq, fmpq_mpoly_get_term_exp_fmpz, fmpq_mpoly_integral, - fmpq_mpoly_is_canonical, fmpq_mpoly_is_one, fmpq_mpoly_is_zero, fmpq_mpoly_length, @@ -1134,10 +1132,6 @@ cdef class fmpq_mpoly(flint_mpoly): (ctx).val ) - if not fmpq_mpoly_is_canonical(res.val, res.ctx.val): - fmpq_mpoly_sort_terms(res.val, res.ctx.val) - fmpq_mpoly_combine_like_terms(res.val, res.ctx.val) - return res diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index ac21611e..b824b2dc 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -19,7 +19,6 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_add, fmpz_mod_mpoly_add_fmpz, fmpz_mod_mpoly_clear, - fmpz_mod_mpoly_combine_like_terms, fmpz_mod_mpoly_compose_fmpz_mod_mpoly, fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen, fmpz_mod_mpoly_ctx_get_modulus, @@ -43,7 +42,6 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_get_term_coeff_fmpz, fmpz_mod_mpoly_get_term_exp_fmpz, fmpz_mod_mpoly_inflate, - fmpz_mod_mpoly_is_canonical, fmpz_mod_mpoly_is_one, fmpz_mod_mpoly_is_zero, fmpz_mod_mpoly_length, @@ -1158,10 +1156,6 @@ cdef class fmpz_mod_mpoly(flint_mpoly): (ctx).val ) - if not fmpz_mod_mpoly_is_canonical(res.val, res.ctx.val): - fmpz_mod_mpoly_sort_terms(res.val, res.ctx.val) - fmpz_mod_mpoly_combine_like_terms(res.val, res.ctx.val) - return res diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index 770a470d..512258d5 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -21,7 +21,6 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_buchberger_naive, fmpz_mpoly_buchberger_naive_with_limits, fmpz_mpoly_clear, - fmpz_mpoly_combine_like_terms, fmpz_mpoly_compose_fmpz_mpoly, fmpz_mpoly_compose_fmpz_mpoly_gen, fmpz_mpoly_ctx_init, @@ -45,7 +44,6 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_get_term_exp_fmpz, fmpz_mpoly_inflate, fmpz_mpoly_integral, - fmpz_mpoly_is_canonical, fmpz_mpoly_is_one, fmpz_mpoly_is_zero, fmpz_mpoly_length, diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index 849b6cc3..2506c46a 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -21,7 +21,6 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_add, nmod_mpoly_add_ui, nmod_mpoly_clear, - nmod_mpoly_combine_like_terms, nmod_mpoly_compose_nmod_mpoly_gen, nmod_mpoly_compose_nmod_mpoly, nmod_mpoly_ctx_init, @@ -42,7 +41,6 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_get_str_pretty, nmod_mpoly_get_term_coeff_ui, nmod_mpoly_get_term_exp_fmpz, - nmod_mpoly_is_canonical, nmod_mpoly_is_one, nmod_mpoly_is_zero, nmod_mpoly_length, @@ -1129,10 +1127,6 @@ cdef class nmod_mpoly(flint_mpoly): (ctx).val ) - if not nmod_mpoly_is_canonical(res.val, res.ctx.val): - nmod_mpoly_sort_terms(res.val, res.ctx.val) - nmod_mpoly_combine_like_terms(res.val, res.ctx.val) - return res From 9d572f1f0d4949d15c0a564586aaa9f72fadc99c Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 16:09:56 +1000 Subject: [PATCH 05/15] Just disable the method all the time. Set ordering is not stable --- src/flint/flint_base/flint_base.pyx | 7 +++++-- src/flint/flintlib/types/flint.pxd | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 82e2808d..e8708c6a 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -430,7 +430,7 @@ cdef class flint_mpoly_context(flint_elem): """ return cls.get( names=ctx.names() if names is None else names, - ordering=ctx.ordering() if ordering is None else names, + ordering=ctx.ordering() if ordering is None else ordering, ) def _any_as_scalar(self, other): @@ -493,8 +493,11 @@ cdef class flint_mpoly_context(flint_elem): >>> from flint import fmpz_mpoly_ctx >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b')) >>> ctx2 = fmpz_mpoly_ctx.get(('b', 'a')) - >>> ctx.infer_generator_mapping(ctx2) + >>> mapping = ctx.infer_generator_mapping(ctx2) + >>> mapping # doctest: +SKIP {3: 1, 4: 0} + >>> list(sorted(mapping.items())) # Set ordering is not stable + [(3, 1), (4, 0)] """ gens_to_idxs = {x: i for i, x in enumerate(self.names())} other_gens_to_idxs = {x: i for i, x in enumerate(ctx.names())} diff --git a/src/flint/flintlib/types/flint.pxd b/src/flint/flintlib/types/flint.pxd index 83672f00..2a79bff0 100644 --- a/src/flint/flintlib/types/flint.pxd +++ b/src/flint/flintlib/types/flint.pxd @@ -51,10 +51,11 @@ cdef extern from *: #define flint_rand_init flint_randinit #define flint_rand_clear flint_randclear - /* Functions added in Flint 3.2.0 */ - #define fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(...) (void)0 - #endif + + /* FIXME: add version guard when https://github.com/flintlib/flint/pull/2068 */ + /* is resolved */ + #define fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(...) (void)0 """ cdef extern from "flint/flint.h": From 4ea2ba867a7cb3604d621e57eca1718f276510b0 Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 16:56:32 +1000 Subject: [PATCH 06/15] Add new tests, use `.names()` over `.py_names` directly --- src/flint/flint_base/flint_base.pyx | 3 ++- src/flint/test/test_all.py | 42 ++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index e8708c6a..80d53337 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -478,10 +478,11 @@ cdef class flint_mpoly_context(flint_elem): if len(gens) > nvars: raise ValueError(f"expected at most {nvars} unique generators, got {len(gens)}") + names = self.names() remaining_gens = [] for i in range(nvars): if i not in gen_idxs: - remaining_gens.append(self.py_names[i]) + remaining_gens.append(names[i]) return self.from_context(self, names=remaining_gens) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 3d64e712..2dba5ed3 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2861,6 +2861,12 @@ def test_mpolys(): ctx = get_context(("x", 2)) + def mpoly(x): + return ctx.from_dict(x) + + def quick_poly(): + return mpoly({(0, 0): 1, (0, 1): 2, (1, 0): 3, (2, 2): 4}) + assert raises(lambda : ctx.__class__("x", flint.Ordering.lex), RuntimeError) assert raises(lambda: get_context(("x", 2), ordering="bad"), ValueError) assert raises(lambda: get_context(("x", -1)), ValueError) @@ -2877,17 +2883,41 @@ def test_mpolys(): assert raises(lambda: P(val={"bad": 1}, ctx=None), ValueError) assert raises(lambda: P(val="1", ctx=None), ValueError) + ctx1 = get_context(("x", 4)) + ctx2 = get_context(("x", 4), ordering="deglex") + assert ctx1.drop_gens(*ctx1.names()).names() == tuple() + assert ctx1.drop_gens(ctx1.name(1), ctx1.name(2)).names() == (ctx1.name(0), ctx1.name(3)) + assert ctx1.drop_gens().names() == ctx1.names() + assert ctx1.drop_gens(-1).names() == ctx1.names()[:-1] + + assert ctx.infer_generator_mapping(ctx) == {i: i for i in range(ctx.nvars())} + assert ctx1.infer_generator_mapping(ctx) == {0: 0, 1: 1} + assert ctx1.drop_gens(*ctx.names()).infer_generator_mapping(ctx) == {} + + # FIXME: Remove this guard when https://github.com/flintlib/flint/pull/2068 is + # resolved + if P is not flint.fmpz_mod_mpoly: + assert quick_poly().coerce_to_context(ctx1) == \ + ctx1.from_dict( + {(0, 0, 0, 0): 1, (0, 1, 0, 0): 2, (1, 0, 0, 0): 3, (2, 2, 0, 0): 4} + ) + assert quick_poly().coerce_to_context(ctx1).drop_unused_gens() == (ctx, quick_poly()) + + new_ctx, new_poly = quick_poly().coerce_to_context(ctx2).drop_unused_gens() + assert new_ctx != ctx + assert new_poly != quick_poly() + + new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering()) + assert new_ctx == ctx + assert new_poly.coerce_to_context(new_ctx) == quick_poly() + else: + assert raises(lambda: quick_poly().coerce_to_context(ctx1), NotImplementedError) + assert P(val={(0, 0): 1}, ctx=ctx) == ctx.from_dict({(0, 0): 1}) assert P(ctx=ctx).context() == ctx assert P(1, ctx=ctx).is_one() assert ctx.gen(1) == ctx.from_dict({(0, 1): 1}) - def mpoly(x): - return ctx.from_dict(x) - - def quick_poly(): - return mpoly({(0, 0): 1, (0, 1): 2, (1, 0): 3, (2, 2): 4}) - assert ctx.nvars() == 2 assert ctx.ordering() == flint.Ordering.lex From aa2901ab1c43fc38f2871bb7eff40477e69e0b2c Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 17:01:54 +1000 Subject: [PATCH 07/15] Add `append_gens` method --- src/flint/flint_base/flint_base.pyx | 11 +++++++++++ src/flint/test/test_all.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 80d53337..89898c89 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -486,6 +486,17 @@ cdef class flint_mpoly_context(flint_elem): return self.from_context(self, names=remaining_gens) + def append_gens(self, *gens: str): + """ + Get a context with the specified generators appended. + + >>> from flint import fmpz_mpoly_ctx + >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z')) + >>> ctx.append_gens('a', 'b') + fmpz_mpoly_ctx(5, '', ('x', 'y', 'z', 'a', 'b')) + """ + return self.from_context(self, names=self.names() + gens) + def infer_generator_mapping(self, ctx: flint_mpoly_context): """ Infer a mapping of generator indexes from this contexts generators, to the diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 2dba5ed3..9c3241b4 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2910,6 +2910,8 @@ def quick_poly(): new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering()) assert new_ctx == ctx assert new_poly.coerce_to_context(new_ctx) == quick_poly() + + assert ctx.append_gens(*ctx1.names()[-2:]) == ctx1 else: assert raises(lambda: quick_poly().coerce_to_context(ctx1), NotImplementedError) From e8cdeaf991eea3bc521e4dc3ac749fddc90fc927 Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 17:11:53 +1000 Subject: [PATCH 08/15] Unconditionally raise `NotImplementedError` --- src/flint/types/fmpz_mod_mpoly.pyx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index b824b2dc..e57f43a2 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -1141,11 +1141,10 @@ cdef class fmpz_mod_mpoly(flint_mpoly): return list(stride), list(shift) cdef _compose_gens_(self, ctx, slong *mapping): - if FLINT_RELEASE < 30200: - raise NotImplementedError( - "this function is not supported below FLINT 3.2.0, " - f"current version is {FLINT_VERSION}" - ) + raise NotImplementedError( + "this function is not supported below FLINT 3.2.0, " + f"current version is {FLINT_VERSION}" + ) cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx) fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen( From 93de55af9f413f8ec9e5282b42329e67271309bb Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Tue, 17 Sep 2024 17:15:39 +1000 Subject: [PATCH 09/15] Linting --- src/flint/types/fmpz_mod_mpoly.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index e57f43a2..cd3eadfb 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -4,7 +4,7 @@ from flint.flint_base.flint_base cimport ( ordering_py_to_c, ordering_c_to_py, ) -from flint.flint_base.flint_base import FLINT_RELEASE, FLINT_VERSION +from flint.flint_base.flint_base import FLINT_VERSION from flint.utils.typecheck cimport typecheck from flint.utils.flint_exceptions import DomainError, IncompatibleContextError From ce1a966bf1075114bd30b77b4d639eb0e98f115f Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 00:31:18 +1000 Subject: [PATCH 10/15] Rename `coerce_to_context` -> `project_to_context` --- src/flint/flint_base/flint_base.pyx | 8 ++++---- src/flint/types/fmpz_mod_mpoly.pyx | 2 +- src/flint/types/nmod_mpoly.pyx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 89898c89..3c82faac 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -965,9 +965,9 @@ cdef class flint_mpoly(flint_elem): ) return new_ctx, self.coerce_to_context(new_ctx) - def coerce_to_context(self, other_ctx, mapping: dict[str | int, str | int] = None): + def project_to_context(self, other_ctx, mapping: dict[str | int, str | int] = None): """ - Coerce this polynomial to a different context. + Project this polynomial to a different context. This is equivalent to composing this polynomial with the generators of another context. By default the mapping between contexts is inferred based on the name @@ -979,9 +979,9 @@ cdef class flint_mpoly(flint_elem): >>> ctx2 = fmpz_mpoly_ctx.get(('a', 'b')) >>> x, y, a, b = ctx.gens() >>> f = x + 2*y + 3*a + 4*b - >>> f.coerce_to_context(ctx2) + >>> f.project_to_context(ctx2) 3*a + 4*b - >>> f.coerce_to_context(ctx2, mapping={"x": "a", "b": 0}) + >>> f.project_to_context(ctx2, mapping={"x": "a", "b": 0}) 5*a """ cdef: diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index cd3eadfb..c68002d6 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -949,7 +949,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): return c, res # TODO: Rethink context conversions, particularly the proposed methods in #132 - # def coerce_to_context(self, ctx): + # def project_to_context(self, ctx): # cdef: # fmpz_mod_mpoly res # slong *C diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index 2506c46a..43131cf0 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -927,7 +927,7 @@ cdef class nmod_mpoly(flint_mpoly): return constant, res # TODO: Rethink context conversions, particularly the proposed methods in #132 - # def coerce_to_context(self, ctx): + # def project_to_context(self, ctx): # cdef: # nmod_mpoly res # slong *C From 324f6aed9d06e001a7e01b78de3322361da4c1fa Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 00:34:43 +1000 Subject: [PATCH 11/15] Require a single arg instead of *args to make composing nicer --- src/flint/flint_base/flint_base.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 3c82faac..8e24c51d 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -463,13 +463,13 @@ cdef class flint_mpoly_context(flint_elem): exp_vec = (0,) * self.nvars() return self.from_dict({tuple(exp_vec): coeff}) - def drop_gens(self, *gens: str | int): + def drop_gens(self, gens: Iterable[str | int]): """ Get a context with the specified generators removed. >>> from flint import fmpz_mpoly_ctx >>> ctx = fmpz_mpoly_ctx.get(('x', 'y', 'z', 'a', 'b')) - >>> ctx.drop_gens('x', -2) + >>> ctx.drop_gens(('x', -2)) fmpz_mpoly_ctx(3, '', ('y', 'z', 'b')) """ nvars = self.nvars() From 737d22cfde4a3994834cad596b182ff647254705 Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 00:35:11 +1000 Subject: [PATCH 12/15] Replace `drop_unused_gens` with `unused_gens` --- src/flint/flint_base/flint_base.pyx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 8e24c51d..16564b12 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -941,10 +941,9 @@ cdef class flint_mpoly(flint_elem): """ return zip(self.monoms(), self.coeffs()) - def drop_unused_gens(self): + def unused_gens(self): """ - Remove unused generators from this polynomial. Returns a potentially new - context, and potentially new polynomial. + Report the unused generators from this polynomial. A generator is unused if it's maximum degree is 0. @@ -954,16 +953,11 @@ cdef class flint_mpoly(flint_elem): >>> f = sum(ctx.gens()[1:3]) >>> f x1 + x2 - >>> new_ctx, new_f = f.drop_unused_gens() - >>> new_ctx - fmpz_mpoly_ctx(2, '', ('x1', 'x2')) - >>> new_f - x1 + x2 + >>> f.unused_gens() + ('x0', 'x3') """ - new_ctx = self.context().drop_gens( - *(i for i, x in enumerate(self.degrees()) if not x) - ) - return new_ctx, self.coerce_to_context(new_ctx) + names = self.context().names() + return tuple(names[i] for i, x in enumerate(self.degrees()) if not x) def project_to_context(self, other_ctx, mapping: dict[str | int, str | int] = None): """ From 77f4bc07d0b005c4248032018b9c260667873892 Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 00:52:01 +1000 Subject: [PATCH 13/15] Update tests --- src/flint/test/test_all.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 9c3241b4..e69a12f9 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2885,35 +2885,38 @@ def quick_poly(): ctx1 = get_context(("x", 4)) ctx2 = get_context(("x", 4), ordering="deglex") - assert ctx1.drop_gens(*ctx1.names()).names() == tuple() - assert ctx1.drop_gens(ctx1.name(1), ctx1.name(2)).names() == (ctx1.name(0), ctx1.name(3)) - assert ctx1.drop_gens().names() == ctx1.names() - assert ctx1.drop_gens(-1).names() == ctx1.names()[:-1] + assert ctx1.drop_gens(ctx1.names()).names() == tuple() + assert ctx1.drop_gens((ctx1.name(1), ctx1.name(2))).names() == (ctx1.name(0), ctx1.name(3)) + assert ctx1.drop_gens(tuple()).names() == ctx1.names() + assert ctx1.drop_gens((-1,)).names() == ctx1.names()[:-1] assert ctx.infer_generator_mapping(ctx) == {i: i for i in range(ctx.nvars())} assert ctx1.infer_generator_mapping(ctx) == {0: 0, 1: 1} - assert ctx1.drop_gens(*ctx.names()).infer_generator_mapping(ctx) == {} + assert ctx1.drop_gens(ctx.names()).infer_generator_mapping(ctx) == {} # FIXME: Remove this guard when https://github.com/flintlib/flint/pull/2068 is # resolved if P is not flint.fmpz_mod_mpoly: - assert quick_poly().coerce_to_context(ctx1) == \ + assert quick_poly().project_to_context(ctx1) == \ ctx1.from_dict( {(0, 0, 0, 0): 1, (0, 1, 0, 0): 2, (1, 0, 0, 0): 3, (2, 2, 0, 0): 4} ) - assert quick_poly().coerce_to_context(ctx1).drop_unused_gens() == (ctx, quick_poly()) + new_poly = quick_poly().project_to_context(ctx1) + assert ctx1.drop_gens(new_poly.unused_gens()) == ctx + assert new_poly.project_to_context(ctx) == quick_poly() - new_ctx, new_poly = quick_poly().coerce_to_context(ctx2).drop_unused_gens() + new_poly = quick_poly().project_to_context(ctx2) + new_ctx = ctx2.drop_gens(new_poly.unused_gens()) assert new_ctx != ctx assert new_poly != quick_poly() new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering()) assert new_ctx == ctx - assert new_poly.coerce_to_context(new_ctx) == quick_poly() + assert new_poly.project_to_context(new_ctx) == quick_poly() assert ctx.append_gens(*ctx1.names()[-2:]) == ctx1 else: - assert raises(lambda: quick_poly().coerce_to_context(ctx1), NotImplementedError) + assert raises(lambda: quick_poly().project_to_context(ctx1), NotImplementedError) assert P(val={(0, 0): 1}, ctx=ctx) == ctx.from_dict({(0, 0): 1}) assert P(ctx=ctx).context() == ctx From f0ca6506b5da118d4718da5e7edb158950c983e4 Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 00:52:13 +1000 Subject: [PATCH 14/15] Remove commented out methods --- src/flint/types/fmpq_mpoly.pyx | 22 -------------- src/flint/types/fmpz_mod_mpoly.pyx | 49 ------------------------------ src/flint/types/fmpz_mpoly.pyx | 22 -------------- src/flint/types/nmod_mpoly.pyx | 49 ------------------------------ 4 files changed, 142 deletions(-) diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index ced7c625..2cc586c4 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -548,28 +548,6 @@ cdef class fmpq_mpoly(flint_mpoly): return res - # def terms(self): - # """ - # Return the terms of this polynomial as a list of fmpq_mpolys. - - # >>> ctx = fmpq_mpoly_ctx.get(('x', 2), 'lex') - # >>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4}) - # >>> f.terms() - # [4*x0*x1, 2*x0, 3*x1, 1] - - # """ - # cdef: - # fmpq_mpoly term - # slong i - - # res = [] - # for i in range(len(self)): - # term = create_fmpq_mpoly(self.ctx) - # fmpq_mpoly_get_term(term.val, self.val, i, self.ctx.val) - # res.append(term) - - # return res - def subs(self, dict_args) -> fmpq_mpoly: """ Partial evaluate this polynomial with select constants. Keys must be generator names or generator indices, diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index c68002d6..2a3bd2b8 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -556,28 +556,6 @@ cdef class fmpz_mod_mpoly(flint_mpoly): return res - # def terms(self): - # """ - # Return the terms of this polynomial as a list of fmpz_mod_mpolys. - - # >>> ctx = fmpz_mod_mpoly_ctx.get(('x', 2), 11, 'lex') - # >>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4}) - # >>> f.terms() - # [4*x0*x1, 2*x0, 3*x1, 1] - - # """ - # cdef: - # fmpz_mod_mpoly term - # slong i - - # res = [] - # for i in range(len(self)): - # term = create_fmpz_mod_mpoly(self.ctx) - # fmpz_mod_mpoly_get_term(term.val, self.val, i, self.ctx.val) - # res.append(term) - - # return res - def subs(self, dict_args) -> fmpz_mod_mpoly: """ Partial evaluate this polynomial with select constants. Keys must be generator names or generator indices, @@ -948,33 +926,6 @@ cdef class fmpz_mod_mpoly(flint_mpoly): fmpz_mod_mpoly_factor_clear(fac, self.ctx.val) return c, res - # TODO: Rethink context conversions, particularly the proposed methods in #132 - # def project_to_context(self, ctx): - # cdef: - # fmpz_mod_mpoly res - # slong *C - # slong i - - # if not typecheck(ctx, fmpz_mod_mpoly_ctx): - # raise ValueError("provided context is not a fmpz_mod_mpoly_ctx") - - # if self.ctx is ctx: - # return self - - # C = libc.stdlib.malloc(self.ctx.val.minfo.nvars * sizeof(slong *)) - # if C is NULL: - # raise MemoryError("malloc returned a null pointer") - # res = create_fmpz_mod_mpoly(self.ctx) - - # vars = {x: i for i, x in enumerate(ctx.py_names)} - # for i, var in enumerate(self.ctx.py_names): - # C[i] = vars[var] - - # fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen(res.val, self.val, C, self.ctx.val, (ctx).val) - - # libc.stdlib.free(C) - # return res - def derivative(self, var): """ Return the derivative of this polynomial with respect to the provided variable. diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index 512258d5..61fce82a 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -538,28 +538,6 @@ cdef class fmpz_mpoly(flint_mpoly): return res - # def terms(self): - # """ - # Return the terms of this polynomial as a list of fmpz_mpolys. - - # >>> ctx = fmpz_mpoly_ctx.get(('x', 2), 'lex') - # >>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4}) - # >>> f.terms() - # [4*x0*x1, 2*x0, 3*x1, 1] - - # """ - # cdef: - # fmpz_mpoly term - # slong i - - # res = [] - # for i in range(len(self)): - # term = create_fmpz_mpoly(self.ctx) - # fmpz_mpoly_get_term(term.val, self.val, i, self.ctx.val) - # res.append(term) - - # return res - def subs(self, dict_args) -> fmpz_mpoly: """ Partial evaluate this polynomial with select constants. Keys must be generator names or generator indices, diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index 43131cf0..c3723747 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -539,28 +539,6 @@ cdef class nmod_mpoly(flint_mpoly): """ return [nmod_mpoly_get_term_coeff_ui(self.val, i, self.ctx.val) for i in range(len(self))] - # def terms(self): - # """ - # Return the terms of this polynomial as a list of nmod_mpolys. - - # >>> ctx = nmod_mpoly_ctx.get(('x', 2), 11, 'lex') - # >>> f = ctx.from_dict({(0, 0): 1, (1, 0): 2, (0, 1): 3, (1, 1): 4}) - # >>> f.terms() - # [4*x0*x1, 2*x0, 3*x1, 1] - - # """ - # cdef: - # nmod_mpoly term - # slong i - - # res = [] - # for i in range(len(self)): - # term = create_nmod_mpoly(self.ctx) - # nmod_mpoly_get_term(term.val, self.val, i, self.ctx.val) - # res.append(term) - - # return res - def subs(self, dict_args) -> nmod_mpoly: """ Partial evaluate this polynomial with select constants. Keys must be generator names or generator indices, @@ -926,33 +904,6 @@ cdef class nmod_mpoly(flint_mpoly): nmod_mpoly_factor_clear(fac, self.ctx.val) return constant, res - # TODO: Rethink context conversions, particularly the proposed methods in #132 - # def project_to_context(self, ctx): - # cdef: - # nmod_mpoly res - # slong *C - # slong i - - # if not typecheck(ctx, nmod_mpoly_ctx): - # raise ValueError("provided context is not a nmod_mpoly_ctx") - - # if self.ctx is ctx: - # return self - - # C = libc.stdlib.malloc(self.ctx.val.minfo.nvars * sizeof(slong *)) - # if C is NULL: - # raise MemoryError("malloc returned a null pointer") - # res = create_nmod_mpoly(self.ctx) - - # vars = {x: i for i, x in enumerate(ctx.py_names)} - # for i, var in enumerate(self.ctx.py_names): - # C[i] = vars[var] - - # nmod_mpoly_compose_nmod_mpoly_gen(res.val, self.val, C, self.ctx.val, (ctx).val) - - # libc.stdlib.free(C) - # return res - def derivative(self, var): """ Return the derivative of this polynomial with respect to the provided variable. From d1a14383997279d96402cdc6d13d4fc17562a5ea Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Mon, 23 Sep 2024 01:07:21 +1000 Subject: [PATCH 15/15] Add `fmpz_mod_mpoly` workaround --- src/flint/test/test_all.py | 41 +++++++++++++----------------- src/flint/types/fmpz_mod_mpoly.pyx | 38 +++++++++++++++------------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index e69a12f9..6d5f19aa 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2894,29 +2894,24 @@ def quick_poly(): assert ctx1.infer_generator_mapping(ctx) == {0: 0, 1: 1} assert ctx1.drop_gens(ctx.names()).infer_generator_mapping(ctx) == {} - # FIXME: Remove this guard when https://github.com/flintlib/flint/pull/2068 is - # resolved - if P is not flint.fmpz_mod_mpoly: - assert quick_poly().project_to_context(ctx1) == \ - ctx1.from_dict( - {(0, 0, 0, 0): 1, (0, 1, 0, 0): 2, (1, 0, 0, 0): 3, (2, 2, 0, 0): 4} - ) - new_poly = quick_poly().project_to_context(ctx1) - assert ctx1.drop_gens(new_poly.unused_gens()) == ctx - assert new_poly.project_to_context(ctx) == quick_poly() - - new_poly = quick_poly().project_to_context(ctx2) - new_ctx = ctx2.drop_gens(new_poly.unused_gens()) - assert new_ctx != ctx - assert new_poly != quick_poly() - - new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering()) - assert new_ctx == ctx - assert new_poly.project_to_context(new_ctx) == quick_poly() - - assert ctx.append_gens(*ctx1.names()[-2:]) == ctx1 - else: - assert raises(lambda: quick_poly().project_to_context(ctx1), NotImplementedError) + assert quick_poly().project_to_context(ctx1) == \ + ctx1.from_dict( + {(0, 0, 0, 0): 1, (0, 1, 0, 0): 2, (1, 0, 0, 0): 3, (2, 2, 0, 0): 4} + ) + new_poly = quick_poly().project_to_context(ctx1) + assert ctx1.drop_gens(new_poly.unused_gens()) == ctx + assert new_poly.project_to_context(ctx) == quick_poly() + + new_poly = quick_poly().project_to_context(ctx2) + new_ctx = ctx2.drop_gens(new_poly.unused_gens()) + assert new_ctx != ctx + assert new_poly != quick_poly() + + new_ctx = new_ctx.from_context(new_ctx, ordering=ctx.ordering()) + assert new_ctx == ctx + assert new_poly.project_to_context(new_ctx) == quick_poly() + + assert ctx.append_gens(*ctx1.names()[-2:]) == ctx1 assert P(val={(0, 0): 1}, ctx=ctx) == ctx.from_dict({(0, 0): 1}) assert P(ctx=ctx).context() == ctx diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index 2a3bd2b8..5ecb01ad 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -4,7 +4,6 @@ from flint.flint_base.flint_base cimport ( ordering_py_to_c, ordering_c_to_py, ) -from flint.flint_base.flint_base import FLINT_VERSION from flint.utils.typecheck cimport typecheck from flint.utils.flint_exceptions import DomainError, IncompatibleContextError @@ -20,7 +19,7 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_add_fmpz, fmpz_mod_mpoly_clear, fmpz_mod_mpoly_compose_fmpz_mod_mpoly, - fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen, + # fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen, fmpz_mod_mpoly_ctx_get_modulus, fmpz_mod_mpoly_ctx_init, fmpz_mod_mpoly_deflate, @@ -71,6 +70,8 @@ from flint.flintlib.functions.fmpz_mod_mpoly_factor cimport ( fmpz_mod_mpoly_factor_t, ) +from flint.types.fmpz_mpoly cimport fmpz_mpoly_ctx, fmpz_mpoly + from cpython.object cimport Py_EQ, Py_NE cimport libc.stdlib @@ -1092,21 +1093,26 @@ cdef class fmpz_mod_mpoly(flint_mpoly): return list(stride), list(shift) cdef _compose_gens_(self, ctx, slong *mapping): - raise NotImplementedError( - "this function is not supported below FLINT 3.2.0, " - f"current version is {FLINT_VERSION}" - ) - - cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx) - fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen( - res.val, - self.val, - mapping, - self.ctx.val, - (ctx).val - ) + # FIXME: Remove this when https://github.com/flintlib/flint/pull/2068 is + # resolved + + # cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(ctx) + # fmpz_mod_mpoly_compose_fmpz_mod_mpoly_gen( + # res.val, + # self.val, + # mapping, + # self.ctx.val, + # (ctx).val + # ) - return res + cdef: + fmpz_mpoly_ctx mpoly_ctx = fmpz_mpoly_ctx.from_context(self.context()) + fmpz_mpoly_ctx res_ctx = fmpz_mpoly_ctx.from_context(ctx) + + fmpz_mpoly poly = mpoly_ctx.from_dict(self.to_dict()) + fmpz_mpoly res = poly._compose_gens_(res_ctx, mapping) + + return ctx.from_dict(res.to_dict()) cdef class fmpz_mod_mpoly_vec: