From 8613fd306f26a4bc654ce3f789eae8b35f453052 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Tue, 28 Jan 2025 17:24:59 +0100 Subject: [PATCH 1/6] gr: Add is_{zero,one,neg_one} to contexts --- src/flint/types/_gr.pxd | 12 ++++++++++++ src/flint/types/_gr.pyx | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/flint/types/_gr.pxd b/src/flint/types/_gr.pxd index e02738c5..08cf2b88 100644 --- a/src/flint/types/_gr.pxd +++ b/src/flint/types/_gr.pxd @@ -378,6 +378,18 @@ cdef class gr_ctx(flint_ctx): gr_vec_clear(gens, self.ctx_t) return py_gens + @cython.final + cdef inline truth_t _is_zero(self, gr x): + return gr_is_zero(x.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _is_one(self, gr x): + return gr_is_one(x.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _is_neg_one(self, gr x): + return gr_is_neg_one(x.pval, self.ctx_t) + # @cython.final # cdef inline list _gens_recursive(self): # cdef int err diff --git a/src/flint/types/_gr.pyx b/src/flint/types/_gr.pyx index f8e98432..be09614f 100644 --- a/src/flint/types/_gr.pyx +++ b/src/flint/types/_gr.pyx @@ -355,6 +355,48 @@ cdef class gr_ctx(flint_ctx): """ return self._gens() + def is_zero(self, x) -> bool | None: + """ + Returns whether x is equal to the ring element 0. + + >>> from flint.types._gr import gr_real_arb_ctx + >>> ctx = gr_real_arb_ctx.new(10) + >>> ctx.is_zero(ctx(0)) + True + >>> ctx.is_zero(ctx(1)) + False + >>> ctx.is_zero(ctx("[0 +/- 0.1]")) + """ + return truth_to_py(self._is_zero(x)) + + def is_one(self, x) -> bool | None: + """ + Returns whether x is equal to the ring element 1. + + >>> from flint.types._gr import gr_real_arb_ctx + >>> ctx = gr_real_arb_ctx.new(10) + >>> ctx.is_one(ctx(0)) + False + >>> ctx.is_one(ctx(1)) + True + >>> ctx.is_one(ctx("[1 +/- 0.1]")) + """ + return truth_to_py(self._is_one(x)) + + def is_neg_one(self, x) -> bool | None: + """ + Returns whether x is equal to the ring element -1. + + >>> from flint.types._gr import gr_real_arb_ctx + >>> ctx = gr_real_arb_ctx.new(10) + >>> ctx.is_neg_one(ctx(1)) + False + >>> ctx.is_neg_one(ctx(-1)) + True + >>> ctx.is_neg_one(ctx("[-1 +/- 0.1]")) + """ + return truth_to_py(self._is_neg_one(x)) + # def gens_recursive(self) -> list[gr]: # """Return all generators of the domain From e47b2a3dfbba5946a2a8748077b15d943db416e4 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Wed, 29 Jan 2025 11:31:20 +0100 Subject: [PATCH 2/6] Add context methods for neg, add, sub and mul --- src/flint/types/_gr.pxd | 125 +++++++++++++++++++++++++++++++++++++++- src/flint/types/_gr.pyx | 74 ++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/src/flint/types/_gr.pxd b/src/flint/types/_gr.pxd index 08cf2b88..96498a42 100644 --- a/src/flint/types/_gr.pxd +++ b/src/flint/types/_gr.pxd @@ -119,10 +119,16 @@ from flint.flintlib.functions.gr cimport ( gr_neg, gr_add, gr_add_si, + gr_add_other, + gr_other_add, gr_sub, gr_sub_si, + gr_sub_other, + gr_other_sub, gr_mul, gr_mul_si, + gr_mul_other, + gr_other_mul, gr_inv, gr_div, gr_div_si, @@ -390,6 +396,123 @@ cdef class gr_ctx(flint_ctx): cdef inline truth_t _is_neg_one(self, gr x): return gr_is_neg_one(x.pval, self.ctx_t) + @cython.final + cdef inline gr _neg(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_neg(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot negate x in this context") + return res + + @cython.final + cdef inline gr _add(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_add(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot add x and y in this context") + return res + + @cython.final + cdef inline gr _add_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_add_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot add x and y in this context") + return res + + @cython.final + cdef inline gr _other_add(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_add(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot add x and y in this context") + return res + + @cython.final + cdef inline gr _add_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_add_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot add x and y in this context") + return res + + @cython.final + cdef inline gr _sub(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_sub(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot sub x and y in this context") + return res + + @cython.final + cdef inline gr _sub_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_sub_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot sub x and y in this context") + return res + + @cython.final + cdef inline gr _other_sub(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_sub(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot sub x and y in this context") + return res + + @cython.final + cdef inline gr _sub_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_sub_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot sub x and y in this context") + return res + + @cython.final + cdef inline gr _mul(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_mul(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot mul x and y in this context") + return res + + @cython.final + cdef inline gr _mul_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_mul_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot mul x and y in this context") + return res + + @cython.final + cdef inline gr _other_mul(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_mul(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot mul x and y in this context") + return res + + @cython.final + cdef inline gr _mul_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_mul_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot mul x and y in this context") + return res + # @cython.final # cdef inline list _gens_recursive(self): # cdef int err @@ -919,7 +1042,7 @@ cdef class gr_series_ctx(gr_ctx): @cython.no_gc cdef class gr(flint_scalar): cdef gr_ptr pval - cdef gr_ctx ctx + cdef public gr_ctx ctx cdef bint _init @cython.final diff --git a/src/flint/types/_gr.pyx b/src/flint/types/_gr.pyx index be09614f..1641becd 100644 --- a/src/flint/types/_gr.pyx +++ b/src/flint/types/_gr.pyx @@ -397,6 +397,80 @@ cdef class gr_ctx(flint_ctx): """ return truth_to_py(self._is_neg_one(x)) + def neg(self, x): + """ + Returns `-x`. + + >>> from flint.types._gr import gr_complex_acb_ctx, gr_real_arb_ctx + >>> arb = gr_real_arb_ctx.new(53); acb = gr_complex_acb_ctx.new(106) + >>> c = acb("2 + I").sqrt(); c + ([1.4553466902253548081226618397097 +/- 3.48e-32] + [0.3435607497225124641385657439146 +/- 5.23e-32]*I) + >>> arb.neg(c) + [-1.455346690225355 +/- 1.92e-16] + """ + return self._neg(x) + + def add(self, x, y) -> gr: + """ + Returns `x + y` + + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._add(x, y) + if x.ctx == self: + return self._add_other(x, y) + if y.ctx == self: + return self._other_add(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._add_si(x, y) + except OverflowError: + pass + + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._add(self(x), self(y)) + + def sub(self, x, y): + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._sub(x, y) + if x.ctx == self: + return self._sub_other(x, y) + if y.ctx == self: + return self._other_sub(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._sub_si(x, y) + except OverflowError: + pass + + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._sub(self(x), self(y)) + + def mul(self, x, y): + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._mul(x, y) + if x.ctx == self: + return self._mul_other(x, y) + if y.ctx == self: + return self._other_mul(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._mul_si(x, y) + except OverflowError: + pass + + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._mul(self(x), self(y)) + # def gens_recursive(self) -> list[gr]: # """Return all generators of the domain From ebcbb663a171eebe61b6f7c97f673af5924304e9 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Wed, 29 Jan 2025 14:45:37 +0100 Subject: [PATCH 3/6] test: Allow skipping some docstring tests based on flint version --- src/flint/test/test_docstrings.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/flint/test/test_docstrings.py b/src/flint/test/test_docstrings.py index 7a4c0287..88785f8b 100644 --- a/src/flint/test/test_docstrings.py +++ b/src/flint/test/test_docstrings.py @@ -5,7 +5,12 @@ import flint -dunder_test_regex = re.compile(r'^(.*?)__test__\..*?\.(.*) \(line (\d+)\)$') +dunder_test_regex = re.compile(r'^(.*?)__test__\.(.*?\.)(.*) \(line (\d+)\)$') + +test_flint_at_least = { + "flint.types._gr.gr_ctx.gens": 30100, + "flint.types._gr.gr_ctx.neg": 30100, +} def find_doctests(module): @@ -20,9 +25,14 @@ def find_doctests(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) + test.name = groups[0] + groups[2] + test.lineno = int(groups[3]) + + if ( + test_flint_at_least.get("".join(groups[:3]), flint.__FLINT_RELEASE__) + <= flint.__FLINT_RELEASE__ + ): + res.append(test) tests.append((module_info.name, res)) From 7044f118ffba95f4d027abce5c71007e5e484e1d Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 30 Jan 2025 15:37:46 +0100 Subject: [PATCH 4/6] Add more gr_ctx methods --- src/flint/types/_gr.pxd | 500 ++++++++++++++++++++++++++++++++++++++-- src/flint/types/_gr.pyx | 321 +++++++++++++++++++++++++- 2 files changed, 796 insertions(+), 25 deletions(-) diff --git a/src/flint/types/_gr.pxd b/src/flint/types/_gr.pxd index 96498a42..2d097ff3 100644 --- a/src/flint/types/_gr.pxd +++ b/src/flint/types/_gr.pxd @@ -111,8 +111,8 @@ from flint.flintlib.functions.gr cimport ( gr_is_zero, gr_is_one, gr_is_neg_one, - # gr_is_integer, - # gr_is_rational, + gr_is_integer, + gr_is_rational, gr_equal, @@ -129,10 +129,25 @@ from flint.flintlib.functions.gr cimport ( gr_mul_si, gr_mul_other, gr_other_mul, + gr_is_invertible, gr_inv, gr_div, gr_div_si, + gr_div_other, + gr_div_nonunique, + gr_divides, + gr_other_div, + gr_divexact, + gr_divexact_si, + gr_divexact_other, + gr_other_divexact, + gr_euclidean_div, + gr_euclidean_rem, + gr_euclidean_divrem, + gr_pow, gr_pow_si, + gr_pow_other, + gr_other_pow, gr_is_square, gr_sqrt, @@ -155,10 +170,20 @@ from flint.flintlib.functions.gr cimport ( gr_csgn, gr_arg, - # gr_le, - # gr_lt, - # gr_ge, - # gr_gt, + gr_cmp, + gr_cmp_other, + gr_cmpabs, + gr_cmpabs_other, + gr_le, + gr_lt, + gr_ge, + gr_gt, + gr_abs_le, + gr_abs_lt, + gr_abs_ge, + gr_abs_gt, + gr_min, + gr_max, gr_numerator, gr_denominator, @@ -237,7 +262,7 @@ cdef class gr_ctx(flint_ctx): py_val = self.new_gr() err = gr_set_other(py_val.pval, x.pval, x.ctx.ctx_t, self.ctx_t) if err != GR_SUCCESS: - raise self._error(err, "Incorrect conversion") + raise self._error(err, "Cannot convert x to the current context") return py_val @cython.final @@ -396,6 +421,18 @@ cdef class gr_ctx(flint_ctx): cdef inline truth_t _is_neg_one(self, gr x): return gr_is_neg_one(x.pval, self.ctx_t) + @cython.final + cdef inline truth_t _equal(self, gr x, gr y): + return gr_equal(x.pval, y.pval, self.ctx_t) + + # @cython.final + # cdef inline truth_t _is_integer(self, gr x): + # return gr_is_integer(x.pval, self.ctx_t) + # + # @cython.final + # cdef inline truth_t _is_rational(self, gr x): + # return gr_is_rational(x.pval, self.ctx_t) + @cython.final cdef inline gr _neg(self, gr x): cdef int err @@ -513,6 +550,447 @@ cdef class gr_ctx(flint_ctx): raise self._error(err, "Cannot mul x and y in this context") return res + ### + # Division + + @cython.final + cdef inline truth_t _is_invertible(self, gr x): + return gr_is_invertible(x.pval, self.ctx_t) + + @cython.final + cdef inline gr _inv(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_inv(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "x in not invertible in this context") + return res + + @cython.final + cdef inline gr _div(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_div(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot div x and y in this context") + return res + + @cython.final + cdef inline gr _div_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_div_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot div x and y in this context") + return res + + @cython.final + cdef inline gr _other_div(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_div(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot div x and y in this context") + return res + + @cython.final + cdef inline gr _div_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_div_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot div x and y in this context") + return res + + @cython.final + cdef inline gr _divexact(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_divexact(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot divexact x and y in this context") + return res + + @cython.final + cdef inline gr _divexact_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_divexact_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot divexact x and y in this context") + return res + + @cython.final + cdef inline gr _other_divexact(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_divexact(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot divexact x and y in this context") + return res + + @cython.final + cdef inline gr _divexact_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_divexact_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot divexact x and y in this context") + return res + + @cython.final + cdef inline gr _div_nonunique(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_div_nonunique(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "No solution q to x = qy has been found") + return res + + @cython.final + cdef inline truth_t _divides(self, gr x, gr d): + return gr_divides(x.pval, d.pval, self.ctx_t) + + @cython.final + cdef inline gr _euclidean_div(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_euclidean_div(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "No solution q to x = qy has been found") + return res + + @cython.final + cdef inline gr _euclidean_rem(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_euclidean_rem(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "No solution q to x = qy has been found") + return res + + @cython.final + cdef inline tuple[gr, gr] _euclidean_divrem(self, gr x, gr y): + cdef int err + cdef gr rem = self.new_gr() + cdef gr div = self.new_gr() + err = gr_euclidean_divrem(div.pval, rem.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "No solution q to x = qy has been found") + return (div, rem) + + ### + # Powering + + @cython.final + cdef inline gr _pow(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_pow(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _pow_other(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_pow_other(res.pval, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _other_pow(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_other_pow(res.pval, x.pval, x.ctx.ctx_t, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _pow_si(self, gr x, slong y): + cdef int err + cdef gr res = self.new_gr() + err = gr_pow_si(res.pval, x.pval, y, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + ### + # Square roots + + @cython.final + cdef inline truth_t _is_square(self, gr x): + return gr_is_square(x.pval, self.ctx_t) + + @cython.final + cdef inline gr _sqrt(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_sqrt(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _rsqrt(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_rsqrt(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + ### + # Greates Common Divisors + + @cython.final + cdef inline gr _gcd(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_gcd(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _lcm(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_lcm(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + ### + # Factorization + + # @cython.final + # cdef inline tuple[gr, list[gr], list[fmpz]] _factor(self, gr x): + # cdef int err + # cdef gr c = self.new_gr() + # cdef gr_vec_t factors + # gr_vec_init(factors, 0, self.ctx_t) + # cdef gr_vec_t exponents + # gr_vec_init(exponents, 0, self.ctx_t) + # err = gr_factor(c.pval, factors, exponents, x.pval, 0, self.ctx_t) + # if err != GR_SUCCESS: + # raise self._error(err, "Cannot factorize x in this context") + + ### + # Fractions + + @cython.final + cdef inline gr _numerator(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_numerator(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + @cython.final + cdef inline gr _denominator(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_denominator(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot pow x and y in this context") + return res + + ### + # Integer and Complex parts + + @cython.final + cdef inline gr _floor(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_floor(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute floor(x) in this context") + return res + + @cython.final + cdef inline gr _ceil(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_ceil(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute ceil(x) in this context") + return res + + @cython.final + cdef inline gr _trunc(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_trunc(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute trunc(x) in this context") + return res + + @cython.final + cdef inline gr _nint(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_nint(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute nint(x) in this context") + return res + + @cython.final + cdef inline gr _abs(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_abs(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute abs(x) in this context") + return res + + @cython.final + cdef inline gr _conj(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_conj(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute conj(x) in this context") + return res + + @cython.final + cdef inline gr _re(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_re(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute re(x) in this context") + return res + + @cython.final + cdef inline gr _im(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_im(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute im(x) in this context") + return res + + @cython.final + cdef inline gr _sgn(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_sgn(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute sgn(x) in this context") + return res + + @cython.final + cdef inline gr _csgn(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_csgn(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute csgn(x) in this context") + return res + + @cython.final + cdef inline gr _arg(self, gr x): + cdef int err + cdef gr res = self.new_gr() + err = gr_arg(res.pval, x.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute arg(x) in this context") + return res + + ### + # Ordering methods + + @cython.final + cdef inline int _cmp(self, gr x, gr y): + cdef int err + cdef int res + err = gr_cmp(&res, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compare x and y") + return res + + @cython.final + cdef inline int _cmp_other(self, gr x, gr y): + cdef int err + cdef int res + err = gr_cmp_other(&res, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compare x and y") + return res + + @cython.final + cdef inline int _cmpabs(self, gr x, gr y): + cdef int err + cdef int res + err = gr_cmpabs(&res, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compare x and y") + return res + + @cython.final + cdef inline int _cmpabs_other(self, gr x, gr y): + cdef int err + cdef int res + err = gr_cmpabs_other(&res, x.pval, y.pval, y.ctx.ctx_t, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compare x and y") + return res + + @cython.final + cdef inline truth_t _le(self, gr x, gr y): + return gr_le(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _abs_le(self, gr x, gr y): + return gr_abs_le(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _lt(self, gr x, gr y): + return gr_lt(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _abs_lt(self, gr x, gr y): + return gr_abs_lt(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _ge(self, gr x, gr y): + return gr_ge(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _abs_ge(self, gr x, gr y): + return gr_abs_ge(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _gt(self, gr x, gr y): + return gr_gt(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline truth_t _abs_gt(self, gr x, gr y): + return gr_abs_gt(x.pval, y.pval, self.ctx_t) + + @cython.final + cdef inline gr _min(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_min(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute min(x) in this context") + return res + + @cython.final + cdef inline gr _max(self, gr x, gr y): + cdef int err + cdef gr res = self.new_gr() + err = gr_max(res.pval, x.pval, y.pval, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Cannot compute min(x) in this context") + return res + # @cython.final # cdef inline list _gens_recursive(self): # cdef int err @@ -1065,14 +1543,6 @@ cdef class gr(flint_scalar): cdef inline truth_t _is_neg_one(self): return gr_is_neg_one(self.pval, self.ctx.ctx_t) - # @cython.final - # cdef inline truth_t _is_integer(self): - # return gr_is_integer(self.pval, self.ctx.ctx_t) - - # @cython.final - # cdef inline truth_t _is_rational(self): - # return gr_is_rational(self.pval, self.ctx.ctx_t) - @cython.final cdef inline gr _neg(self): cdef int err diff --git a/src/flint/types/_gr.pyx b/src/flint/types/_gr.pyx index 1641becd..62d7288c 100644 --- a/src/flint/types/_gr.pyx +++ b/src/flint/types/_gr.pyx @@ -232,6 +232,8 @@ cdef class gr_ctx(flint_ctx): 18446744073709551615 """ if isinstance(arg, gr): + if arg.ctx == self: + return arg return self.from_other(arg) if type(arg) is int: try: @@ -397,7 +399,25 @@ cdef class gr_ctx(flint_ctx): """ return truth_to_py(self._is_neg_one(x)) - def neg(self, x): + def equal(self, x, y) -> bool | None: + """ + Returns whether the elements `x` and `y` are equal. + """ + return self._equal(self(x), self(y)) + + # def is_integer(self, x): + # """ + # Returns whether `x` represents an integer. + # """ + # return self._is_integer(self(x)) + # + # def is_rational(self, x): + # """ + # Returns whether x represents a rational number. + # """ + # return self._is_rational(self(x)) + + def neg(self, x) -> gr: """ Returns `-x`. @@ -405,10 +425,13 @@ cdef class gr_ctx(flint_ctx): >>> arb = gr_real_arb_ctx.new(53); acb = gr_complex_acb_ctx.new(106) >>> c = acb("2 + I").sqrt(); c ([1.4553466902253548081226618397097 +/- 3.48e-32] + [0.3435607497225124641385657439146 +/- 5.23e-32]*I) - >>> arb.neg(c) - [-1.455346690225355 +/- 1.92e-16] + >>> acb.neg(c) + ([-1.4553466902253548081226618397097 +/- 3.48e-32] + [-0.3435607497225124641385657439146 +/- 5.23e-32]*I) """ - return self._neg(x) + return self._neg(self(x)) + + ### + # Arithmetic methods def add(self, x, y) -> gr: """ @@ -433,7 +456,7 @@ cdef class gr_ctx(flint_ctx): # context before performing the operation return self._add(self(x), self(y)) - def sub(self, x, y): + def sub(self, x, y) -> gr: if isinstance(x, gr) and isinstance(y, gr): if x.ctx == self and y.ctx == self: return self._sub(x, y) @@ -452,7 +475,7 @@ cdef class gr_ctx(flint_ctx): # context before performing the operation return self._sub(self(x), self(y)) - def mul(self, x, y): + def mul(self, x, y) -> gr: if isinstance(x, gr) and isinstance(y, gr): if x.ctx == self and y.ctx == self: return self._mul(x, y) @@ -471,12 +494,290 @@ cdef class gr_ctx(flint_ctx): # context before performing the operation return self._mul(self(x), self(y)) - # def gens_recursive(self) -> list[gr]: - # """Return all generators of the domain + ### + # Division + + def is_invertible(self, x) -> bool | None: + """ + Returns whether `x` has a multiplicative inverse in the present ring, i.e. whether `x` is a unit. + """ + return self._is_invertible(self(x)) + + def inv(self, x) -> gr: + """ + Returns the multiplicative inverse of `x` in the present ring, if such an element exists. + """ + return self._inv(self(x)) + + def div(self, x, y) -> gr: + """ + Returns the quotient `x/y` + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._div(x, y) + if x.ctx == self: + return self._div_other(x, y) + if y.ctx == self: + return self._other_div(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._div_si(x, y) + except OverflowError: + pass + + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._div(self(x), self(y)) + + def divexact(self, x, y) -> gr: + """ + Returns the quotient `x/y`, assuming that the quotient is exact in the current context. + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._divexact(x, y) + if x.ctx == self: + return self._divexact_other(x, y) + if y.ctx == self: + return self._other_divexact(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._divexact_si(x, y) + except OverflowError: + pass + + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._divexact(self(x), self(y)) + + def div_nonunique(self, x, y) -> gr: + """ + Returns an arbitrary solution `q` of the equation `x = qy`. + """ + return self._div_nonunique(self(x), self(y)) + + def divides(self, d, x) -> bool | None: + """ + Returns whether `d | x`; that is, whether there is an element `q` such that `x = qd`. + """ + return self._divides(self(d), self(x)) + + def euclidean_div(self, x, y) -> gr: + return self._euclidean_div(self(x), self(y)) + + def euclidean_rem(self, x, y) -> gr: + return self._euclidean_rem(self(x), self(y)) + + def euclidean_divrem(self, x, y) -> tuple[gr, gr]: + return self._euclidean_divrem(self(x), self(y)) + + ### + # Powering + + def pow(self, x, y) -> gr: + """ + Returns the power x^y, the interpretation of which depends on the ring when y ∉ ℤ + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx == self: + return self._pow(x, y) + if x.ctx == self: + return self._pow_other(x, y) + if y.ctx == self: + return self._other_pow(x, y) + + if isinstance(x, gr) and x.ctx == self and type(y) is int: + try: + return self._pow_si(x, y) + except OverflowError: + pass - # See :meth:`gens` for an example. + # NOTE: By default, convert everything to the required + # context before performing the operation + return self._pow(self(x), self(y)) + + ### + # Square Roots + + def is_square(self, x) -> bool | None: + """ + Returns whether x is a perfect square in the context. + """ + return self._is_square(self(x)) + + def sqrt(self, x) -> gr: + """ + Returns the square root of `x`. + """ + return self._sqrt(self(x)) + + def rsqrt(self, x) -> gr: + """ + Returns the reciprocal square root of `x`. + """ + return self._rsqrt(self(x)) + + ### + # Greatest Common Divisors + + def gcd(self, x, y) -> gr: + """ + Returns a greatest common divisor (GCD) of `x` and `y`. + Since the GCD is unique only up to multiplication by a unit, + an implementation-defined representative is chosen. + """ + return self._gcd(self(x), self(y)) + + def lcm(self, x, y) -> gr: + """ + Returns a least common multiple (LCM) of x and y. + Since the LCM is unique only up to multiplication by a unit, + an implementation-defined representative is chosen. + """ + return self._lcm(self(x), self(y)) + + ### + # Factorization + + # def factor(self, x) -> tuple[gr, list[gr], list[gr]]: # """ - # return self._gens_recursive() + # Given an element of the context, this returns a factorization (c, f, e): + # x = c f₁^e₁ ··· fₙ^eₙ, where fₖ will be irreducible or prime depending on the ring. + # The prefactor c stores a unit, sign or coefficient. + # Note that c is an element of the same ring as x. + # """ + # return self._factor(self(x)) + + ### + # Fractions + + def numerator(self, x) -> gr: + """ + Return a numerator p such that x = p / q. + For typical fraction fields, the denominator will be minimal and canonical. + However, some rings may return an arbitrary denominator as long as the numerator matches. + The default implementations simply returns p = x. + """ + return self._numerator(self(x)) + + def denominator(self, x) -> gr: + """ + Return a denominator q such that x = p / q. + For typical fraction fields, the denominator will be minimal and canonical. + However, some rings may return an arbitrary denominator as long as the numerator matches. + The default implementations simply returns q = 1. + """ + return self._denominator(self(x)) + + ### + # Integer and Complex parts + + def floor(self, x) -> gr: + return self._floor(self(x)) + + def ceil(self, x) -> gr: + return self._ceil(self(x)) + + def trunc(self, x) -> gr: + return self._trunc(self(x)) + + def nint(self, x) -> gr: + return self._nint(self(x)) + + def abs(self, x) -> gr: + return self._abs(self(x)) + + def conj(self, x) -> gr: + return self._conj(self(x)) + + def re(self, x) -> gr: + return self._re(self(x)) + + def im(self, x) -> gr: + return self._im(self(x)) + + def sgn(self, x) -> gr: + return self._sgn(self(x)) + + def csgn(self, x) -> gr: + return self._csgn(self(x)) + + def arg(self, x) -> gr: + return self._arg(self(x)) + + ### + # Ordering methods + + def cmp(self, x, y) -> int: + """ + Returns: + - -1 if x < y + - 0 if x = y + - 1 if x > y + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx != self: + return self._cmp_other(x, y) + + if y.ctx == self and x.ctx != self: + return -self._cmp_other(y, x) + + if x.ctx == self == y.ctx: + return self._cmp(x, y) + + return self._cmp(self(x), self(y)) + + def cmpabs(self, x, y) -> int: + """ + Returns: + - -1 if |x| < |y| + - 0 if |x| = |y| + - 1 if |x| > |y| + """ + if isinstance(x, gr) and isinstance(y, gr): + if x.ctx == self and y.ctx != self: + return self._cmpabs_other(x, y) + + if y.ctx == self and x.ctx != self: + return -self._cmpabs_other(y, x) + + if x.ctx == self == y.ctx: + return self._cmpabs(x, y) + + return self._cmpabs(self(x), self(y)) + + def le(self, x, y) -> bool | None: + return truth_to_py(self._le(self(x), self(y))) + + def abs_le(self, x, y) -> bool | None: + return truth_to_py(self._abs_le(self(x), self(y))) + + def lt(self, x, y) -> bool | None: + return truth_to_py(self._lt(self(x), self(y))) + + def abs_lt(self, x, y) -> bool | None: + return truth_to_py(self._abs_lt(self(x), self(y))) + + def ge(self, x, y) -> bool | None: + return truth_to_py(self._ge(self(x), self(y))) + + def abs_ge(self, x, y) -> bool | None: + return truth_to_py(self._abs_ge(self(x), self(y))) + + def gt(self, x, y) -> bool | None: + return truth_to_py(self._gt(self(x), self(y))) + + def abs_gt(self, x, y) -> bool | None: + return truth_to_py(self._abs_gt(self(x), self(y))) + + def min(self, x, y) -> gr: + return self._min(self(x), self(y)) + + def max(self, x, y) -> gr: + return self._max(self(x), self(y)) cdef class gr_scalar_ctx(gr_ctx): From 8ec4fbdbe8a5577f94a7faefd5f9ae85ff0ee7f2 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 30 Jan 2025 16:31:08 +0100 Subject: [PATCH 5/6] fix: Define error values for functions not present in flint --- src/flint/flintlib/types/gr.pxd | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/flint/flintlib/types/gr.pxd b/src/flint/flintlib/types/gr.pxd index b62cc01e..3db145ba 100644 --- a/src/flint/flintlib/types/gr.pxd +++ b/src/flint/flintlib/types/gr.pxd @@ -5,6 +5,26 @@ from flint.flintlib.types.flint cimport ( flint_bitcnt_t, ) +cdef extern from *: + """ + /* + * The following functions were introduced in FLINT 3.2.0 + */ + + #if __FLINT_RELEASE < 30200 + #define gr_min(res, x, y, ctx) GR_UNABLE + #define gr_max(res, x, y, ctx) GR_UNABLE + #define gr_le(x, y, ctx) T_UNKNOWN + #define gr_lt(x, y, ctx) T_UNKNOWN + #define gr_ge(x, y, ctx) T_UNKNOWN + #define gr_gt(x, y, ctx) T_UNKNOWN + #define gr_abs_le(x, y, ctx) T_UNKNOWN + #define gr_abs_lt(x, y, ctx) T_UNKNOWN + #define gr_abs_ge(x, y, ctx) T_UNKNOWN + #define gr_abs_gt(x, y, ctx) T_UNKNOWN + #endif + """ + cdef extern from "flint/gr.h": From 8b7f0c6fa183e6d85d7aa6c73531f6ab705d06f0 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 30 Jan 2025 17:09:24 +0100 Subject: [PATCH 6/6] Add gr_ctx factor --- src/flint/types/_gr.pxd | 42 ++++++++++++++++++++++++++++++----------- src/flint/types/_gr.pyx | 16 ++++++++-------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/flint/types/_gr.pxd b/src/flint/types/_gr.pxd index 2d097ff3..3de00a5e 100644 --- a/src/flint/types/_gr.pxd +++ b/src/flint/types/_gr.pxd @@ -767,17 +767,37 @@ cdef class gr_ctx(flint_ctx): ### # Factorization - # @cython.final - # cdef inline tuple[gr, list[gr], list[fmpz]] _factor(self, gr x): - # cdef int err - # cdef gr c = self.new_gr() - # cdef gr_vec_t factors - # gr_vec_init(factors, 0, self.ctx_t) - # cdef gr_vec_t exponents - # gr_vec_init(exponents, 0, self.ctx_t) - # err = gr_factor(c.pval, factors, exponents, x.pval, 0, self.ctx_t) - # if err != GR_SUCCESS: - # raise self._error(err, "Cannot factorize x in this context") + @cython.final + cdef inline tuple[gr, list[tuple[gr, int]]] _factor(self, gr x): + cdef int err, i + cdef slong length, exp_s + cdef gr c, f + cdef gr_ptr fac_i, exp_i + cdef gr_vec_t factors, exponents + cdef int flags = 0 # XXX: What is flags? + c = self.new_gr() + gr_vec_init(factors, 0, self.ctx_t) + gr_vec_init(exponents, 0, gr_fmpz_ctx_c.ctx_t) + err = gr_factor(c.pval, factors, exponents, x.pval, flags, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Failed to factor gr object") + length = gr_vec_length(factors, self.ctx_t) + py_factors = [None] * length + for 0 <= i < length: + f = self.new_gr() + fac_i = gr_vec_entry_ptr(factors, i, self.ctx_t) + err = gr_set(f.pval, fac_i, self.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Failed to copy factor.") + exp_i = gr_vec_entry_ptr(exponents, i, gr_fmpz_ctx_c.ctx_t) + err = gr_get_si(&exp_s, exp_i, gr_fmpz_ctx_c.ctx_t) + if err != GR_SUCCESS: + raise self._error(err, "Failed to get integer value of exponent.") + exp = exp_s + py_factors[i] = (f, exp) + gr_vec_clear(factors, self.ctx_t) + gr_vec_clear(exponents, gr_fmpz_ctx_c.ctx_t) + return c, py_factors ### # Fractions diff --git a/src/flint/types/_gr.pyx b/src/flint/types/_gr.pyx index 62d7288c..fa9f0f3a 100644 --- a/src/flint/types/_gr.pyx +++ b/src/flint/types/_gr.pyx @@ -642,14 +642,14 @@ cdef class gr_ctx(flint_ctx): ### # Factorization - # def factor(self, x) -> tuple[gr, list[gr], list[gr]]: - # """ - # Given an element of the context, this returns a factorization (c, f, e): - # x = c f₁^e₁ ··· fₙ^eₙ, where fₖ will be irreducible or prime depending on the ring. - # The prefactor c stores a unit, sign or coefficient. - # Note that c is an element of the same ring as x. - # """ - # return self._factor(self(x)) + def factor(self, x) -> tuple[gr, list[tuple[gr, int]]]: + """ + Given an element of the context, this returns a factorization (c, f, e): + x = c f₁^e₁ ··· fₙ^eₙ, where fₖ will be irreducible or prime depending on the ring. + The prefactor c stores a unit, sign or coefficient. + Note that c is an element of the same ring as x. + """ + return self._factor(self(x)) ### # Fractions