diff --git a/doc/source/fmpq_mpoly.rst b/doc/source/fmpq_mpoly.rst new file mode 100644 index 00000000..bce01253 --- /dev/null +++ b/doc/source/fmpq_mpoly.rst @@ -0,0 +1,18 @@ +**fmpq_mpoly** -- multivariate polynomials over the rational numbers +=============================================================================== + +.. autoclass :: flint.fmpq_mpoly_ctx + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpq_mpoly + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpq_mpoly_vec + :members: + :inherited-members: + :undoc-members: + diff --git a/doc/source/fmpz_mod_mpoly.rst b/doc/source/fmpz_mod_mpoly.rst new file mode 100644 index 00000000..d28c0faa --- /dev/null +++ b/doc/source/fmpz_mod_mpoly.rst @@ -0,0 +1,18 @@ +**fmpz_mod_mpoly** -- multivariate polynomials over the integers mod n +=============================================================================== + +.. autoclass :: flint.fmpz_mod_mpoly_ctx + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpz_mod_mpoly + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpz_mod_mpoly_vec + :members: + :inherited-members: + :undoc-members: + diff --git a/doc/source/fmpz_mpoly.rst b/doc/source/fmpz_mpoly.rst new file mode 100644 index 00000000..cbdb776e --- /dev/null +++ b/doc/source/fmpz_mpoly.rst @@ -0,0 +1,18 @@ +**fmpz_mpoly** -- multivariate polynomials over the integers +=============================================================================== + +.. autoclass :: flint.fmpz_mpoly_ctx + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpz_mpoly + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.fmpz_mpoly_vec + :members: + :inherited-members: + :undoc-members: + diff --git a/doc/source/index.rst b/doc/source/index.rst index 410b04ba..887c68a7 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -73,9 +73,13 @@ Polynomial types :maxdepth: 1 fmpz_poly.rst + fmpz_mpoly.rst fmpq_poly.rst + fmpq_mpoly.rst nmod_poly.rst + nmod_mpoly.rst fmpz_mod_poly.rst + fmpz_mod_mpoly.rst fq_default_poly.rst arb_poly.rst acb_poly.rst diff --git a/doc/source/nmod_mpoly.rst b/doc/source/nmod_mpoly.rst new file mode 100644 index 00000000..b20c45ed --- /dev/null +++ b/doc/source/nmod_mpoly.rst @@ -0,0 +1,18 @@ +**nmod_mpoly** -- multivariate polynomials over the integers mod n (word-size n) +=============================================================================== + +.. autoclass :: flint.nmod_mpoly_ctx + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.nmod_mpoly + :members: + :inherited-members: + :undoc-members: + +.. autoclass :: flint.nmod_mpoly_vec + :members: + :inherited-members: + :undoc-members: + diff --git a/src/flint/flint_base/flint_base.pxd b/src/flint/flint_base/flint_base.pxd index 2f493687..1f1d2371 100644 --- a/src/flint/flint_base/flint_base.pxd +++ b/src/flint/flint_base/flint_base.pxd @@ -23,6 +23,8 @@ cdef class flint_mpoly(flint_elem): cdef _mul_mpoly_(self, other) cdef _divmod_mpoly_(self, other) + cdef _truediv_scalar_(self, other) + cdef _divexact_scalar_(self, other) cdef _floordiv_mpoly_(self, other) cdef _truediv_mpoly_(self, other) cdef _mod_mpoly_(self, other) diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index 69f53490..18ac3e0c 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -229,7 +229,7 @@ cdef class flint_poly(flint_elem): integer root and *m* is the multiplicity of the root. To compute complex roots of a polynomial, instead use - the `.complex_roots()` method, which is available on + the ``.complex_roots()`` method, which is available on certain polynomial rings. >>> from flint import fmpz_poly @@ -322,9 +322,9 @@ cdef class flint_mpoly_context(flint_elem): @staticmethod def create_variable_names(slong nvars, names: str): """ - Create a tuple of variable names based on the comma separated `names` string. + Create a tuple of variable names based on the comma separated ``names`` string. - If `names` contains a single value, and `nvars` > 1, then the variables are numbered, e.g. + If ``names`` contains a single value, and ``nvars`` > 1, then the variables are numbered, e.g. >>> flint_mpoly_context.create_variable_names(3, "x") ('x0', 'x1', 'x2') @@ -344,24 +344,24 @@ cdef class flint_mpoly_context(flint_elem): Create a key for the context cache via the number of variables, the ordering, and either a variable name string, or a tuple of variable names. """ - # A type hint of `ordering: Ordering` results in the error "TypeError: an integer is required" if a Ordering + # A type hint of ``ordering: Ordering`` results in the error "TypeError: an integer is required" if a Ordering # object is not provided. This is pretty obtuse so we check its type ourselves if not isinstance(ordering, Ordering): - raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering") + raise TypeError(f"'ordering' ('{ordering}') is not an instance of flint.Ordering") if nametup is not None: key = nvars, ordering, nametup elif nametup is None and names is not None: key = nvars, ordering, cls.create_variable_names(nvars, names) else: - raise ValueError("must provide either `names` or `nametup`") + raise ValueError("must provide either 'names' or 'nametup'") return key @classmethod def get_context(cls, *args, **kwargs): """ - Retrieve a context via the number of variables, `nvars`, the ordering, `ordering`, and either a variable - name string, `names`, or a tuple of variable names, `nametup`. + Retrieve a context via the number of variables, ``nvars``, the ordering, ``ordering``, and either a variable + name string, ``names``, or a tuple of variable names, ``nametup``. """ key = cls.create_context_key(*args, **kwargs) @@ -391,6 +391,24 @@ cdef class flint_mpoly_context(flint_elem): elif other is not self: raise IncompatibleContextError(f"{other} is not {self}") + def term(self, coeff = None, exp_vec = None): + """ + Create a monomial from a coefficient and exponent vector. ``coeff`` defaults + to ``1``. ``exp_vec``` defaults to ``(0,) * self.nvars()```. + + >>> from flint import fmpz_mpoly_ctx, Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> ctx.term(coeff=5, exp_vec=(2, 3)) + 5*x0^2*x1^3 + >>> ctx.term() + 1 + """ + if coeff is None: + coeff = 1 + if exp_vec is None: + exp_vec = (0,) * self.nvars() + return self.from_dict({tuple(exp_vec): coeff}) + cdef class flint_mpoly(flint_elem): """ @@ -405,7 +423,7 @@ cdef class flint_mpoly(flint_elem): def _division_check(self, other): if not other: - raise ZeroDivisionError("nmod_mpoly division by zero") + raise ZeroDivisionError(f"{self.__class__.__name__} division by zero") cdef _add_scalar_(self, other): return NotImplemented @@ -431,6 +449,12 @@ cdef class flint_mpoly(flint_elem): cdef _floordiv_mpoly_(self, other): return NotImplemented + cdef _truediv_scalar_(self, other): + return NotImplemented + + cdef _divexact_scalar_(self, other): + return NotImplemented + cdef _truediv_mpoly_(self, other): return NotImplemented @@ -589,8 +613,12 @@ cdef class flint_mpoly(flint_elem): if other is NotImplemented: return NotImplemented - other = self.context().scalar_as_mpoly(other) self._division_check(other) + res = self._truediv_scalar_(other) + if res is not NotImplemented: + return res + + other = self.context().scalar_as_mpoly(other) return self._truediv_mpoly_(other) def __rtruediv__(self, other): @@ -725,7 +753,7 @@ cdef class flint_mpoly(flint_elem): def __contains__(self, x): """ - Returns True if `self` contains a term with exponent vector `x` and a non-zero coefficient. + Returns True if ``self`` contains a term with exponent vector ``x`` and a non-zero coefficient. >>> from flint import fmpq_mpoly_ctx, Ordering >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') @@ -829,7 +857,7 @@ cdef class flint_mat(flint_elem): cdef ordering_t ordering_py_to_c(ordering): # Cython does not like an "Ordering" type hint here if not isinstance(ordering, Ordering): - raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering") + raise TypeError(f"'ordering' ('{ordering}') is not an instance of flint.Ordering") if ordering == Ordering.lex: return ordering_t.ORD_LEX diff --git a/src/flint/flintlib/types/nmod.pxd b/src/flint/flintlib/types/nmod.pxd index 1b7616e3..c1324908 100644 --- a/src/flint/flintlib/types/nmod.pxd +++ b/src/flint/flintlib/types/nmod.pxd @@ -13,9 +13,6 @@ cdef extern from "flint/nmod_types.h": ctypedef nmod_mat_struct nmod_mat_t[1] - # XXX: Undocumented function: - int nmod_mat_is_square(const nmod_mat_t mat) - # Macros: ulong nmod_mat_entry(nmod_mat_t mat, slong i, slong j) diff --git a/src/flint/flintlib/types/undocumented.pxd b/src/flint/flintlib/types/undocumented.pxd new file mode 100644 index 00000000..247a3912 --- /dev/null +++ b/src/flint/flintlib/types/undocumented.pxd @@ -0,0 +1,12 @@ +from flint.flintlib.types.nmod cimport nmod_mat_t + +from flint.flintlib.types.flint cimport fmpz_struct +from flint.flintlib.types.nmod cimport nmod_mpoly_ctx_t, nmod_mpoly_t + +cdef extern from "flint/nmod_types.h": + int nmod_mat_is_square(const nmod_mat_t mat) + +cdef extern from "flint/nmod_mpoly.h": + void nmod_mpoly_deflation(fmpz_struct * shift, fmpz_struct * stride, const nmod_mpoly_t A, const nmod_mpoly_ctx_t ctx) + void nmod_mpoly_deflate(nmod_mpoly_t A, const nmod_mpoly_t B, const fmpz_struct * shift, const fmpz_struct * stride, const nmod_mpoly_ctx_t ctx) + void nmod_mpoly_inflate(nmod_mpoly_t A, const nmod_mpoly_t B, const fmpz_struct * shift, const fmpz_struct * stride, const nmod_mpoly_ctx_t ctx) diff --git a/src/flint/test/__main__.py b/src/flint/test/__main__.py index 21124c51..0dea1419 100644 --- a/src/flint/test/__main__.py +++ b/src/flint/test/__main__.py @@ -11,6 +11,7 @@ import flint from flint.test.test_all import all_tests +from flint.test.test_docstrings import find_doctests def run_tests(verbose=None): @@ -48,54 +49,18 @@ def run_tests(verbose=None): return failed, total -def run_doctests(verbose=None): - """Run the python-flint doctests""" - # Here verbose=True shows a lot of output. - modules = [flint.pyflint, - flint.flint_base.flint_base, - flint.flint_base.flint_context, - flint.types.fmpz, - flint.types.fmpz_poly, - flint.types.fmpz_mat, - flint.types.fmpz_mpoly, - flint.types.fmpz_series, - flint.types.fmpz_mod, - flint.types.fmpz_mod_poly, - flint.types.fmpz_mod_mat, - flint.types.fmpq, - flint.types.fmpq_poly, - flint.types.fmpq_mat, - flint.types.fmpq_mpoly, - flint.types.fmpq_series, - flint.types.nmod, - flint.types.nmod_poly, - flint.types.nmod_mat, - flint.types.nmod_series, - flint.types.fq_default, - flint.types.fq_default_poly, - flint.types.arf, - flint.types.arb, - flint.types.arb_poly, - flint.types.arb_mat, - flint.types.arb_series, - flint.types.acb, - flint.types.acb_poly, - flint.types.acb_mat, - flint.types.acb_series, - flint.types.dirichlet, - flint.functions.showgood] - try: - from flint.types import acb_theta - modules.append(acb_theta) - except ImportError: - pass - - results = [] - for x in modules: - if verbose: - print(f" {x.__name__}") - results.append(doctest.testmod(x)) - return tuple(sum(res) for res in zip(*results)) +def run_doctests(tests, verbose=False): + runner = doctest.DocTestRunner() + for module, test_set in tests: + if test_set: + print(f"{module}...", end="" if not verbose else "\n", flush=True) + for test in test_set: + if verbose: + print("\tTesting:", test.name) + runner.run(test) + print("OK") + + return runner.summarize() def run_all_tests(tests=True, doctests=True, verbose=None): @@ -108,7 +73,7 @@ def run_all_tests(tests=True, doctests=True, verbose=None): if doctests: print("Running doctests...") - d_failed, d_total = run_doctests(verbose=verbose) + d_failed, d_total = run_doctests(find_doctests(flint), verbose=verbose) if tests: if t_failed: diff --git a/src/flint/test/meson.build b/src/flint/test/meson.build index 4aa245a1..4f93f395 100644 --- a/src/flint/test/meson.build +++ b/src/flint/test/meson.build @@ -4,6 +4,7 @@ pyfiles = [ '__init__.py', '__main__.py', 'test_all.py', + 'test_docstrings.py', ] py.install_sources( diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 9494302f..a9e3ee75 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -3229,6 +3229,43 @@ def quick_poly(): assert raises(lambda: quick_poly().gcd(None), TypeError) assert raises(lambda: quick_poly().gcd(P(ctx=ctx1)), IncompatibleContextError) + x0, x1 = ctx.gens() + assert (2*x0**3 + 4*x0**2 + 6*x0).term_content() == x0 if is_field else 2*x0 + assert (3*x0**2*x1 + 6*x0*x1**2 + 9*x1**3).term_content() == x1 if is_field else 3*x1 + + assert (x0**2 - 1).resultant(x0**2 - 2*x0 + 1, 'x0') == 0 + assert (x0**2 + x1**2 - 1).resultant(x0 - x1, 'x0') == 2*x1**2 - 1 + + assert (x0**3 - 6*x0**2 + 11*x0 - 6).discriminant('x0') == 4 + assert (x0**2 + 4*x0*x1 + 4*x1**2 - 1).discriminant('x0') == 4 + + f1 = 3*x0**2*x1**2 + 6*x0*x1**2 + 9*x1**2 + res, stride = f1.deflation() + assert res == 3*x0**2*x1 + 6*x0*x1 + 9*x1 + assert tuple(stride) == (1, 2) + + g1 = ((x0**2 + x1**2)**3 + (x0**2 + x1**2)**2 + 1) + res, stride = g1.deflation() + assert res == x0**3 + 3*x0**2*x1 + x0**2 + 3*x0*x1**2 + 2*x0*x1 + x1**3 + x1**2 + 1 + assert tuple(stride) == (2, 2) + + for p in [f1, g1]: + pd, n = p.deflation() + assert pd.inflate(n) == p + assert p.deflate(n).inflate(n) == p + + pd, n, m = p.deflation_monom() + assert m * pd.inflate(n) == p + + if not composite_characteristic: + n, i = p.deflation_index() + m = ctx.term(exp_vec=i) + assert (p / m).deflate(n).inflate(n) * m == p + + if P is flint.fmpz_mpoly: + assert (x0**2 * x1 + x0 * x1).primitive() == (1, x0**2*x1 + x0*x1) + assert (4 * x0 + 2 * x0 * x1).primitive() == (2, x0 * x1 + 2 * x0) + if composite_characteristic: # Factorisation not allowed over Z/nZ for n not prime. # Flint would abort so we raise an exception instead: @@ -3304,7 +3341,7 @@ def test_fmpz_mpoly_vec(): assert vec != mpoly_vec([x, x * y, ctx.from_dict({})], ctx) assert vec != mpoly_vec([ctx.from_dict({})], ctx) assert vec != mpoly_vec([ctx1.from_dict({})], ctx1) - assert vec.to_tuple() == mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx).to_tuple() + assert tuple(vec) == tuple(mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx)) assert raises(lambda: vec.__setitem__(None, 0), TypeError) assert raises(lambda: vec.__setitem__(-1, 0), IndexError) assert raises(lambda: vec.__setitem__(0, 0), TypeError) diff --git a/src/flint/test/test_docstrings.py b/src/flint/test/test_docstrings.py new file mode 100644 index 00000000..7a4c0287 --- /dev/null +++ b/src/flint/test/test_docstrings.py @@ -0,0 +1,86 @@ +import doctest +import importlib +import pkgutil +import re + +import flint + +dunder_test_regex = re.compile(r'^(.*?)__test__\..*?\.(.*) \(line (\d+)\)$') + + +def find_doctests(module): + finder = doctest.DocTestFinder() + tests = [] + for module_info in pkgutil.walk_packages(module.__path__, flint.__name__ + "."): + try: + module = importlib.import_module(module_info.name) + + res = [] + for test in filter(lambda x: bool(x.examples), finder.find(module)): + m = dunder_test_regex.match(test.name) + if m is not None: + groups = m.groups() + test.name = groups[0] + groups[1] + test.lineno = int(groups[2]) + res.append(test) + + tests.append((module_info.name, res)) + + except Exception as e: + print(f"Error importing {module_info.name}: {e}") + return tests + + +# The below definitions are only useful when pytest is a) installed, and b) being currently run. +# We don't want to impose pytest on those that just want to use `python -m flint.test` +try: + import pytest + + class PyTestDocTestRunner(doctest.DocTestRunner): + def report_failure(self, out, test, example, got): + pytest.fail( + "\n".join([ + f"{test.name}, line: {test.lineno}", + "Failed example:", + f"\t{example.source.strip()}", + "Expected:", + f"\t{example.want.strip()}", + "Got:", + f"\t{got.strip()}" + ]), + pytrace=False, + ) + + def report_unexpected_exception(self, out, test, example, exc_info): + pytest.fail( + "\n".join([ + f"{test.name}, line: {test.lineno}", + "Failed example:", + f"\t{example.source.strip()}", + "Exception raised:", + doctest._indent(doctest._exception_traceback(exc_info)) + ]), + pytrace=False, + ) + + runner = PyTestDocTestRunner() + + @pytest.mark.parametrize( + "test", + [ + test for _, test_set in find_doctests(flint) + for test in test_set + ], + ids=lambda test: test.name, + ) + def test_docstrings(test): + runner.run(test) + +except ImportError: + class PyTestDocTestRunner(doctest.DocTestRunner): + pass + + runner = None + + def test_docstrings(test): + pass diff --git a/src/flint/types/arb.pyx b/src/flint/types/arb.pyx index 2553a92f..94c281ae 100644 --- a/src/flint/types/arb.pyx +++ b/src/flint/types/arb.pyx @@ -2268,7 +2268,7 @@ cdef class arb(flint_scalar): >>> from flint import showgood >>> showgood(lambda: arb("9/10").hypgeom_2f1(arb(2).sqrt(), 0.5, arb(2).sqrt()+1.5, abc=True), dps=25) 1.447530478120770807945697 - >>> showgood(lambda: arb("9/10").hypgeom_2f1(arb(2).sqrt(), 0.5, arb(2).sqrt()+1.5), dps=25) + >>> showgood(lambda: arb("9/10").hypgeom_2f1(arb(2).sqrt(), 0.5, arb(2).sqrt()+1.5), dps=25) # doctest: +SKIP Traceback (most recent call last): ... ValueError: no convergence (maxprec=960, try higher maxprec) diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index cda577fb..6e585a51 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -24,6 +24,7 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_ctx_init, fmpq_mpoly_degrees_fmpz, fmpq_mpoly_derivative, + fmpq_mpoly_discriminant, fmpq_mpoly_div, fmpq_mpoly_divides, fmpq_mpoly_divrem, @@ -46,7 +47,10 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_neg, fmpq_mpoly_pow_fmpz, fmpq_mpoly_push_term_fmpq_ffmpz, + fmpq_mpoly_push_term_ui_ffmpz, fmpq_mpoly_reduce, + fmpq_mpoly_resultant, + fmpq_mpoly_scalar_div_fmpq, fmpq_mpoly_scalar_mul_fmpq, fmpq_mpoly_set, fmpq_mpoly_set_coeff_fmpq_fmpz, @@ -56,6 +60,7 @@ from flint.flintlib.functions.fmpq_mpoly cimport ( fmpq_mpoly_sqrt, fmpq_mpoly_sub, fmpq_mpoly_sub_fmpq, + fmpq_mpoly_term_content, fmpq_mpoly_total_degree_fmpz, ) from flint.flintlib.functions.fmpq_mpoly_factor cimport ( @@ -67,7 +72,12 @@ from flint.flintlib.functions.fmpq_mpoly_factor cimport ( ) from flint.flintlib.functions.fmpz cimport fmpz_init_set -from flint.flintlib.functions.fmpz_mpoly cimport fmpz_mpoly_set +from flint.flintlib.functions.fmpz_mpoly cimport ( + fmpz_mpoly_set, + fmpz_mpoly_deflate, + fmpz_mpoly_deflation, + fmpz_mpoly_inflate, +) from cpython.object cimport Py_EQ, Py_NE cimport libc.stdlib @@ -83,7 +93,7 @@ cdef class fmpq_mpoly_ctx(flint_mpoly_context): :param ordering: The term order for the ring :param names: A tuple containing the names of the variables of the ring. - Do not construct one of these directly, use `fmpz_mpoly_ctx.get_context`. + Do not construct one of these directly, use ``fmpz_mpoly_ctx.get_context``. """ _ctx_cache = _fmpq_mpoly_ctx_cache @@ -132,7 +142,7 @@ cdef class fmpq_mpoly_ctx(flint_mpoly_context): def gen(self, slong i): """ - Return the `i`th generator of the polynomial ring + Return the ``i`` th generator of the polynomial ring >>> from flint import Ordering >>> ctx = fmpq_mpoly_ctx.get_context(3, Ordering.degrevlex, 'z') @@ -292,8 +302,8 @@ cdef class fmpq_mpoly(flint_mpoly): def __getitem__(self, x): """ - Return the coefficient of the term with the exponent vector `x`. - Always returns a value, missing keys will return `0`. + Return the coefficient of the term with the exponent vector ``x``. + Always returns a value, missing keys will return ``0``. Negative exponents are made positive. >>> from flint import Ordering @@ -318,7 +328,7 @@ cdef class fmpq_mpoly(flint_mpoly): def __setitem__(self, x, y): """ - Set the coefficient of the term with the exponent vector `x` to `y`. + Set the coefficient of the term with the exponent vector ``x`` to ``y``. Will always set a value, missing keys will create a new term. Negative exponents are made positive. @@ -410,6 +420,16 @@ cdef class fmpq_mpoly(flint_mpoly): fmpq_mpoly_div(quotient.val, self.val, other.val, self.ctx.val) return quotient + cdef _truediv_scalar_(self, arg): + cdef fmpq_mpoly quotient + cdef fmpq other = arg + quotient = create_fmpq_mpoly(self.ctx) + fmpq_mpoly_scalar_div_fmpq(quotient.val, self.val, other.val, self.ctx.val) + return quotient + + cdef _divexact_scalar_(self, arg): + return self._truediv_scalar_(arg) + cdef _truediv_mpoly_(self, arg): cdef fmpq_mpoly quotient, other = arg quotient = create_fmpq_mpoly(self.ctx) @@ -505,7 +525,7 @@ cdef class fmpq_mpoly(flint_mpoly): res = [] for i in range(len(self)): fmpq_mpoly_get_term_exp_fmpz(vec.double_indirect, self.val, i, self.ctx.val) - res.append(vec.to_tuple()) + res.append(tuple(vec)) return res @@ -650,7 +670,7 @@ cdef class fmpq_mpoly(flint_mpoly): def coefficient(self, slong i): """ - Return the coefficient at index `i`. + Return the coefficient at index ``i``. >>> from flint import Ordering >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') @@ -668,7 +688,7 @@ cdef class fmpq_mpoly(flint_mpoly): def monomial(self, slong i): """ - Return the exponent vector at index `i` as a tuple. + Return the exponent vector at index ``i`` as a tuple. >>> from flint import Ordering >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') @@ -683,7 +703,7 @@ cdef class fmpq_mpoly(flint_mpoly): raise IndexError("term index out of range") res = fmpz_vec(nvars, double_indirect=True) fmpq_mpoly_get_term_exp_fmpz(res.double_indirect, self.val, i, self.ctx.val) - return res.to_tuple() + return tuple(res) def degrees(self): """ @@ -700,7 +720,7 @@ cdef class fmpq_mpoly(flint_mpoly): res = fmpz_vec(nvars, double_indirect=True) fmpq_mpoly_degrees_fmpz(res.double_indirect, self.val, self.ctx.val) - return res.to_tuple() + return tuple(res) def total_degree(self): """ @@ -767,6 +787,71 @@ cdef class fmpq_mpoly(flint_mpoly): fmpq_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res + def term_content(self): + """ + Return the GCD of the terms of ``self``. If ``self`` is zero, then the result will + be zero, otherwise it will be a monomial with positive coefficient. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = 3 * x0**2 * x1 + 6 * x0 * x1 + >>> f.term_content() + x0*x1 + """ + cdef fmpq_mpoly res = create_fmpq_mpoly(self.ctx) + fmpq_mpoly_term_content(res.val, self.val, self.ctx.val) + return res + + def resultant(self, other, var): + """ + Return the resultant of ``self`` and ``other`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = x0**2 * x1 + x0 * x1 + >>> g = x0 + x1 + >>> f.resultant(g, 'x1') + x0^3 + x0^2 + """ + cdef: + fmpq_mpoly res + slong i + + if not typecheck(other, fmpq_mpoly): + raise TypeError("argument must be a fmpq_mpoly") + elif (self).ctx is not (other).ctx: + raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + + i = self.ctx.variable_to_index(var) + res = create_fmpq_mpoly(self.ctx) + if not fmpq_mpoly_resultant(res.val, self.val, (other).val, i, self.ctx.val): + raise RuntimeError(f"failed to compute resultant with respect to {var}") + return res + + def discriminant(self, var): + """ + Return the discriminant of ``self`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = (x0 + x1)**2 + 1 + >>> f.discriminant('x1') + -4 + + """ + cdef: + fmpq_mpoly res + slong i + + i = self.ctx.variable_to_index(var) + res = create_fmpq_mpoly(self.ctx) + if not fmpq_mpoly_discriminant(res.val, self.val, i, self.ctx.val): + raise RuntimeError(f"failed to compute discriminant with respect to {var}") + return res + def sqrt(self): """ Return the square root of self. @@ -914,6 +999,154 @@ cdef class fmpq_mpoly(flint_mpoly): fmpq_mpoly_integral(res.val, self.val, i, self.ctx.val) return res + def inflate(self, N: list[int]) -> fmpq_mpoly: + """ + Compute the inflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^N)``. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x + y + 1 + >>> f.inflate([2, 3]) + x^2 + y^3 + 1 + """ + + cdef nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + elif any(n < 0 for n in N): + raise ValueError("all inflate strides must be non-negative") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpq_mpoly res = create_fmpq_mpoly(self.ctx) + + fmpz_mpoly_inflate(res.val.zpoly, self.val.zpoly, shift.val, stride.val, self.ctx.val.zctx) + fmpq_set(res.val.content, self.val.content) + return res + + def deflate(self, N: list[int]) -> fmpq_mpoly: + """ + Compute the deflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^(1/N))``. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> f.deflate([2, 3]) + x + y + 1 + """ + cdef slong nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpq_mpoly res = create_fmpq_mpoly(self.ctx) + + fmpz_mpoly_deflate(res.val.zpoly, self.val.zpoly, shift.val, stride.val, self.ctx.val.zctx) + fmpq_set(res.val.content, self.val.content) + return res + + def deflation(self) -> tuple[fmpq_mpoly, list[int]]: + """ + Compute the deflation of ``self``, that is ``p(X^(1/N))`` for maximal N. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**2 * y**2 + x * y**2 + >>> q, N = f.deflation() + >>> q, N + (x^2*y + x*y, [1, 2]) + >>> q.inflate(N) == f + True + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + fmpq_mpoly res = create_fmpq_mpoly(self.ctx) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx) + + for i in range(nvars): + stride[i] = shift[i].gcd(stride[i]) + shift[i] = 0 + + fmpz_mpoly_deflate(res.val.zpoly, self.val.zpoly, shift.val, stride.val, self.ctx.val.zctx) + fmpq_set(res.val.content, self.val.content) + + return res, list(stride) + + def deflation_monom(self) -> tuple[fmpq_mpoly, list[int], fmpq_mpoly]: + """ + Compute the exponent vector ``N`` and monomial ``m`` such that ``p(X^(1/N)) + = m * q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned monomial allows the undo-ing of the deflation. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> fd, N, m = f.deflation_monom() + >>> fd, N, m + (x + y + 1, [2, 3], x*y) + >>> m * fd.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpq_mpoly res = create_fmpq_mpoly(self.ctx) + fmpq_mpoly monom = create_fmpq_mpoly(self.ctx) + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx) + fmpq_mpoly_push_term_ui_ffmpz(monom.val, 1, fmpz_vec(shift).val, self.ctx.val) + fmpz_mpoly_deflate(res.val.zpoly, self.val.zpoly, shift.val, stride.val, self.ctx.val.zctx) + fmpq_set(res.val.content, self.val.content) + + return res, list(stride), monom + + def deflation_index(self) -> tuple[list[int], list[int]]: + """ + Compute the exponent vectors ``N`` and ``I`` such that ``p(X^(1/N)) = X^I * + q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned exponent vector ``I`` is the shift that was applied to the + exponents. It is the exponent vector of the monomial returned by + ``deflation_monom``. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> N, I = f.deflation_index() + >>> N, I + ([2, 3], [1, 1]) + >>> f_deflated = f.deflate(N) + >>> f_deflated + x + y + 1 + >>> m = ctx.term(exp_vec=I) + >>> m + x*y + >>> m * f_deflated.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val.zpoly, self.ctx.val.zctx) + return list(stride), list(shift) + cdef class fmpq_mpoly_vec: """ @@ -1000,5 +1233,9 @@ cdef class fmpq_mpoly_vec: else: return NotImplemented - def to_tuple(self): - return tuple(self[i] for i in range(self.length)) + def __iter__(self): + cdef fmpq_mpoly z + for i in range(self.length): + z = create_fmpq_mpoly(self.ctx) + fmpq_mpoly_set(z.val, &self.val[i], self.ctx.val) + yield z diff --git a/src/flint/types/fmpq_vec.pyx b/src/flint/types/fmpq_vec.pyx index 298b6b16..bb82662b 100644 --- a/src/flint/types/fmpq_vec.pyx +++ b/src/flint/types/fmpq_vec.pyx @@ -68,6 +68,13 @@ cdef class fmpq_vec: def __repr__(self): return self.repr() + def __iter__(self): + cdef fmpq z + for i in range(self.length): + z = fmpq.__new__(fmpq) + fmpq_set(z.val, &self.val[i]) + yield z + def str(self, *args): s = [None] * self.length for i in range(self.length): @@ -79,6 +86,3 @@ cdef class fmpq_vec: def repr(self, *args): return f"fmpq_vec({self.str(*args)}, {self.length})" - - def to_tuple(self): - return tuple(self[i] for i in range(self.length)) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index c92ed650..5559e20e 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -246,7 +246,7 @@ cdef class fmpz_mod(flint_scalar): self.ctx = ctx check = self.ctx.set_any_as_fmpz_mod(self.val, val) if check is NotImplemented: - raise NotImplementedError(f"Cannot convert {val} to type `fmpz_mod`") + raise NotImplementedError(f"Cannot convert {val} to type 'fmpz_mod'") def is_zero(self): """ diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index 287b43e2..576477b5 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -22,8 +22,11 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_compose_fmpz_mod_mpoly, fmpz_mod_mpoly_ctx_get_modulus, fmpz_mod_mpoly_ctx_init, + fmpz_mod_mpoly_deflate, + fmpz_mod_mpoly_deflation, fmpz_mod_mpoly_degrees_fmpz, fmpz_mod_mpoly_derivative, + fmpz_mod_mpoly_discriminant, fmpz_mod_mpoly_div, fmpz_mod_mpoly_divides, fmpz_mod_mpoly_divrem, @@ -37,6 +40,7 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_get_str_pretty, fmpz_mod_mpoly_get_term_coeff_fmpz, fmpz_mod_mpoly_get_term_exp_fmpz, + fmpz_mod_mpoly_inflate, fmpz_mod_mpoly_is_one, fmpz_mod_mpoly_is_zero, fmpz_mod_mpoly_length, @@ -44,16 +48,19 @@ from flint.flintlib.functions.fmpz_mod_mpoly cimport ( fmpz_mod_mpoly_neg, fmpz_mod_mpoly_pow_fmpz, fmpz_mod_mpoly_push_term_fmpz_ffmpz, + fmpz_mod_mpoly_push_term_ui_ffmpz, + fmpz_mod_mpoly_resultant, fmpz_mod_mpoly_scalar_mul_fmpz, fmpz_mod_mpoly_set, fmpz_mod_mpoly_set_coeff_fmpz_fmpz, fmpz_mod_mpoly_set_fmpz, fmpz_mod_mpoly_set_str_pretty, fmpz_mod_mpoly_sort_terms, + fmpz_mod_mpoly_sqrt, fmpz_mod_mpoly_sub, fmpz_mod_mpoly_sub_fmpz, + fmpz_mod_mpoly_term_content, fmpz_mod_mpoly_total_degree_fmpz, - fmpz_mod_mpoly_sqrt, ) from flint.flintlib.functions.fmpz_mod_mpoly_factor cimport ( fmpz_mod_mpoly_factor, @@ -77,7 +84,7 @@ cdef class fmpz_mod_mpoly_ctx(flint_mpoly_context): :param ordering: The term order for the ring :param names: A tuple containing the names of the variables of the ring. - Do not construct one of these directly, use `fmpz_mod_mpoly_ctx.get_context`. + Do not construct one of these directly, use ``fmpz_mod_mpoly_ctx.get_context``. """ _ctx_cache = _fmpz_mod_mpoly_ctx_cache @@ -107,14 +114,14 @@ cdef class fmpz_mod_mpoly_ctx(flint_mpoly_context): Create a key for the context cache via the number of variables, the ordering, the modulus, and either a variable name string, or a tuple of variable names. """ - # A type hint of `ordering: Ordering` results in the error "TypeError: an integer is required" if a Ordering + # A type hint of ``ordering: Ordering`` results in the error "TypeError: an integer is required" if a Ordering # object is not provided. This is pretty obtuse so we check its type ourselves if not isinstance(ordering, Ordering): - raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering") + raise TypeError(f"'ordering' ('{ordering}') is not an instance of flint.Ordering") elif not typecheck(modulus, fmpz): m = any_as_fmpz(modulus) if m is NotImplemented: - raise TypeError(f"`modulus` ('{modulus}') is not coercible to fmpz") + raise TypeError(f"'modulus' ('{modulus}') is not coercible to fmpz") else: modulus = m @@ -123,7 +130,7 @@ cdef class fmpz_mod_mpoly_ctx(flint_mpoly_context): elif nametup is None and names is not None: key = nvars, ordering, cls.create_variable_names(nvars, names), modulus else: - raise ValueError("must provide either `names` or `nametup`") + raise ValueError("must provide either 'names' or 'nametup'") return key def any_as_scalar(self, other): @@ -203,12 +210,12 @@ cdef class fmpz_mod_mpoly_ctx(flint_mpoly_context): True """ if self.__prime_modulus is None: - self.__prime_modulus = self.modulus().is_prime() + self.__prime_modulus = self.modulus().is_prime() return self.__prime_modulus def gen(self, slong i): """ - Return the `i`th generator of the polynomial ring + Return the ``i`` th generator of the polynomial ring >>> from flint import Ordering >>> ctx = fmpz_mod_mpoly_ctx.get_context(3, Ordering.degrevlex, 11, 'z') @@ -281,7 +288,7 @@ cdef class fmpz_mod_mpoly_ctx(flint_mpoly_context): cdef class fmpz_mod_mpoly(flint_mpoly): """ The *fmpz_mod_mpoly* type represents sparse multivariate polynomials over - the integers modulo `n`, for large `n`. + the integers modulo ``n``, for large ``n``. """ def __cinit__(self): @@ -371,8 +378,8 @@ cdef class fmpz_mod_mpoly(flint_mpoly): def __getitem__(self, x): """ - Return the coefficient of the term with the exponent vector `x`. - Always returns a value, missing keys will return `0`. + Return the coefficient of the term with the exponent vector ``x``. + Always returns a value, missing keys will return ``0``. Negative exponents are made positive. >>> from flint import Ordering @@ -397,7 +404,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): def __setitem__(self, x, y): """ - Set the coefficient of the term with the exponent vector `x` to `y`. + Set the coefficient of the term with the exponent vector ``x`` to ``y``. Will always set a value, missing keys will create a new term. Negative exponents are made positive. @@ -406,7 +413,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): >>> p = ctx.from_dict({(0, 1): 2, (1, 1): 3}) >>> p[1, 1] = 20 >>> p - 20*x0*x1 + 2*x1 + 9*x0*x1 + 2*x1 """ cdef: @@ -583,7 +590,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): res = [] for i in range(len(self)): fmpz_mod_mpoly_get_term_exp_fmpz(vec.double_indirect, self.val, i, self.ctx.val) - res.append(vec.to_tuple()) + res.append(tuple(vec)) return res @@ -725,7 +732,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): def coefficient(self, slong i): """ - Return the coefficient at index `i`. + Return the coefficient at index ``i``. >>> from flint import Ordering >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') @@ -743,7 +750,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): def monomial(self, slong i): """ - Return the exponent vector at index `i` as a tuple. + Return the exponent vector at index ``i`` as a tuple. >>> from flint import Ordering >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') @@ -758,7 +765,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): raise IndexError("term index out of range") res = fmpz_vec(nvars, double_indirect=True) fmpz_mod_mpoly_get_term_exp_fmpz(res.double_indirect, self.val, i, self.ctx.val) - return res.to_tuple() + return tuple(res) def degrees(self): """ @@ -775,7 +782,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): res = fmpz_vec(nvars, double_indirect=True) fmpz_mod_mpoly_degrees_fmpz(res.double_indirect, self.val, self.ctx.val) - return res.to_tuple() + return tuple(res) def total_degree(self): """ @@ -829,7 +836,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): >>> f = ctx.from_dict({(1, 1): 4, (0, 0): 1}) >>> g = ctx.from_dict({(0, 1): 2, (1, 0): 2}) >>> (f * g).gcd(f) - 4*x0*x1 + 1 + x0*x1 + 3 """ cdef fmpz_mod_mpoly res if not typecheck(other, fmpz_mod_mpoly): @@ -842,6 +849,72 @@ cdef class fmpz_mod_mpoly(flint_mpoly): fmpz_mod_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res + def term_content(self): + """ + Return the GCD of the terms of ``self``. If ``self`` is zero, then the result will + be zero, otherwise it will be a monomial with positive coefficient. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = 3 * x0**2 * x1 + 6 * x0 * x1 + >>> f.term_content() + x0*x1 + """ + + cdef fmpz_mod_mpoly res = create_fmpz_mod_mpoly(self.ctx) + fmpz_mod_mpoly_term_content(res.val, self.val, self.ctx.val) + return res + + def resultant(self, other, var): + """ + Return the resultant of ``self`` and ``other`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = x0**2 * x1 + x0 * x1 + >>> g = x0 + x1 + >>> f.resultant(g, 'x1') + x0^3 + x0^2 + """ + cdef: + fmpz_mod_mpoly res + slong i + + if not typecheck(other, fmpz_mod_mpoly): + raise TypeError("argument must be a fmpz_mod_mpoly") + elif (self).ctx is not (other).ctx: + raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + + i = self.ctx.variable_to_index(var) + res = create_fmpz_mod_mpoly(self.ctx) + if not fmpz_mod_mpoly_resultant(res.val, self.val, (other).val, i, self.ctx.val): + raise RuntimeError(f"failed to compute resultant with respect to {var}") + return res + + def discriminant(self, var): + """ + Return the discriminant of ``self`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = (x0 + x1)**2 + 1 + >>> f.discriminant('x1') + 7 + + """ + cdef: + fmpz_mod_mpoly res + slong i + + i = self.ctx.variable_to_index(var) + res = create_fmpz_mod_mpoly(self.ctx) + if not fmpz_mod_mpoly_discriminant(res.val, self.val, i, self.ctx.val): + raise RuntimeError(f"failed to compute discriminant with respect to {var}") + return res + def sqrt(self): """ Return the square root of self. @@ -875,9 +948,9 @@ cdef class fmpz_mod_mpoly(flint_mpoly): >>> p1 = Zm("2*x + 4", ctx) >>> p2 = Zm("3*x*z + 3*x + 3*z + 3", ctx) >>> (p1 * p2).factor() - (6, [(z + 1, 1), (x + 2, 1), (x + 1, 1)]) + (6, [(z + 1, 1), (x + 1, 1), (x + 2, 1)]) >>> (p2 * p1 * p2).factor() - (18, [(z + 1, 2), (x + 2, 1), (x + 1, 2)]) + (7, [(z + 1, 2), (x + 2, 1), (x + 1, 2)]) """ cdef: fmpz_mod_mpoly_factor_t fac @@ -920,7 +993,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): >>> (p1 * p2).factor_squarefree() (6, [(y + 1, 1), (x^2 + 3*x + 2, 1)]) >>> (p1 * p2 * p1).factor_squarefree() - (12, [(y + 1, 1), (x + 1, 1), (x + 2, 2)]) + (1, [(y + 1, 1), (x + 1, 1), (x + 2, 2)]) """ cdef: fmpz_mod_mpoly_factor_t fac @@ -1003,6 +1076,151 @@ cdef class fmpz_mod_mpoly(flint_mpoly): fmpz_mod_mpoly_derivative(res.val, self.val, i, self.ctx.val) return res + def inflate(self, N: list[int]) -> fmpz_mod_mpoly: + """ + Compute the inflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^N)``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x + y + 1 + >>> f.inflate([2, 3]) + x^2 + y^3 + 1 + """ + + cdef nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + elif any(n < 0 for n in N): + raise ValueError("all inflate strides must be non-negative") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpz_mod_mpoly res = create_fmpz_mod_mpoly(self.ctx) + + fmpz_mod_mpoly_inflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflate(self, N: list[int]) -> fmpz_mod_mpoly: + """ + Compute the deflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^(1/N))``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> f.deflate([2, 3]) + x + y + 1 + """ + cdef slong nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpz_mod_mpoly res = create_fmpz_mod_mpoly(self.ctx) + + fmpz_mod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflation(self) -> tuple[fmpz_mod_mpoly, list[int]]: + """ + Compute the deflation of ``self``, that is ``p(X^(1/N))`` for maximal + N. Returns ``q, N`` such that ``self == q.inflate(N)``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**2 * y**2 + x * y**2 + >>> q, N = f.deflation() + >>> q, N + (x^2*y + x*y, [1, 2]) + >>> q.inflate(N) == f + True + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + fmpz_mod_mpoly res = create_fmpz_mod_mpoly(self.ctx) + + fmpz_mod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + + for i in range(nvars): + stride[i] = shift[i].gcd(stride[i]) + shift[i] = 0 + + fmpz_mod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride) + + def deflation_monom(self) -> tuple[fmpz_mod_mpoly, list[int], fmpz_mod_mpoly]: + """ + Compute the exponent vector ``N`` and monomial ``m`` such that ``p(X^(1/N)) + = m * q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned monomial allows the undo-ing of the deflation. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> fd, N, m = f.deflation_monom() + >>> fd, N, m + (x + y + 1, [2, 3], x*y) + >>> m * fd.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_mod_mpoly res = create_fmpz_mod_mpoly(self.ctx) + fmpz_mod_mpoly monom = create_fmpz_mod_mpoly(self.ctx) + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + fmpz_mod_mpoly_push_term_ui_ffmpz(monom.val, 1, fmpz_vec(shift).val, self.ctx.val) + fmpz_mod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride), monom + + def deflation_index(self) -> tuple[list[int], list[int]]: + """ + Compute the exponent vectors ``N`` and ``I`` such that ``p(X^(1/N)) = X^I * + q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned exponent vector ``I`` is the shift that was applied to the + exponents. It is the exponent vector of the monomial returned by + ``deflation_monom``. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> N, I = f.deflation_index() + >>> N, I + ([2, 3], [1, 1]) + >>> f_deflated = f.deflate(N) + >>> f_deflated + x + y + 1 + >>> m = ctx.term(exp_vec=I) + >>> m + x*y + >>> m * f_deflated.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + return list(stride), list(shift) + cdef class fmpz_mod_mpoly_vec: """ @@ -1078,5 +1296,9 @@ cdef class fmpz_mod_mpoly_vec: def __repr__(self): return f"fmpz_mod_mpoly_vec({self}, ctx={self.ctx})" - def to_tuple(self): - return tuple(self[i] for i in range(self.val.length)) + def __iter__(self): + cdef fmpz_mod_mpoly z + for i in range(self.val.length): + z = create_fmpz_mod_mpoly(self.ctx) + fmpz_mod_mpoly_set(z.val, &self.val[i], self.ctx.val) + yield z diff --git a/src/flint/types/fmpz_mod_poly.pyx b/src/flint/types/fmpz_mod_poly.pyx index a28e56be..5204db35 100644 --- a/src/flint/types/fmpz_mod_poly.pyx +++ b/src/flint/types/fmpz_mod_poly.pyx @@ -130,7 +130,7 @@ cdef class fmpz_mod_poly_ctx: """ cdef slong length if not (isinstance(monic, bool) and isinstance(irreducible, bool)): - raise ValueError("Both `monic` and `irreducible` must be of type bool") + raise ValueError("Both 'monic' and 'irreducible' must be of type bool") length = degree + 1 if length <= 0: @@ -292,7 +292,7 @@ cdef class fmpz_mod_poly_ctx: for i in range(n): check = self.mod.set_any_as_fmpz_mod(&xs.val[i], vals[i]) if check is NotImplemented: - raise ValueError(f"Unable to cast {vals[i]} to an `fmpz_mod`") + raise ValueError(f"Unable to cast {vals[i]} to an 'fmpz_mod'") res = self.new_ctype_poly() fmpz_mod_poly_minpoly(res.val, xs.val, n, self.mod.val) @@ -482,7 +482,7 @@ cdef class fmpz_mod_poly(flint_poly): # Case when right is not fmpz_mod_poly, try to convert to fmpz right = self.ctx.any_as_fmpz_mod_poly(right) if right is NotImplemented: - raise TypeError(f"Cannot convert {right} to `fmpz_mod_poly` type.") + raise TypeError(f"Cannot convert {right} to 'fmpz_mod_poly' type.") if right == 0: raise ZeroDivisionError("Cannot divide by zero") @@ -765,7 +765,7 @@ cdef class fmpz_mod_poly(flint_poly): for i in range(n): check = self.ctx.mod.set_any_as_fmpz_mod(&xs.val[i], vals[i]) if check is NotImplemented: - raise ValueError(f"Unable to cast {vals[i]} to an `fmpz_mod`") + raise ValueError(f"Unable to cast {vals[i]} to an 'fmpz_mod'") # Call for multipoint eval, iterative horner will be used # for small arrays (len < 32) and a fast eval for larger ones diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index fbb88373..719dff41 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -23,8 +23,11 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_clear, fmpz_mpoly_compose_fmpz_mpoly, fmpz_mpoly_ctx_init, + fmpz_mpoly_deflate, + fmpz_mpoly_deflation, fmpz_mpoly_degrees_fmpz, fmpz_mpoly_derivative, + fmpz_mpoly_discriminant, fmpz_mpoly_div, fmpz_mpoly_divides, fmpz_mpoly_divrem, @@ -38,6 +41,7 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_get_str_pretty, fmpz_mpoly_get_term_coeff_fmpz, fmpz_mpoly_get_term_exp_fmpz, + fmpz_mpoly_inflate, fmpz_mpoly_integral, fmpz_mpoly_is_one, fmpz_mpoly_is_zero, @@ -46,7 +50,11 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_neg, fmpz_mpoly_pow_fmpz, fmpz_mpoly_push_term_fmpz_ffmpz, + fmpz_mpoly_push_term_ui_ffmpz, fmpz_mpoly_reduction_primitive_part, + fmpz_mpoly_resultant, + fmpz_mpoly_scalar_divexact_fmpz, + fmpz_mpoly_scalar_divides_fmpz, fmpz_mpoly_scalar_mul_fmpz, fmpz_mpoly_set, fmpz_mpoly_set_coeff_fmpz_fmpz, @@ -57,6 +65,7 @@ from flint.flintlib.functions.fmpz_mpoly cimport ( fmpz_mpoly_sqrt_heap, fmpz_mpoly_sub, fmpz_mpoly_sub_fmpz, + fmpz_mpoly_term_content, fmpz_mpoly_total_degree_fmpz, fmpz_mpoly_vec_autoreduction, fmpz_mpoly_vec_autoreduction_groebner, @@ -72,6 +81,7 @@ from flint.flintlib.functions.fmpz_mpoly_factor cimport ( fmpz_mpoly_factor_squarefree, fmpz_mpoly_factor_t, ) +from flint.flintlib.functions.fmpz_vec cimport _fmpz_vec_content from cpython.object cimport Py_EQ, Py_NE cimport libc.stdlib @@ -87,7 +97,7 @@ cdef class fmpz_mpoly_ctx(flint_mpoly_context): :param ordering: The term order for the ring :param names: A tuple containing the names of the variables of the ring. - Do not construct one of these directly, use `fmpz_mpoly_ctx.get_context`. + Do not construct one of these directly, use ``fmpz_mpoly_ctx.get_context``. """ _ctx_cache = _fmpz_mpoly_ctx_cache @@ -134,7 +144,7 @@ cdef class fmpz_mpoly_ctx(flint_mpoly_context): def gen(self, slong i): """ - Return the `i`th generator of the polynomial ring + Return the ``i`` th generator of the polynomial ring >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(3, Ordering.degrevlex, 'z') @@ -277,8 +287,8 @@ cdef class fmpz_mpoly(flint_mpoly): def __getitem__(self, x): """ - Return the coefficient of the term with the exponent vector `x`. - Always returns a value, missing keys will return `0`. + Return the coefficient of the term with the exponent vector ``x``. + Always returns a value, missing keys will return ``0``. Negative exponents are made positive. >>> from flint import Ordering @@ -303,7 +313,7 @@ cdef class fmpz_mpoly(flint_mpoly): def __setitem__(self, x, y): """ - Set the coefficient of the term with the exponent vector `x` to `y`. + Set the coefficient of the term with the exponent vector ``x`` to ``y``. Will always set a value, missing keys will create a new term. Negative exponents are made positive. @@ -395,6 +405,22 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_div(quotient.val, self.val, other.val, self.ctx.val) return quotient + cdef _truediv_scalar_(self, arg): + cdef fmpz_mpoly quotient, + cdef fmpz other = arg + quotient = create_fmpz_mpoly(self.ctx) + if fmpz_mpoly_scalar_divides_fmpz(quotient.val, self.val, other.val, self.ctx.val): + return quotient + else: + raise DomainError("fmpz_mpoly division is not exact") + + cdef _divexact_scalar_(self, arg): + cdef fmpz_mpoly quotient, + cdef fmpz other = arg + quotient = create_fmpz_mpoly(self.ctx) + fmpz_mpoly_scalar_divexact_fmpz(quotient.val, self.val, other.val, self.ctx.val) + return quotient + cdef _truediv_mpoly_(self, arg): cdef fmpz_mpoly quotient, other = arg quotient = create_fmpz_mpoly(self.ctx) @@ -490,7 +516,7 @@ cdef class fmpz_mpoly(flint_mpoly): res = [] for i in range(len(self)): fmpz_mpoly_get_term_exp_fmpz(vec.double_indirect, self.val, i, self.ctx.val) - res.append(vec.to_tuple()) + res.append(tuple(vec)) return res @@ -635,7 +661,7 @@ cdef class fmpz_mpoly(flint_mpoly): def coefficient(self, slong i): """ - Return the coefficient at index `i`. + Return the coefficient at index ``i``. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') @@ -653,7 +679,7 @@ cdef class fmpz_mpoly(flint_mpoly): def monomial(self, slong i): """ - Return the exponent vector at index `i` as a tuple. + Return the exponent vector at index ``i`` as a tuple. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') @@ -668,7 +694,7 @@ cdef class fmpz_mpoly(flint_mpoly): raise IndexError("term index out of range") res = fmpz_vec(nvars, double_indirect=True) fmpz_mpoly_get_term_exp_fmpz(res.double_indirect, self.val, i, self.ctx.val) - return res.to_tuple() + return tuple(res) def degrees(self): """ @@ -685,7 +711,7 @@ cdef class fmpz_mpoly(flint_mpoly): res = fmpz_vec(nvars, double_indirect=True) fmpz_mpoly_degrees_fmpz(res.double_indirect, self.val, self.ctx.val) - return res.to_tuple() + return tuple(res) def total_degree(self): """ @@ -750,10 +776,104 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res + def content(self): + """ + Return the GCD of the coefficients of ``self``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = 3 * x0**2 * x1 + 6 * x0 * x1 + >>> f.content() + 3 + """ + cdef fmpz res = fmpz() + _fmpz_vec_content(res.val, self.val.coeffs, self.val.length) + return res + + def term_content(self): + """ + Return the GCD of the terms of ``self``. If ``self`` is zero, then the result will + be zero, otherwise it will be a monomial with positive coefficient. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = 3 * x0**2 * x1 + 6 * x0 * x1 + >>> f.term_content() + 3*x0*x1 + """ + cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + fmpz_mpoly_term_content(res.val, self.val, self.ctx.val) + return res + + def resultant(self, other, var): + """ + Return the resultant of ``self`` and ``other`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = x0**2 * x1 + x0 * x1 + >>> g = x0 + x1 + >>> f.resultant(g, 'x1') + x0^3 + x0^2 + """ + cdef: + fmpz_mpoly res + slong i + + if not typecheck(other, fmpz_mpoly): + raise TypeError("argument must be a fmpz_mpoly") + elif (self).ctx is not (other).ctx: + raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + + i = self.ctx.variable_to_index(var) + res = create_fmpz_mpoly(self.ctx) + if not fmpz_mpoly_resultant(res.val, self.val, (other).val, i, self.ctx.val): + raise RuntimeError(f"failed to compute resultant with respect to {var}") + return res + + def discriminant(self, var): + """ + Return the discriminant of ``self`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x0, x1 = ctx.gens() + >>> f = (x0 + x1)**2 + 1 + >>> f.discriminant('x1') + -4 + + """ + cdef: + fmpz_mpoly res + slong i + + i = self.ctx.variable_to_index(var) + res = create_fmpz_mpoly(self.ctx) + if not fmpz_mpoly_discriminant(res.val, self.val, i, self.ctx.val): + raise RuntimeError(f"failed to compute discriminant with respect to {var}") + return res + + def primitive(self): + """ + Return the content and primitive of ``self``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, 'x') + >>> x, y = ctx.gens() + >>> f = 4*x + 2*x*y + >>> f.primitive() + (2, x0*x1 + 2*x0) + """ + cdef fmpz content = self.content() + return content, self._divexact_scalar_(content) + def sqrt(self, assume_perfect_square: bool = False): """ Return the square root of self. - If self is known to be a perfect square provide `assume_perfect_square=True` for a more efficient + If self is known to be a perfect square provide ``assume_perfect_square=True`` for a more efficient result. If self is not a square root the result is not guaranteed to be correct. >>> from flint import Ordering @@ -935,7 +1055,7 @@ cdef class fmpz_mpoly(flint_mpoly): def spoly(self, g): """ - Compute the S-polynomial of `self` and `g`, scaled to an integer polynomial + Compute the S-polynomial of ``self`` and ``g``, scaled to an integer polynomial by computing the LCM of the leading coefficients. >>> from flint import Ordering @@ -957,7 +1077,7 @@ cdef class fmpz_mpoly(flint_mpoly): def reduction_primitive_part(self, vec): """ Compute the the primitive part of the reduction (remainder of multivariate - quasi-division with remainder) with respect to the polynomials `vec`. + quasi-division with remainder) with respect to the polynomials ``vec``. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -979,6 +1099,150 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_reduction_primitive_part(res.val, self.val, (vec).val, self.ctx.val) return res + def inflate(self, N: list[int]) -> fmpz_mpoly: + """ + Compute the inflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^N)``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x + y + 1 + >>> f.inflate([2, 3]) + x^2 + y^3 + 1 + """ + + cdef nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + elif any(n < 0 for n in N): + raise ValueError("all inflate strides must be non-negative") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + + fmpz_mpoly_inflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflate(self, N: list[int]) -> fmpz_mpoly: + """ + Compute the deflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^(1/N))``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> f.deflate([2, 3]) + x + y + 1 + """ + cdef slong nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + + fmpz_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflation(self) -> tuple[fmpz_mpoly, list[int]]: + """ + Compute the deflation of ``self``, that is ``p(X^(1/N))`` for maximal N. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**2 * y**2 + x * y**2 + >>> q, N = f.deflation() + >>> q, N + (x^2*y + x*y, [1, 2]) + >>> q.inflate(N) == f + True + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + + for i in range(nvars): + stride[i] = shift[i].gcd(stride[i]) + shift[i] = 0 + + fmpz_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride) + + def deflation_monom(self) -> tuple[fmpz_mpoly, list[int], fmpz_mpoly]: + """ + Compute the exponent vector ``N`` and monomial ``m`` such that ``p(X^(1/N)) + = m * q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned monomial allows the undo-ing of the deflation. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> fd, N, m = f.deflation_monom() + >>> fd, N, m + (x + y + 1, [2, 3], x*y) + >>> m * fd.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + fmpz_mpoly monom = create_fmpz_mpoly(self.ctx) + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + fmpz_mpoly_push_term_ui_ffmpz(monom.val, 1, fmpz_vec(shift).val, self.ctx.val) + fmpz_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride), monom + + def deflation_index(self) -> tuple[list[int], list[int]]: + """ + Compute the exponent vectors ``N`` and ``I`` such that ``p(X^(1/N)) = X^I * + q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned exponent vector ``I`` is the shift that was applied to the + exponents. It is the exponent vector of the monomial returned by + ``deflation_monom``. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> N, I = f.deflation_index() + >>> N, I + ([2, 3], [1, 1]) + >>> f_deflated = f.deflate(N) + >>> f_deflated + x + y + 1 + >>> m = ctx.term(exp_vec=I) + >>> m + x*y + >>> m * f_deflated.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + fmpz_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + return list(stride), list(shift) + cdef class fmpz_mpoly_vec: """ @@ -1069,13 +1333,17 @@ cdef class fmpz_mpoly_vec: else: return NotImplemented - def to_tuple(self): - return tuple(self[i] for i in range(self.val.length)) + def __iter__(self): + cdef fmpz_mpoly z + for i in range(self.val.length): + z = create_fmpz_mpoly(self.ctx) + fmpz_mpoly_set(z.val, fmpz_mpoly_vec_entry(self.val, i), self.ctx.val) + yield z def is_groebner(self, other=None) -> bool: """ - Check if self is a Gröbner basis. If `other` is not None then check if self - is a Gröbner basis for `other`. + Check if self is a Gröbner basis. If ``other`` is not None then check if self + is a Gröbner basis for ``other``. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -1127,9 +1395,9 @@ cdef class fmpz_mpoly_vec: def autoreduction(self, groebner=False) -> fmpz_mpoly_vec: """ - Compute the autoreduction of `self`. If `groebner` is True and `self` is a - Gröbner basis, compute the reduced reduced Gröbner basis of `self`, throws an - `RuntimeError` otherwise. + Compute the autoreduction of ``self``. If ``groebner`` is True and ``self`` is a + Gröbner basis, compute the reduced reduced Gröbner basis of ``self``, throws an + ``RuntimeError`` otherwise. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -1152,7 +1420,7 @@ cdef class fmpz_mpoly_vec: if groebner: if not self.is_groebner(): raise RuntimeError( - "reduced Gröbner basis construction requires that `self` is a " + "reduced Gröbner basis construction requires that ``self`` is a " "Gröbner basis." ) fmpz_mpoly_vec_autoreduction_groebner(h.val, self.val, self.ctx.val) @@ -1163,21 +1431,21 @@ cdef class fmpz_mpoly_vec: def buchberger_naive(self, limits=None): """ - Compute the Gröbner basis of `self` using a naive implementation of + Compute the Gröbner basis of ``self`` using a naive implementation of Buchberger’s algorithm. - Provide `limits` in the form of a tuple of `(ideal_len_limit, poly_len_limit, - poly_bits_limit)` to halt execution if the length of the ideal basis set exceeds - `ideal_len_limit`, the length of any polynomial exceeds `poly_len_limit`, or the - size of the coefficients of any polynomial exceeds `poly_bits_limit`. + Provide ``limits`` in the form of a tuple of ``(ideal_len_limit, poly_len_limit, + poly_bits_limit)`` to halt execution if the length of the ideal basis set exceeds + ``ideal_len_limit``, the length of any polynomial exceeds ``poly_len_limit``, or the + size of the coefficients of any polynomial exceeds ``poly_bits_limit``. - If limits is provided return a tuple of `(result, success)`. If `success` is - False then `result` is a valid basis for `self`, but it may not be a Gröbner + If limits is provided return a tuple of ``(result, success)``. If ``success`` is + False then ``result`` is a valid basis for ``self``, but it may not be a Gröbner basis. NOTE: This function is exposed only for convenience, it is a naive implementation and does not compute a reduced basis. To construct a reduced - basis use `autoreduce`. + basis use ``autoreduce``. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) diff --git a/src/flint/types/fmpz_poly.pyx b/src/flint/types/fmpz_poly.pyx index 7a1c33c3..6b080333 100644 --- a/src/flint/types/fmpz_poly.pyx +++ b/src/flint/types/fmpz_poly.pyx @@ -1,6 +1,8 @@ from cpython.list cimport PyList_GET_SIZE from cpython.long cimport PyLong_Check +cimport libc.stdlib + from flint.flint_base.flint_context cimport getprec from flint.flint_base.flint_base cimport flint_poly from flint.utils.typecheck cimport typecheck @@ -14,7 +16,6 @@ from flint.types.acb cimport acb from flint.types.arb cimport any_as_arb_or_notimplemented from flint.types.arb cimport arb from flint.types.acb cimport any_as_acb_or_notimplemented -cimport libc.stdlib from flint.flintlib.functions.fmpz cimport fmpz_init, fmpz_clear, fmpz_set from flint.flintlib.functions.fmpz cimport fmpz_is_zero, fmpz_is_one, fmpz_equal_si, fmpz_equal from flint.flintlib.functions.acb_modular cimport * @@ -26,6 +27,7 @@ from flint.flintlib.types.arith cimport arith_chebyshev_t_polynomial, arith_cheb from flint.flintlib.functions.acb cimport * from flint.flintlib.functions.arb_poly cimport * from flint.flintlib.functions.arb_fmpz_poly cimport * +from flint.flintlib.functions.fmpz_vec cimport _fmpz_vec_content from flint.utils.flint_exceptions import DomainError @@ -593,18 +595,98 @@ cdef class fmpz_poly(flint_poly): else: raise DomainError(f"Cannot compute square root of {self}") - def deflation(self): - cdef fmpz_poly v + def inflate(self, n: int) -> fmpz_poly: + """ + Compute the inflation of ``self`` for a provided ``n``, that is return ``q`` + such that ``q(x) = p(x^n)``. + + >>> f = fmpz_poly([1, 1]) + >>> f.inflate(2) + x^2 + 1 + """ + cdef fmpz_poly res = fmpz_poly() + fmpz_poly_inflate(res.val, self.val, n) + return res + + def deflate(self, n: int) -> fmpz_poly: + """ + Compute the deflation of ``self`` for a provided ``n``, that is return ``q`` + such that ``q(x) = p(x^(1/n))``. + + >>> f = fmpz_poly([1, 0, 1]) + >>> f.deflate(2) + x + 1 + """ + cdef fmpz_poly res = fmpz_poly() + if n > 0: + fmpz_poly_deflate(res.val, self.val, n) + return res + else: + raise ValueError("deflate requires n > 0") + + def deflation(self) -> tuple[fmpz_poly, int]: + """ + Compute the deflation of ``self``, that is ``p(x^(1/n))`` for maximal + n. returns ``q, n`` such that ``self == q.inflate(n)``. + + >>> f = fmpz_poly([1, 0, 1]) + >>> q, n = f.deflation() + >>> q, n + (x + 1, 2) + >>> q.inflate(n) == f + True + """ cdef ulong n if fmpz_poly_is_zero(self.val): return self, 1 - n = arb_fmpz_poly_deflation(self.val) - if n == 1: - return self, int(n) - else: - v = fmpz_poly() - arb_fmpz_poly_deflate(v.val, self.val, n) - return v, int(n) + n = fmpz_poly_deflation(self.val) + return self if n <= 1 else self.deflate(n), int(n) + + def deflation_monom(self) -> tuple[fmpz_poly, int, fmpz_poly]: + """ + Compute the exponent ``n`` and monomial ``m`` such that ``p(x^(1/n)) + = m * q(x^n)`` for maximal n. Importantly the deflation itself is not computed + here. The returned monomial allows the undo-ing of the deflation. + + >>> f = fmpz_poly([1, 0, 1]) + >>> f.deflation_monom() + (x^2 + 1, 1, x) + """ + n, m = self.deflation_index() + + cdef fmpz_poly monom = fmpz_poly.__new__(fmpz_poly) + cdef fmpz_poly res = fmpz_poly.__new__(fmpz_poly) + + fmpz_poly_set_coeff_ui(monom.val, m, 1) + fmpz_poly_deflate(res.val, self.val, n) + + return res, n, monom + + def deflation_index(self) -> tuple[int, int]: + """ + Compute the exponent ``n`` and ``i`` such that ``p(x^(1/n)) = x^i * + q(x^n)`` for maximal ``n``. Importantly the deflation itself is not computed + here. The returned exponent ``i`` is the shift that was applied to the + exponents. It is the exponent of the monomial returned by + ``deflation_monom``. + + >>> f = fmpz_poly([1, 0, 1]) + >>> f.deflation_index() + (1, 1) + """ + cdef fmpz_poly res = fmpz_poly.__new__(fmpz_poly) + cdef slong length = fmpz_poly_length(self.val) + + if length <= 0: + return self, 0, fmpz_poly([1]) + + # Find the smallest non-zero power, that is the gcd of the monomials + for i in range(1, length + 1): + if not fmpz_is_zero(&self.val.coeffs[length - i]): + break + + fmpz_poly_shift_right(res.val, self.val, i) + return int(fmpz_poly_deflation(res.val)), int(i) def is_cyclotomic(self): cdef long * phi @@ -648,3 +730,14 @@ cdef class fmpz_poly(flint_poly): return int(i) libc.stdlib.free(phi) return 0 + + def content(self): + """ + Return the GCD of the coefficients of ``self``. + + >>> fmpz_poly([3, 6, 0]).content() + 3 + """ + cdef fmpz res = fmpz() + _fmpz_vec_content(res.val, self.val.coeffs, self.val.length) + return res diff --git a/src/flint/types/fmpz_vec.pyx b/src/flint/types/fmpz_vec.pyx index 70854e54..0b76f01c 100644 --- a/src/flint/types/fmpz_vec.pyx +++ b/src/flint/types/fmpz_vec.pyx @@ -66,6 +66,13 @@ cdef class fmpz_vec: def __repr__(self): return self.repr() + def __iter__(self): + cdef fmpz z + for i in range(self.length): + z = fmpz.__new__(fmpz) + fmpz_init_set(z.val, &self.val[i]) + yield z + def str(self, *args): s = [None] * self.length for i in range(self.length): @@ -76,6 +83,3 @@ cdef class fmpz_vec: def repr(self, *args): return f"fmpz_vec({self.str(*args)}, {self.length})" - - def to_tuple(self): - return tuple(self[i] for i in range(self.length)) diff --git a/src/flint/types/fq_default_poly.pyx b/src/flint/types/fq_default_poly.pyx index ec2dfe83..8b323b57 100644 --- a/src/flint/types/fq_default_poly.pyx +++ b/src/flint/types/fq_default_poly.pyx @@ -188,7 +188,7 @@ cdef class fq_default_poly_ctx: """ cdef slong length if not (isinstance(monic, bool) and isinstance(irreducible, bool) and isinstance(not_zero, bool)): - raise TypeError("All of `not_zero`, `monic` and `irreducible` must be of type bool") + raise TypeError("All of 'not_zero', 'monic' and 'irreducible' must be of type bool") length = degree + 1 if length <= 0: @@ -748,7 +748,7 @@ cdef class fq_default_poly(flint_poly): # Case when right is not fq_default_poly, try to convert to fmpz other = self.ctx.any_as_fq_default_poly(other) if other is NotImplemented: - raise TypeError(f"Cannot convert {other} to `fq_default_poly` type.") + raise TypeError(f"Cannot convert {other} to 'fq_default_poly' type.") if other.is_zero(): raise ZeroDivisionError("Cannot divide by zero") diff --git a/src/flint/types/nmod_mat.pyx b/src/flint/types/nmod_mat.pyx index 60f12f5c..4ba93a9c 100644 --- a/src/flint/types/nmod_mat.pyx +++ b/src/flint/types/nmod_mat.pyx @@ -12,10 +12,8 @@ from flint.flintlib.functions.fmpz_mat cimport fmpz_mat_get_nmod_mat from flint.flintlib.types.nmod cimport nmod_mat_struct -from flint.flintlib.types.nmod cimport ( - nmod_mat_is_square, - nmod_mat_entry, -) +from flint.flintlib.types.nmod cimport nmod_mat_entry +from flint.flintlib.types.undocumented cimport nmod_mat_is_square from flint.flintlib.functions.nmod_mat cimport ( nmod_mat_init, diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index 8af80b47..d21b233a 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -23,10 +23,11 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_add_ui, nmod_mpoly_clear, nmod_mpoly_compose_nmod_mpoly, - nmod_mpoly_ctx_modulus, nmod_mpoly_ctx_init, + nmod_mpoly_ctx_modulus, nmod_mpoly_degrees_fmpz, nmod_mpoly_derivative, + nmod_mpoly_discriminant, nmod_mpoly_div, nmod_mpoly_divides, nmod_mpoly_divrem, @@ -47,16 +48,18 @@ from flint.flintlib.functions.nmod_mpoly cimport ( nmod_mpoly_neg, nmod_mpoly_pow_fmpz, nmod_mpoly_push_term_ui_ffmpz, + nmod_mpoly_resultant, nmod_mpoly_scalar_mul_ui, nmod_mpoly_set, nmod_mpoly_set_coeff_ui_fmpz, - nmod_mpoly_set_ui, nmod_mpoly_set_str_pretty, + nmod_mpoly_set_ui, nmod_mpoly_sort_terms, + nmod_mpoly_sqrt, nmod_mpoly_sub, nmod_mpoly_sub_ui, + nmod_mpoly_term_content, nmod_mpoly_total_degree_fmpz, - nmod_mpoly_sqrt, ) from flint.flintlib.functions.nmod_mpoly_factor cimport ( nmod_mpoly_factor, @@ -66,6 +69,11 @@ from flint.flintlib.functions.nmod_mpoly_factor cimport ( nmod_mpoly_factor_t, ) from flint.flintlib.functions.ulong_extras cimport n_is_prime +from flint.flintlib.types.undocumented cimport ( + nmod_mpoly_deflate, + nmod_mpoly_deflation, + nmod_mpoly_inflate, +) from cpython.object cimport Py_EQ, Py_NE cimport libc.stdlib @@ -81,7 +89,7 @@ cdef class nmod_mpoly_ctx(flint_mpoly_context): :param ordering: The term order for the ring :param names: A tuple containing the names of the variables of the ring. - Do not construct one of these directly, use `nmod_mpoly_ctx.get_context`. + Do not construct one of these directly, use ``nmod_mpoly_ctx.get_context``. """ _ctx_cache = _nmod_mpoly_ctx_cache @@ -107,17 +115,17 @@ cdef class nmod_mpoly_ctx(flint_mpoly_context): Create a key for the context cache via the number of variables, the ordering, the modulus, and either a variable name string, or a tuple of variable names. """ - # A type hint of `ordering: Ordering` results in the error "TypeError: an integer is required" if a Ordering + # A type hint of ``ordering: Ordering`` results in the error "TypeError: an integer is required" if a Ordering # object is not provided. This is pretty obtuse so we check its type ourselves if not isinstance(ordering, Ordering): - raise TypeError(f"`ordering` ('{ordering}') is not an instance of flint.Ordering") + raise TypeError(f"'ordering' ('{ordering}') is not an instance of flint.Ordering") if nametup is not None: key = nvars, ordering, nametup, modulus elif nametup is None and names is not None: key = nvars, ordering, cls.create_variable_names(nvars, names), modulus else: - raise ValueError("must provide either `names` or `nametup`") + raise ValueError("must provide either 'names' or 'nametup'") return key def any_as_scalar(self, other): @@ -186,20 +194,20 @@ cdef class nmod_mpoly_ctx(flint_mpoly_context): Return whether the modulus is prime >>> from flint import Ordering - >>> ctx = nmod_mpoly_ctx.get_context(4, Ordering.degrevlex, 2**127, 'z') + >>> ctx = nmod_mpoly_ctx.get_context(4, Ordering.degrevlex, 2**32, 'z') >>> ctx.is_prime() False - >>> ctx = nmod_mpoly_ctx.get_context(4, Ordering.degrevlex, 2**127 - 1, 'z') + >>> ctx = nmod_mpoly_ctx.get_context(4, Ordering.degrevlex, 2**32 - 17, 'z') >>> ctx.is_prime() True """ if self.__prime_modulus is None: - self.__prime_modulus = n_is_prime(self.modulus()) + self.__prime_modulus = n_is_prime(self.modulus()) return self.__prime_modulus def gen(self, slong i): """ - Return the `i`th generator of the polynomial ring + Return the ``i`` th generator of the polynomial ring >>> from flint import Ordering >>> ctx = nmod_mpoly_ctx.get_context(3, Ordering.degrevlex, 11, 'z') @@ -347,8 +355,8 @@ cdef class nmod_mpoly(flint_mpoly): def __getitem__(self, x): """ - Return the coefficient of the term with the exponent vector `x`. - Always returns a value, missing keys will return `0`. + Return the coefficient of the term with the exponent vector ``x``. + Always returns a value, missing keys will return ``0``. Negative exponents are made positive. >>> from flint import Ordering @@ -371,7 +379,7 @@ cdef class nmod_mpoly(flint_mpoly): def __setitem__(self, x, y): """ - Set the coefficient of the term with the exponent vector `x` to `y`. + Set the coefficient of the term with the exponent vector ``x`` to ``y``. Will always set a value, missing keys will create a new term. Negative exponents are made positive. @@ -380,7 +388,7 @@ cdef class nmod_mpoly(flint_mpoly): >>> p = ctx.from_dict({(0, 1): 2, (1, 1): 3}) >>> p[1, 1] = 20 >>> p - 20*x0*x1 + 2*x1 + 9*x0*x1 + 2*x1 """ cdef: @@ -536,7 +544,7 @@ cdef class nmod_mpoly(flint_mpoly): args = [self.ctx.any_as_scalar(x) for x in args] cdef: - # Using sizeof(ulong) here breaks on 64 windows machines because of the `ctypedef unsigned long ulong` in + # Using sizeof(ulong) here breaks on 64 windows machines because of the ``ctypedef unsigned long ulong`` in # flintlib/flint.pxd. Cython will inline this definition and then allocate the wrong amount of memory. ulong *vals = libc.stdlib.malloc(nargs * SIZEOF_ULONG) ulong res @@ -570,7 +578,7 @@ cdef class nmod_mpoly(flint_mpoly): res = [] for i in range(len(self)): nmod_mpoly_get_term_exp_fmpz(vec.double_indirect, self.val, i, self.ctx.val) - res.append(vec.to_tuple()) + res.append(tuple(vec)) return res @@ -702,7 +710,7 @@ cdef class nmod_mpoly(flint_mpoly): def coefficient(self, slong i): """ - Return the coefficient at index `i`. + Return the coefficient at index ``i``. >>> from flint import Ordering >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') @@ -717,7 +725,7 @@ cdef class nmod_mpoly(flint_mpoly): def monomial(self, slong i): """ - Return the exponent vector at index `i` as a tuple. + Return the exponent vector at index ``i`` as a tuple. >>> from flint import Ordering >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') @@ -732,7 +740,7 @@ cdef class nmod_mpoly(flint_mpoly): raise IndexError("term index out of range") res = fmpz_vec(nvars, double_indirect=True) nmod_mpoly_get_term_exp_fmpz(res.double_indirect, self.val, i, self.ctx.val) - return res.to_tuple() + return tuple(res) def degrees(self): """ @@ -749,7 +757,7 @@ cdef class nmod_mpoly(flint_mpoly): res = fmpz_vec(nvars, double_indirect=True) nmod_mpoly_degrees_fmpz(res.double_indirect, self.val, self.ctx.val) - return res.to_tuple() + return tuple(res) def total_degree(self): """ @@ -803,7 +811,7 @@ cdef class nmod_mpoly(flint_mpoly): >>> f = ctx.from_dict({(1, 1): 4, (0, 0): 1}) >>> g = ctx.from_dict({(0, 1): 2, (1, 0): 2}) >>> (f * g).gcd(f) - 4*x0*x1 + 1 + x0*x1 + 3 """ cdef nmod_mpoly res if not typecheck(other, nmod_mpoly): @@ -816,6 +824,72 @@ cdef class nmod_mpoly(flint_mpoly): nmod_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res + def term_content(self): + """ + Return the GCD of the terms of ``self``. If ``self`` is zero, then the result will + be zero, otherwise it will be a monomial with positive coefficient. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = 3 * x0**2 * x1 + 6 * x0 * x1 + >>> f.term_content() + x0*x1 + """ + + cdef nmod_mpoly res = create_nmod_mpoly(self.ctx) + nmod_mpoly_term_content(res.val, self.val, self.ctx.val) + return res + + def resultant(self, other, var): + """ + Return the resultant of ``self`` and ``other`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = x0**2 * x1 + x0 * x1 + >>> g = x0 + x1 + >>> f.resultant(g, 'x1') + x0^3 + x0^2 + """ + cdef: + nmod_mpoly res + slong i + + if not typecheck(other, nmod_mpoly): + raise TypeError("argument must be a nmod_mpoly") + elif (self).ctx is not (other).ctx: + raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + + i = self.ctx.variable_to_index(var) + res = create_nmod_mpoly(self.ctx) + if not nmod_mpoly_resultant(res.val, self.val, (other).val, i, self.ctx.val): + raise RuntimeError(f"failed to compute resultant with respect to {var}") + return res + + def discriminant(self, var): + """ + Return the discriminant of ``self`` with respect to variable ``var``. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, 'x') + >>> x0, x1 = ctx.gens() + >>> f = (x0 + x1)**2 + 1 + >>> f.discriminant('x1') + 7 + + """ + cdef: + nmod_mpoly res + slong i + + i = self.ctx.variable_to_index(var) + res = create_nmod_mpoly(self.ctx) + if not nmod_mpoly_discriminant(res.val, self.val, i, self.ctx.val): + raise RuntimeError(f"failed to compute discriminant with respect to {var}") + return res + def sqrt(self): """ Return the square root of self. @@ -849,9 +923,9 @@ cdef class nmod_mpoly(flint_mpoly): >>> p1 = Zm("2*x + 4", ctx) >>> p2 = Zm("3*x*z + 3*x + 3*z + 3", ctx) >>> (p1 * p2).factor() - (6, [(z + 1, 1), (x + 2, 1), (x + 1, 1)]) + (6, [(z + 1, 1), (x + 1, 1), (x + 2, 1)]) >>> (p2 * p1 * p2).factor() - (18, [(z + 1, 2), (x + 2, 1), (x + 1, 2)]) + (7, [(z + 1, 2), (x + 2, 1), (x + 1, 2)]) """ cdef: nmod_mpoly_factor_t fac @@ -893,7 +967,7 @@ cdef class nmod_mpoly(flint_mpoly): >>> (p1 * p2).factor_squarefree() (6, [(y + 1, 1), (x^2 + 3*x + 2, 1)]) >>> (p1 * p2 * p1).factor_squarefree() - (12, [(y + 1, 1), (x + 1, 1), (x + 2, 2)]) + (1, [(y + 1, 1), (x + 1, 1), (x + 2, 2)]) """ cdef: nmod_mpoly_factor_t fac @@ -975,6 +1049,150 @@ cdef class nmod_mpoly(flint_mpoly): nmod_mpoly_derivative(res.val, self.val, i, self.ctx.val) return res + def inflate(self, N: list[int]) -> nmod_mpoly: + """ + Compute the inflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^N)``. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x + y + 1 + >>> f.inflate([2, 3]) + x^2 + y^3 + 1 + """ + + cdef nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + elif any(n < 0 for n in N): + raise ValueError("all inflate strides must be non-negative") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + nmod_mpoly res = create_nmod_mpoly(self.ctx) + + nmod_mpoly_inflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflate(self, N: list[int]) -> nmod_mpoly: + """ + Compute the deflation of ``self`` for a provided ``N``, that is return ``q`` + such that ``q(X) = p(X^(1/N))``. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> f.deflate([2, 3]) + x + y + 1 + """ + cdef slong nvars = self.ctx.nvars() + + if nvars != len(N): + raise ValueError(f"expected list of length {nvars}, got {len(N)}") + + cdef: + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(N) + nmod_mpoly res = create_nmod_mpoly(self.ctx) + + nmod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + return res + + def deflation(self) -> tuple[nmod_mpoly, list[int]]: + """ + Compute the deflation of ``self``, that is ``p(X^(1/N))`` for maximal N. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**2 * y**2 + x * y**2 + >>> q, N = f.deflation() + >>> q, N + (x^2*y + x*y, [1, 2]) + >>> q.inflate(N) == f + True + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + nmod_mpoly res = create_nmod_mpoly(self.ctx) + + nmod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + + for i in range(nvars): + stride[i] = shift[i].gcd(stride[i]) + shift[i] = 0 + + nmod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride) + + def deflation_monom(self) -> tuple[nmod_mpoly, list[int], nmod_mpoly]: + """ + Compute the exponent vector ``N`` and monomial ``m`` such that ``p(X^(1/N)) + = m * q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned monomial allows the undo-ing of the deflation. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> fd, N, m = f.deflation_monom() + >>> fd, N, m + (x + y + 1, [2, 3], x*y) + >>> m * fd.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + nmod_mpoly res = create_nmod_mpoly(self.ctx) + nmod_mpoly monom = create_nmod_mpoly(self.ctx) + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + nmod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + nmod_mpoly_push_term_ui_ffmpz(monom.val, 1, fmpz_vec(shift).val, self.ctx.val) + nmod_mpoly_deflate(res.val, self.val, shift.val, stride.val, self.ctx.val) + + return res, list(stride), monom + + def deflation_index(self) -> tuple[list[int], list[int]]: + """ + Compute the exponent vectors ``N`` and ``I`` such that ``p(X^(1/N)) = X^I * + q(X^N)`` for maximal N. Importantly the deflation itself is not computed + here. The returned exponent vector ``I`` is the shift that was applied to the + exponents. It is the exponent vector of the monomial returned by + ``deflation_monom``. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx.get_context(2, Ordering.lex, 11, nametup=('x', 'y')) + >>> x, y = ctx.gens() + >>> f = x**3 * y + x * y**4 + x * y + >>> N, I = f.deflation_index() + >>> N, I + ([2, 3], [1, 1]) + >>> f_deflated = f.deflate(N) + >>> f_deflated + x + y + 1 + >>> m = ctx.term(exp_vec=I) + >>> m + x*y + >>> m * f_deflated.inflate(N) + x^3*y + x*y^4 + x*y + """ + cdef: + slong nvars = self.ctx.nvars() + fmpz_vec shift = fmpz_vec(nvars) + fmpz_vec stride = fmpz_vec(nvars) + + nmod_mpoly_deflation(shift.val, stride.val, self.val, self.ctx.val) + return list(stride), list(shift) + cdef class nmod_mpoly_vec: """ @@ -1050,5 +1268,9 @@ cdef class nmod_mpoly_vec: def __repr__(self): return f"nmod_mpoly_vec({self}, ctx={self.ctx})" - def to_tuple(self): - return tuple(self[i] for i in range(self.val.length)) + def __iter__(self): + cdef nmod_mpoly z + for i in range(self.length): + z = create_nmod_mpoly(self.ctx) + nmod_mpoly_set(z.val, &self.val[i], self.ctx.val) + yield z