diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index bfb92de3e15..3d1518255d5 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -76,8 +76,5 @@ }, "sage.structure.coerce_actions": { "failed": "random failure https://github.com/sagemath/sage/issues/35973" - }, - "sage.tests.gap_packages": { - "failed": true } } diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 9b33358d332..988e535dd04 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=c72fc2006b1702e74f38c480b0376e3e6c8a5758 -md5=a650e8682fe75708f679eabe918d2aa7 -cksum=785462498 +sha1=8124eeddb2d440bd40f9656a8f69ce53175a4706 +md5=db7e8fb79c8cd9a69e8cc6865e43add9 +cksum=719116257 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 268e67a6a5f..b591deba141 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -919b021b0c103e4fb798cc784218015e0a515510 +0c16f847a4db786d105c48e79bf3bc3fe07d82fc diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 86f30b73058..35ad8b20879 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -210,4 +210,4 @@ cat < "$OUTPUT_INDEX" -sage-package list --has-file SPKG.rst | OUTPUT_DIR=$OUTPUT_DIR OUTPUT_RST=1 xargs -P 0 -n 1 sage-spkg-info +sage-package list --has-file SPKG.rst | OUTPUT_DIR=$OUTPUT_DIR OUTPUT_RST=1 xargs -P 99 -n 1 sage-spkg-info diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1c075a474d6..b86ae85d65c 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2444,6 +2444,11 @@ REFERENCES: J. Graph Algorithms and Applications 15 (2): 269-293, 2011. :doi:`10.7155/jgaa.00226`, :arxiv:`0705.1025`. +.. [EPSV2023] Jonathan Komada Eriksen, Lorenz Panny, Jana Sotáková, and Mattia Veroni. + Deuring for the People: Supersingular Elliptic Curves with Prescribed + Endomorphism Ring in General Characteristic. + LuCaNT 2023. https://ia.cr/2023/106 + .. [Eri1995] \H. Erikson. Computational and Combinatorial Aspects of Coxeter Groups. Thesis, 1995. diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index 32b5b8ef3cb..ee141189143 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -107,20 +107,7 @@ def __init_extra__(self): 0 sage: F(3) 3*B[0] - - sage: class Bar(Parent): - ....: _no_generic_basering_coercion = True - sage: Bar(category=Algebras(QQ)) - doctest:warning...: - DeprecationWarning: the attribute _no_generic_basering_coercion is deprecated, implement _coerce_map_from_base_ring() instead - See https://github.com/sagemath/sage/issues/19225 for details. - <__main__.Bar_with_category object at 0x...> """ - if getattr(self, '_no_generic_basering_coercion', False): - from sage.misc.superseded import deprecation - deprecation(19225, "the attribute _no_generic_basering_coercion is deprecated, implement _coerce_map_from_base_ring() instead") - return - base_ring = self.base_ring() if base_ring is self: # There are rings that are their own base rings. No need to register that. diff --git a/src/sage/homology/free_resolution.py b/src/sage/homology/free_resolution.py index 280a479b438..8cc950df90c 100644 --- a/src/sage/homology/free_resolution.py +++ b/src/sage/homology/free_resolution.py @@ -7,7 +7,7 @@ .. MATH:: - R^{n_1} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} + R^{n_0} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} \cdots \xleftarrow{d_k} R^{n_k} \xleftarrow{d_{k+1}} 0 terminating with a zero module at the end that is exact (all homology groups @@ -242,9 +242,20 @@ def _repr_module(self, i): 'S^2' sage: r # indirect doctest S^1 <-- S^3 <-- S^2 <-- 0 + + TESTS:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal(0) + sage: C = I.free_resolution() + sage: C + S^1 <-- 0 """ if i == 0: - r = self._maps[0].nrows() + if self._length > 0: + r = self._maps[0].nrows() + else: + r = self._initial_differential.domain().dimension() s = f'{self._name}^{r}' return s elif i > self._length: @@ -422,6 +433,8 @@ def __getitem__(self, i): raise IndexError('invalid index') elif i > self._length: F = FreeModule(self._base_ring, 0) + elif i == 0: + F = self.differential(0).domain() elif i == self._length: F = FreeModule(self._base_ring, self._maps[i - 1].ncols()) else: @@ -444,11 +457,12 @@ def differential(self, i): sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 sage: r.differential(3) - Free module morphism defined by the matrix [] - Domain: Ambient free module of rank 0 over the integral domain - Multivariate Polynomial Ring in x, y, z, w over Rational Field - Codomain: Ambient free module of rank 2 over the integral domain - Multivariate Polynomial Ring in x, y, z, w over Rational Field + Free module morphism defined as left-multiplication by the matrix + [] + Domain: Ambient free module of rank 0 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Codomain: Ambient free module of rank 2 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field sage: r.differential(2) Free module morphism defined as left-multiplication by the matrix [-y x] @@ -476,6 +490,31 @@ def differential(self, i): [-z^2 + y*w] [ y*z - x*w] [-y^2 + x*z] + + TESTS:: + + sage: P2. = ProjectiveSpace(QQ, 2) + sage: S = P2.coordinate_ring() + sage: I = S.ideal(0) + sage: C = I.graded_free_resolution(); C + S(0) <-- 0 + sage: C[1] + Ambient free module of rank 0 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field + sage: C[0] + Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field + sage: C.differential(1) + Free module morphism defined as left-multiplication by the matrix + [] + Domain: Ambient free module of rank 0 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field + Codomain: Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field + sage: C.differential(1).matrix() + [] + sage: C.differential(1).matrix().dimensions() + (1, 0) """ if i < 0: raise IndexError('invalid index') @@ -484,14 +523,17 @@ def differential(self, i): return self._initial_differential except AttributeError: raise ValueError('0th differential map undefined') - elif i == self._length + 1: - s = FreeModule(self._base_ring, 0) - t = FreeModule(self._base_ring, self._maps[i - 2].ncols()) - m = s.hom(0, t) elif i > self._length + 1: s = FreeModule(self._base_ring, 0) t = FreeModule(self._base_ring, 0) - m = s.hom(0, t) + m = s.hom(0, t, side='right') + elif i == self._length + 1: + s = FreeModule(self._base_ring, 0) + if self._length > 0: + t = FreeModule(self._base_ring, self._maps[i - 2].ncols()) + else: + t = self._initial_differential.domain() + m = s.hom(0, t, side='right') else: s = FreeModule(self._base_ring, self._maps[i - 1].ncols()) t = FreeModule(self._base_ring, self._maps[i - 1].nrows()) diff --git a/src/sage/logic/booleval.py b/src/sage/logic/booleval.py index f582c80a379..84da89f0c3f 100644 --- a/src/sage/logic/booleval.py +++ b/src/sage/logic/booleval.py @@ -23,15 +23,15 @@ sage: booleval.eval_formula(t, d) False """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 Chris Gorecki # Copyright (C) 2013 Paul Scurek # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from . import logicparser @@ -72,8 +72,8 @@ def eval_formula(tree, vdict): """ global __vars __vars = vdict - b = logicparser.apply_func(tree, eval_f) - return b + return logicparser.apply_func(tree, eval_f) + def eval_f(tree): r""" @@ -104,6 +104,7 @@ def eval_f(tree): """ return eval_op(tree[0], tree[1], tree[2]) + def eval_op(op, lv, rv): r""" Evaluate ``lv`` and ``rv`` according to the operator ``op``. diff --git a/src/sage/logic/boolformula.py b/src/sage/logic/boolformula.py index ae2c1ee6aba..d28faa87b8d 100644 --- a/src/sage/logic/boolformula.py +++ b/src/sage/logic/boolformula.py @@ -148,7 +148,7 @@ ('->', '\\rightarrow ')] -class BooleanFormula(): +class BooleanFormula: """ Boolean formulas. @@ -673,10 +673,7 @@ def is_satisfiable(self): False """ table = self.truthtable().get_table_list() - for row in table[1:]: - if row[-1] is True: - return True - return False + return any(row[-1] is True for row in table[1:]) def is_tautology(self): r""" @@ -1309,11 +1306,11 @@ def reduce_op(self, tree): if tree[0] == '<->': # parse tree for (~tree[1]|tree[2])&(~tree[2]|tree[1]) new_tree = ['&', ['|', ['~', tree[1], None], tree[2]], - ['|', ['~', tree[2], None], tree[1]]] + ['|', ['~', tree[2], None], tree[1]]] elif tree[0] == '^': # parse tree for (tree[1]|tree[2])&~(tree[1]&tree[2]) new_tree = ['&', ['|', tree[1], tree[2]], - ['~', ['&', tree[1], tree[2]], None]] + ['~', ['&', tree[1], tree[2]], None]] elif tree[0] == '->': # parse tree for ~tree[1]|tree[2] new_tree = ['|', ['~', tree[1], None], tree[2]] @@ -1354,10 +1351,7 @@ def dist_not(self, tree): if tree[0] == '~' and isinstance(tree[1], list): op = tree[1][0] if op != '~': - if op == '&': - op = '|' - else: - op = '&' + op = '|' if op == '&' else '&' new_tree = [op, ['~', tree[1][1], None], ['~', tree[1][2], None]] return logicparser.apply_func(new_tree, self.dist_not) else: diff --git a/src/sage/logic/logic.py b/src/sage/logic/logic.py index 16a9cf9a1da..b07a8d0edd8 100644 --- a/src/sage/logic/logic.py +++ b/src/sage/logic/logic.py @@ -15,7 +15,7 @@ - Paul Scurek (2013-08-03): updated docstring formatting """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Chris Gorecki # Copyright (C) 2007 William Stein # Copyright (C) 2013 Paul Scurek @@ -23,8 +23,8 @@ # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import string @@ -36,6 +36,7 @@ vars = {} vars_order = [] + class SymbolicLogic: """ EXAMPLES: @@ -184,7 +185,7 @@ def truthtable(self, statement, start=0, end=-1): end = 2 ** len(vars) table = [statement] keys = vars_order - for i in range(start,end): + for i in range(start, end): j = 0 row = [] for key in reversed(keys): @@ -270,10 +271,7 @@ def print_table(self, table): line = s = "" i = 0 for e in row: - if e == 'True': - j = 2 - else: - j = 1 + j = 2 if e == 'True' else 1 s = e + ' ' * j if i < len(vars_len): while len(s) <= vars_len[i]: @@ -370,6 +368,7 @@ def prove(self, statement): """ raise NotImplementedError + def get_bit(x, c): r""" Determine if bit ``c`` of the number ``x`` is 1. @@ -402,10 +401,7 @@ def get_bit(x, c): """ bits = [] while x > 0: - if x % 2 == 0: - b = 'False' - else: - b = 'True' + b = 'False' if x % 2 == 0 else 'True' x = x // 2 bits.append(b) if c > len(bits) - 1: @@ -456,6 +452,7 @@ def eval(toks): raise RuntimeError return stack[0] + def eval_ltor_toks(lrtoks): r""" Evaluates the expression contained in ``lrtoks``. @@ -494,6 +491,7 @@ def eval_ltor_toks(lrtoks): raise RuntimeError return lrtoks[0] + def reduce_bins(lrtoks): r""" Evaluate ``lrtoks`` to a single boolean value. @@ -531,6 +529,7 @@ def reduce_bins(lrtoks): reduce_bins(lrtoks) i += 1 + def reduce_monos(lrtoks): r""" Replace monotonic operator/variable pairs with a boolean value. @@ -566,6 +565,7 @@ def reduce_monos(lrtoks): del lrtoks[i + 1] i += 1 + def eval_mon_op(args): r""" Return a boolean value based on the truth table of the operator @@ -592,21 +592,17 @@ def eval_mon_op(args): sage: log = SymbolicLogic() sage: s = log.statement("!(a&b)|!a"); s - [['OPAREN', 'NOT', 'OPAREN', 'a', 'AND', 'b', 'CPAREN', 'OR', 'NOT', 'a', 'CPAREN'], + [['OPAREN', 'NOT', 'OPAREN', 'a', 'AND', 'b', 'CPAREN', 'OR', + 'NOT', 'a', 'CPAREN'], {'a': 'False', 'b': 'False'}, ['a', 'b']] sage: sage.logic.logic.eval_mon_op(['NOT', 'a']) 'True' """ - if args[1] != 'True' and args[1] != 'False': - val = vars[args[1]] - else: - val = args[1] + val = vars[args[1]] if args[1] != 'True' and args[1] != 'False' else args[1] + + return 'False' if val == 'True' else 'True' - if val == 'True': - return 'False' - else: - return 'True' def eval_bin_op(args): r""" @@ -660,6 +656,7 @@ def eval_bin_op(args): elif args[1] == 'IFF': return eval_iff_op(lval, rval) + def eval_and_op(lval, rval): r""" Apply the 'and' operator to ``lval`` and ``rval``. @@ -691,14 +688,8 @@ def eval_and_op(lval, rval): sage: sage.logic.logic.eval_and_op('True', 'True') 'True' """ - if lval == 'False' and rval == 'False': - return 'False' - elif lval == 'False' and rval == 'True': - return 'False' - elif lval == 'True' and rval == 'False': - return 'False' - elif lval == 'True' and rval == 'True': - return 'True' + return 'True' if (lval == 'True' == rval) else 'False' + def eval_or_op(lval, rval): r""" @@ -731,14 +722,8 @@ def eval_or_op(lval, rval): sage: sage.logic.logic.eval_or_op('True', 'True') 'True' """ - if lval == 'False' and rval == 'False': - return 'False' - elif lval == 'False' and rval == 'True': - return 'True' - elif lval == 'True' and rval == 'False': - return 'True' - elif lval == 'True' and rval == 'True': - return 'True' + return 'True' if (lval == 'True' or rval == 'True') else 'False' + def eval_ifthen_op(lval, rval): r""" @@ -772,14 +757,8 @@ def eval_ifthen_op(lval, rval): sage: sage.logic.logic.eval_ifthen_op('True', 'True') 'True' """ - if lval == 'False' and rval == 'False': - return 'True' - elif lval == 'False' and rval == 'True': - return 'True' - elif lval == 'True' and rval == 'False': - return 'False' - elif lval == 'True' and rval == 'True': - return 'True' + return 'False' if (lval == 'True' and rval == 'False') else 'True' + def eval_iff_op(lval, rval): r""" @@ -813,14 +792,8 @@ def eval_iff_op(lval, rval): sage: sage.logic.logic.eval_iff_op('True', 'True') 'True' """ - if lval == 'False' and rval == 'False': - return 'True' - elif lval == 'False' and rval == 'True': - return 'False' - elif lval == 'True' and rval == 'False': - return 'False' - elif lval == 'True' and rval == 'True': - return 'True' + return 'True' if (lval == rval) else 'False' + def tokenize(s, toks): r""" @@ -887,7 +860,8 @@ def tokenize(s, toks): if tok[0] not in string.ascii_letters: valid = 0 for c in tok: - if c not in string.ascii_letters and c not in string.digits and c != '_': + if not (c in string.ascii_letters + or c in string.digits or c == '_'): valid = 0 if valid == 1: diff --git a/src/sage/logic/logicparser.py b/src/sage/logic/logicparser.py index 7c234f2ff7e..e3d74f512e1 100644 --- a/src/sage/logic/logicparser.py +++ b/src/sage/logic/logicparser.py @@ -75,7 +75,7 @@ sage: logicparser.tree_parse(r, polish = True) ['|', ['~', ['~', 'a']], 'b'] """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Chris Gorecki # Copyright (C) 2013 Paul Scurek # @@ -83,7 +83,7 @@ # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** import string @@ -164,6 +164,7 @@ def polish_parse(s): return vars_order return tree + def get_trees(*statements): r""" Return the full syntax parse trees of the statements. @@ -276,6 +277,7 @@ def recover_formula(prefix_tree): return formula return formula[1:-1] + def recover_formula_internal(prefix_tree): r""" Recover the formula from a parse tree in prefix form. @@ -385,6 +387,7 @@ def prefix_to_infix(prefix_tree): raise TypeError("the input must be a parse tree as a list") return apply_func(prefix_tree, to_infix_internal) + def to_infix_internal(prefix_tree): r""" Convert a simple parse tree from prefix form to infix form. @@ -434,6 +437,7 @@ def to_infix_internal(prefix_tree): return [prefix_tree[1], prefix_tree[0], prefix_tree[2]] return prefix_tree + def tokenize(s): r""" Return the tokens and the distinct variables appearing in a boolean @@ -516,6 +520,7 @@ def tokenize(s): toks.append(')') return toks, vars_order + def tree_parse(toks, polish=False): r""" Return a parse tree from the tokens in ``toks``. @@ -572,6 +577,7 @@ def tree_parse(toks, polish=False): stack.append(branch) return stack[0] + def parse_ltor(toks, n=0, polish=False): r""" Return a parse tree from ``toks``, where each token in ``toks`` is atomic. @@ -657,6 +663,7 @@ def parse_ltor(toks, n=0, polish=False): raise SyntaxError return toks[0] + def apply_func(tree, func): r""" Apply ``func`` to each node of ``tree``, and return a new parse tree. diff --git a/src/sage/logic/logictable.py b/src/sage/logic/logictable.py index 06b0fb6ed63..b43fbef66d8 100644 --- a/src/sage/logic/logictable.py +++ b/src/sage/logic/logictable.py @@ -106,7 +106,7 @@ For statements that contain a variable list that when printed is longer than the latex page, the columns of the table will run off the screen. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 William Stein # Copyright (C) 2006 Chris Gorecki # Copyright (C) 2013 Paul Scurek @@ -114,13 +114,14 @@ # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** # Global variables __table = [] __vars_order = [] + class Truthtable: """ A truth table. @@ -244,10 +245,7 @@ def __repr__(self): line = s = "" i = 0 for e in row: - if e: - j = 2 - else: - j = 1 + j = 2 if e else 1 s = str(e) + ' ' * j if i < len(vars_len): while len(s) <= vars_len[i]: diff --git a/src/sage/logic/propcalc.py b/src/sage/logic/propcalc.py index 2533cdac702..2c359452aa5 100644 --- a/src/sage/logic/propcalc.py +++ b/src/sage/logic/propcalc.py @@ -140,8 +140,8 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** -### TODO: -### converts (cnf) returns w/o change +# TODO: +# converts (cnf) returns w/o change from . import boolformula from . import logicparser @@ -189,6 +189,7 @@ def formula(s): raise SyntaxError(msg) return f + def get_formulas(*statements): r""" Convert statements and parse trees into instances of @@ -261,6 +262,7 @@ def get_formulas(*statements): raise TypeError return formulas + def consistent(*formulas): r""" Determine if the formulas are logically consistent. diff --git a/src/sage/matrix/matrix_double_dense.pyx b/src/sage/matrix/matrix_double_dense.pyx index 7ac41c4df54..ffd385039ee 100644 --- a/src/sage/matrix/matrix_double_dense.pyx +++ b/src/sage/matrix/matrix_double_dense.pyx @@ -867,7 +867,7 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): # set cutoff as RDF element if eps == 'auto': if scipy is None: import scipy - eps = 2*max(self._nrows, self._ncols)*scipy.finfo(float).eps*sv[0] + eps = 2*max(self._nrows, self._ncols)*numpy.finfo(float).eps*sv[0] eps = RDF(eps) # locate non-zero entries rank = 0 diff --git a/src/sage/misc/table.py b/src/sage/misc/table.py index de24f09592c..e0145407e87 100644 --- a/src/sage/misc/table.py +++ b/src/sage/misc/table.py @@ -432,7 +432,7 @@ def _widths(self): widths = w return tuple(widths) - def _repr_(self): + def _repr_(self) -> str: r""" String representation of a table. @@ -470,7 +470,8 @@ def _repr_(self): for row in rows[:-1]: s += self._str_table_row(row, header_row=False) - s += self._str_table_row(rows[-1], header_row=False, last_row=True) + if rows: + s += self._str_table_row(rows[-1], header_row=False, last_row=True) return s.strip("\n") def _rich_repr_(self, display_manager, **kwds): diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index 708d440a205..9f973c6bd69 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -426,7 +426,7 @@ def minimize(func, x0, gradient=None, hessian=None, algorithm="default", hess = func.hessian() hess_fast = [ [fast_callable(a, vars=var_names, domain=float) for a in row] for row in hess] hessian = lambda p: [[a(*p) for a in row] for row in hess_fast] - from scipy import dot + from numpy import dot hessian_p = lambda p,v: dot(numpy.array(hessian(p)),v) min = optimize.fmin_ncg(f, [float(_) for _ in x0], fprime=gradient, fhess=hessian, fhess_p=hessian_p, disp=verbose, **args) diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index c78e7badeb4..c15b8b1c1dc 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -25,14 +25,15 @@ from sage.matrix.matrix_space import MatrixSpace from sage.misc.lazy_import import lazy_import from sage.structure.element import is_Matrix +from sage.categories.rings import Rings +from sage.categories.fields import Fields +from sage.categories.principal_ideal_domains import PrincipalIdealDomains from sage.rings.integer_ring import IntegerRing, ZZ -from sage.rings.ring import Ring from sage.misc.functional import denominator, is_even from sage.arith.misc import GCD from sage.arith.functions import lcm as LCM from sage.rings.ideal import Ideal from sage.rings.rational_field import QQ -from sage.rings.ring import is_Ring, PrincipalIdealDomain from sage.structure.element import is_Vector from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.polynomial_element import Polynomial @@ -552,7 +553,7 @@ def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_ """ # Deal with: QuadraticForm(ring, matrix) matrix_init_flag = False - if isinstance(R, Ring): + if R in Rings(): if is_Matrix(n): # Test if n is symmetric and has even diagonal if not self._is_even_symmetric_matrix_(n, R): @@ -1110,7 +1111,7 @@ def _is_even_symmetric_matrix_(self, A, R=None): R = A.base_ring() ring_coerce_test = False - if not isinstance(R, Ring): + if R not in Rings(): raise TypeError("R is not a ring.") if not (A.is_square() and A.is_symmetric()): @@ -1260,17 +1261,16 @@ def has_integral_Gram_matrix(self): """ # Warning over fields - if self.base_ring().is_field(): + if self.base_ring() in Fields(): warn("Warning -- A quadratic form over a field always has integral Gram matrix. Do you really want to do this?!?") # Determine integrality of the Gram matrix - flag = True try: self.Gram_matrix() - except Exception: - flag = False - - return flag + except TypeError: + return False + else: + return True def gcd(self): """ @@ -1297,9 +1297,9 @@ def polynomial(self, names='x'): INPUT: - - ``self`` - a quadratic form over a commutative ring + - ``self`` -- a quadratic form over a commutative ring - - ``names`` - specification of the names of the variables; see :func:`PolynomialRing` + - ``names`` -- specification of the names of the variables; see :func:`PolynomialRing` OUTPUT: The polynomial form of the quadratic form. @@ -1328,21 +1328,19 @@ def polynomial(self, names='x'): sage: Q.polynomial() Traceback (most recent call last): ... - ValueError: Can only create polynomial rings over commutative rings. + ValueError: Can only create polynomial rings over commutative rings """ B = self.base_ring() + if B not in Rings().Commutative(): + raise ValueError('Can only create polynomial rings over commutative rings') n = self.dim() M = matrix(B, n) for i in range(n): for j in range(i, n): M[i, j] = self[i, j] - try: - R = PolynomialRing(self.base_ring(), names, n) - except Exception: - raise ValueError('Can only create polynomial rings over commutative rings.') + R = PolynomialRing(self.base_ring(), names, n) V = vector(R.gens()) - P = (V*M).dot_product(V) - return P + return (V * M).dot_product(V) @staticmethod def from_polynomial(poly): @@ -1568,7 +1566,7 @@ def change_ring(self, R): 1 """ # Check that a canonical coercion is possible - if not is_Ring(R): + if R not in Rings(): raise TypeError("R is not a ring") if not R.has_coerce_map_from(self.base_ring()): raise TypeError(f"there is no canonical coercion from {self.base_ring()} to R") @@ -1609,11 +1607,11 @@ def level(self): except AttributeError: # Check that the base ring is a PID - if not isinstance(self.base_ring(), PrincipalIdealDomain): + if self.base_ring() not in PrincipalIdealDomains(): raise TypeError("the level (as a number) is only defined over a Principal Ideal Domain ; try using level_ideal()") # Warn the user if the form is defined over a field! - if self.base_ring().is_field(): + if self.base_ring() in Fields(): warn("Warning -- The level of a quadratic form over a field is always 1. Do you really want to do this?!?") # raise RuntimeError("Warning -- The level of a quadratic form over a field is always 1. Do you really want to do this?!?") diff --git a/src/sage/rings/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field.py index 87843227295..a633a555d1d 100644 --- a/src/sage/rings/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field.py @@ -170,10 +170,10 @@ from sage.structure.richcmp import rich_to_bool from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import FieldElement, parent +from sage.structure.parent import Parent from sage.structure.coerce import py_scalar_to_element from sage.categories.morphism import Morphism -from sage.rings.ring import Field from sage.rings.integer import Integer from sage.rings.rational import Rational @@ -326,7 +326,7 @@ def __init__(self, parent, obj): self._obj = obj FieldElement.__init__(self, parent) - def __bool__(self): + def __bool__(self) -> bool: r""" TESTS:: @@ -358,7 +358,7 @@ def __reduce__(self): """ return self.parent(), (str(self),) - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" Equality test. @@ -401,7 +401,7 @@ def __eq__(self, other): return self == other return self._obj == other._obj - def __ne__(self, other): + def __ne__(self, other) -> bool: r""" Difference test. @@ -470,7 +470,7 @@ def imag(self): imag_part = imag - def is_real(self): + def is_real(self) -> bool: r""" Test whether this element is real. @@ -489,7 +489,7 @@ def is_real(self): """ return self._obj.RealPart() == self._obj - def is_integral(self): + def is_integral(self) -> bool: """ Return whether ``self`` is an algebraic integer. @@ -648,7 +648,7 @@ def _algebraic_(self, R): """ return R(QQbar(self)) - def __float__(self): + def __float__(self) -> float: r""" TESTS:: @@ -749,7 +749,7 @@ def _eval_real_(self, R): _mpfr_ = _eval_real_ - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: r""" Comparison (using the complex embedding). @@ -846,7 +846,7 @@ def additive_order(self): """ return Infinity if self else ZZ.zero() - def is_rational(self): + def is_rational(self) -> bool: r""" Test whether this element is a rational number. @@ -886,7 +886,7 @@ def _rational_(self): raise TypeError("Unable to coerce to a rational") return Rational(self._obj.sage()) - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -1011,7 +1011,7 @@ def _pow_(self, other): raise NotImplementedError("no powering implemented for non-rational exponents") - def is_square(self): + def is_square(self) -> bool: r""" EXAMPLES:: @@ -1110,7 +1110,7 @@ def sqrt(self, extend=True, all=False): return UCF_sqrt_int(D, UCF) else: return UCF_sqrt_int(D.numerator(), UCF) / \ - UCF_sqrt_int(D.denominator(), UCF) + UCF_sqrt_int(D.denominator(), UCF) # root of unity k = self._obj.Conductor() @@ -1139,7 +1139,7 @@ def conjugate(self): P = self.parent() return P.element_class(P, self._obj.ComplexConjugate()) - def galois_conjugates(self, n=None): + def galois_conjugates(self, n=None) -> list: r""" Return the Galois conjugates of ``self``. @@ -1321,7 +1321,7 @@ def __init__(self, names=None): False """ from sage.categories.fields import Fields - Field.__init__(self, base_ring=QQ, category=Fields().Infinite()) + Parent.__init__(self, base=QQ, category=Fields().Infinite()) self._populate_coercion_lists_(embedding=UCFtoQQbar(self)) late_import() @@ -1349,7 +1349,7 @@ def an_element(self): """ return self.gen(5, 1) - self(3) * self.gen(5, 2) - def some_elements(self): + def some_elements(self) -> tuple: r""" Return a tuple of some elements in the universal cyclotomic field. @@ -1364,7 +1364,7 @@ def some_elements(self): self.gen(3, 1), self.gen(7, 1) - self(2) / self(3) * self.gen(7, 2)) - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -1373,7 +1373,7 @@ def _repr_(self): """ return "Universal Cyclotomic Field" - def is_exact(self): + def is_exact(self) -> bool: r""" Return ``True`` as this is an exact ring (i.e. not numerical). @@ -1685,8 +1685,8 @@ def _factor_univariate_polynomial(self, f): m = p.is_cyclotomic(certificate=True) if not m: raise NotImplementedError('no known factorization for this polynomial') - for i in m.coprime_integers(m): - factors.append((x - UCF.zeta(m, i), e)) + factors.extend((x - UCF.zeta(m, i), e) + for i in m.coprime_integers(m)) return Factorization(factors, unit) @@ -1702,7 +1702,7 @@ def degree(self): """ return Infinity - def _gap_init_(self): + def _gap_init_(self) -> str: r""" Return gap string representation of ``self``. diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 81d4a8ea359..944be554fd4 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -19,6 +19,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring import polygen from sage.rings.rational_field import QQ +from sage.misc.misc_c import prod from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field from sage.schemes.curves.projective_curve import ProjectivePlaneCurve_field @@ -1393,6 +1394,208 @@ def isogeny_codomain(self, kernel): E._fetch_cached_order(self) return E + def kernel_polynomial_from_point(self, P, *, algorithm=None): + r""" + Given a point `P` on this curve which generates a rational subgroup, + return the kernel polynomial of that subgroup as a polynomial over + the base field of the curve. + (The point `P` itself may be defined over an extension.) + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101), [1,1]) + sage: F = GF(101^3) + sage: EE = E.change_ring(F) + sage: xK = F([77, 28, 8]); xK + 8*z3^2 + 28*z3 + 77 + sage: K = EE.lift_x(xK); K.order() + 43 + sage: E.kernel_polynomial_from_point(K) + x^21 + 7*x^20 + 22*x^19 + 4*x^18 + 7*x^17 + 81*x^16 + 41*x^15 + 68*x^14 + 18*x^13 + 58*x^12 + 31*x^11 + 26*x^10 + 62*x^9 + 20*x^8 + 73*x^7 + 23*x^6 + 66*x^5 + 79*x^4 + 12*x^3 + 40*x^2 + 50*x + 93 + + The ``"minpoly"`` algorithm is often much faster than the + ``"basic"`` algorithm:: + + sage: from sage.schemes.elliptic_curves.ell_field import EllipticCurve_field, point_of_order + sage: p = 2^127 - 1 + sage: E = EllipticCurve(GF(p), [1,0]) + sage: P = point_of_order(E, 31) + sage: %timeit E.kernel_polynomial_from_point(P, algorithm='basic') # not tested + 4.38 ms ± 13.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) + sage: %timeit E.kernel_polynomial_from_point(P, algorithm='minpoly') # not tested + 854 µs ± 1.56 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) + + Example of finding all the rational isogenies using this method:: + + sage: E = EllipticCurve(GF(71), [1,2,3,4,5]) + sage: F = E.division_field(11) + sage: EE = E.change_ring(F) + sage: fs = set() + sage: for K in EE(0).division_points(11): + ....: if not K: + ....: continue + ....: Kp = EE.frobenius_isogeny()(K) + ....: if Kp.weil_pairing(K, 11) == 1: + ....: fs.add(E.kernel_polynomial_from_point(K)) + sage: fs = sorted(fs); fs + [x^5 + 10*x^4 + 18*x^3 + 10*x^2 + 43*x + 46, + x^5 + 65*x^4 + 39*x^2 + 20*x + 63] + sage: from sage.schemes.elliptic_curves.isogeny_small_degree import is_kernel_polynomial + sage: {is_kernel_polynomial(E, 11, f) for f in fs} + {True} + sage: isogs = [E.isogeny(f) for f in fs] + sage: isogs[0] + Isogeny of degree 11 from Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Finite Field of size 71 to Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 34*x + 42 over Finite Field of size 71 + sage: isogs[1] + Isogeny of degree 11 from Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Finite Field of size 71 to Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 12*x + 40 over Finite Field of size 71 + sage: set(isogs) == set(E.isogenies_prime_degree(11)) + True + + ALGORITHM: + + - The ``"basic"`` algorithm is to multiply together all the linear + factors `(X - x([i]P))` of the kernel polynomial using a product + tree, then converting the result to the base field of the curve. + Its complexity is `\widetilde O(\ell k)` where `k` is the + extension degree. + + - The ``"minpoly"`` algorithm is + [EPSV2023]_, Algorithm 4 (``KernelPolynomialFromIrrationalX``). + Over finite fields, its complexity is `O(\ell k) + \widetilde O(\ell)` + where `k` is the extension degree. + """ + R = self.base_ring() + + if not P: + return R['x'].one() + + S = P.base_ring() + if not S.has_coerce_map_from(R): + raise TypeError(f'{R} does not coerce into {S}') + + EE = self.change_ring(S) + if P.curve() is not EE: + raise TypeError(f'{P} is not a point on {EE}') + + l = P.order() + + if algorithm is None: + if R in FiniteFields(): + # In this case the minpoly approach is likely to be faster. + if l & 1 and l.is_prime_power(): + algorithm = 'minpoly' + if algorithm is None: + algorithm = 'basic' + + if algorithm == 'basic': + from sage.groups.generic import multiples + Qs = multiples(P, l//2, P) + x = polygen(S) + f = prod(x - Q.xy()[0] for Q in Qs) + return f.change_ring(R) + + if algorithm == 'minpoly': + if not l & 1 or not l.is_prime_power(): + raise ValueError('algorithm "minpoly" only supports odd prime-power degrees') + + xx = P.xy()[0] + ext = xx.parent().over(self.base_ring()) + mu = ext(xx).minpoly() + assert mu.base_ring() == self.base_ring() + + return self.kernel_polynomial_from_divisor(mu, P.order(), check=False) + + raise ValueError('unknown algorithm') + + def kernel_polynomial_from_divisor(self, f, l, *, check=True): + r""" + Given an irreducible divisor `f` of the `l`-division polynomial + on this curve, return the kernel polynomial defining the subgroup + defined by `f`. + + If the given polynomial does not define a rational subgroup, a + :class:`ValueError` is raised. + + This method is currently only implemented for prime `l`. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(101^2), [0,1]) + sage: f,_ = E.division_polynomial(5).factor()[0] + sage: ker = E.kernel_polynomial_from_divisor(f, 5); ker + x^2 + (49*z2 + 10)*x + 30*z2 + 80 + sage: E.isogeny(ker) + Isogeny of degree 5 + from Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field in z2 of size 101^2 + to Elliptic Curve defined by y^2 = x^3 + (6*z2+16)*x + 18 over Finite Field in z2 of size 101^2 + + The method detects invalid inputs:: + + sage: E = EllipticCurve(GF(101), [0,1]) + sage: f,_ = E.division_polynomial(5).factor()[-1] + sage: E.kernel_polynomial_from_divisor(f, 5) + Traceback (most recent call last): + ... + ValueError: given polynomial does not define a rational 5-isogeny + + :: + + sage: E = EllipticCurve(GF(101), [1,1]) + sage: f,_ = E.division_polynomial(7).factor()[-1] + sage: E.kernel_polynomial_from_divisor(f, 7) + Traceback (most recent call last): + ... + ValueError: given polynomial does not define a rational 7-isogeny + + :: + + sage: x = polygen(QQ) + sage: K. = NumberField(x^12 - 2*x^10 + 3*x^8 + 228/13*x^6 + 235/13*x^4 + 22/13*x^2 + 1/13) + sage: E = EllipticCurve(K, [1,0]) + sage: ker = E.kernel_polynomial_from_divisor(x - t, 13); ker + x^6 + (-169/64*t^10 + 169/32*t^8 - 247/32*t^6 - 377/8*t^4 - 2977/64*t^2 - 105/32)*x^4 + (-169/32*t^10 + 169/16*t^8 - 247/16*t^6 - 377/4*t^4 - 2977/32*t^2 - 89/16)*x^2 - 13/64*t^10 + 13/32*t^8 - 19/32*t^6 - 29/8*t^4 - 229/64*t^2 - 13/32 + sage: phi = E.isogeny(ker, check=True); phi + Isogeny of degree 13 + from Elliptic Curve defined by y^2 = x^3 + x + over Number Field in t with defining polynomial x^12 - 2*x^10 + 3*x^8 + 228/13*x^6 + 235/13*x^4 + 22/13*x^2 + 1/13 + to Elliptic Curve defined by y^2 = x^3 + (-2535/16*t^10+2535/8*t^8-3705/8*t^6-5655/2*t^4-44655/16*t^2-2047/8)*x + over Number Field in t with defining polynomial x^12 - 2*x^10 + 3*x^8 + 228/13*x^6 + 235/13*x^4 + 22/13*x^2 + 1/13 + + ALGORITHM: [EPSV2023]_, Algorithm 3 (``KernelPolynomialFromDivisor``). + """ + l = ZZ(l) + if check: + if not l.is_prime(): + raise NotImplementedError('currently, kernel_polynomial_from_divisor() only supports prime orders') + if not f.is_irreducible(): + raise NotImplementedError('currently, kernel_polynomial_from_divisor() only supports irreducible polynomials') + if f.parent().base_ring() != self.base_ring(): + raise TypeError(f'given polynomial is not defined over the base ring of the curve') + if self.division_polynomial(l, x=f.parent().quotient_ring(f).gen()): + raise ValueError(f'given polynomial does not divide the {l}-division polynomial') + + if l == 2: + return f + + if not f.degree().divides(l//2): + raise ValueError(f'given polynomial does not define a rational {l}-isogeny') + + from sage.schemes.elliptic_curves.isogeny_small_degree import _least_semi_primitive + a = _least_semi_primitive(l) + mul_a = lambda x: self._multiple_x_numerator(a, x=x) / self._multiple_x_denominator(a, x=x) + x_mod = lambda g: g.parent().quotient(g).gen() + + fs = [f] + m = l//2//f.degree() + + for i in range(1, m): + fs.append(mul_a(x_mod(fs[-1])).minpoly()) + + if fs[0](mul_a(x_mod(fs[-1]))): + raise ValueError(f'given polynomial does not define a rational {l}-isogeny') + + return prod(fs) + def isogenies_prime_degree(self, l=None, max_l=31): """ Return a list of all separable isogenies of given prime degree(s) diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 054ad695ef8..65be8c246c0 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -619,7 +619,7 @@ def _reduce_point(self, R, p): sage: E = EllipticCurve([1,-1,0,94,9]) sage: R = E([0,3]) + 5*E([8,31]) - sage: factor(R.xy()[0].denominator()) + sage: factor(R.x().denominator()) 2^2 * 11^2 * 1457253032371^2 Since 11 is a factor of the denominator, this point corresponds to the diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index e86d0c8a5cf..cde3b14fb97 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -872,11 +872,57 @@ def xy(self): ... ZeroDivisionError: rational division by zero """ - if self[2] == 1: + if self[2].is_one(): return self[0], self[1] else: return self[0]/self[2], self[1]/self[2] + def x(self): + """ + Return the `x` coordinate of this point, as an element of the base field. + If this is the point at infinity, a :class:`ZeroDivisionError` is raised. + + EXAMPLES:: + + sage: E = EllipticCurve('389a') + sage: P = E([-1,1]) + sage: P.x() + -1 + sage: Q = E(0); Q + (0 : 1 : 0) + sage: Q.x() + Traceback (most recent call last): + ... + ZeroDivisionError: rational division by zero + """ + if self[2].is_one(): + return self[0] + else: + return self[0]/self[2] + + def y(self): + """ + Return the `y` coordinate of this point, as an element of the base field. + If this is the point at infinity, a :class:`ZeroDivisionError` is raised. + + EXAMPLES:: + + sage: E = EllipticCurve('389a') + sage: P = E([-1,1]) + sage: P.y() + 1 + sage: Q = E(0); Q + (0 : 1 : 0) + sage: Q.y() + Traceback (most recent call last): + ... + ZeroDivisionError: rational division by zero + """ + if self[2].is_one(): + return self[1] + else: + return self[1]/self[2] + def is_divisible_by(self, m): """ Return True if there exists a point `Q` defined over the same @@ -1572,7 +1618,7 @@ def _miller_(self, Q, n): sage: Fx. = GF((2,(4*5))) sage: Ex = EllipticCurve(Fx, [0,0,1,1,1]) sage: phi = Hom(F,Fx)(F.gen().minpoly().roots(Fx)[0][0]) - sage: Px = Ex(phi(P.xy()[0]), phi(P.xy()[1])) + sage: Px = Ex(phi(P.x()), phi(P.y())) sage: Qx = Ex(b^19 + b^18 + b^16 + b^12 + b^10 + b^9 + b^8 + b^5 + b^3 + 1, ....: b^18 + b^13 + b^10 + b^8 + b^5 + b^4 + b^3 + b) sage: Px._miller_(Qx,41) == b^17 + b^13 + b^12 + b^9 + b^8 + b^6 + b^4 + 1 @@ -1757,7 +1803,7 @@ def weil_pairing(self, Q, n, algorithm=None): sage: Fx. = GF((2, 4*5)) sage: Ex = EllipticCurve(Fx, [0,0,1,1,1]) sage: phi = Hom(F, Fx)(F.gen().minpoly().roots(Fx)[0][0]) - sage: Px = Ex(phi(P.xy()[0]), phi(P.xy()[1])) + sage: Px = Ex(phi(P.x()), phi(P.y())) sage: O = Ex(0) sage: Qx = Ex(b^19 + b^18 + b^16 + b^12 + b^10 + b^9 + b^8 + b^5 + b^3 + 1, ....: b^18 + b^13 + b^10 + b^8 + b^5 + b^4 + b^3 + b) @@ -1997,7 +2043,7 @@ def tate_pairing(self, Q, n, k, q=None): sage: Fx. = GF((2,4*5)) sage: Ex = EllipticCurve(Fx,[0,0,1,1,1]) sage: phi = Hom(F, Fx)(F.gen().minpoly().roots(Fx)[0][0]) - sage: Px = Ex(phi(P.xy()[0]), phi(P.xy()[1])) + sage: Px = Ex(phi(P.x()), phi(P.y())) sage: Qx = Ex(b^19 + b^18 + b^16 + b^12 + b^10 + b^9 + b^8 + b^5 + b^3 + 1, ....: b^18 + b^13 + b^10 + b^8 + b^5 + b^4 + b^3 + b) sage: Px.tate_pairing(Qx, n=41, k=4) @@ -2203,7 +2249,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: Fx. = GF(q^k) sage: Ex = EllipticCurve(Fx, [0,0,1,1,1]) sage: phi = Hom(F, Fx)(F.gen().minpoly().roots(Fx)[0][0]) - sage: Px = Ex(phi(P.xy()[0]), phi(P.xy()[1])) + sage: Px = Ex(phi(P.x()), phi(P.y())) sage: Qx = Ex(b^19+b^18+b^16+b^12+b^10+b^9+b^8+b^5+b^3+1, ....: b^18+b^13+b^10+b^8+b^5+b^4+b^3+b) sage: Qx = Ex(Qx[0]^q, Qx[1]^q) - Qx # ensure Qx is in ker(pi - q) @@ -3995,14 +4041,14 @@ def padic_elliptic_logarithm(self,Q, p): for k in range(0,p): Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + k * p for t in E.a_invariants() ]) - P_Qps = Eqp.lift_x(ZZ(self.xy()[0]), all=True) + P_Qps = Eqp.lift_x(ZZ(self.x()), all=True) for P_Qp in P_Qps: - if F(P_Qp.xy()[1]) == self.xy()[1]: + if F(P_Qp.y()) == self.y(): break - Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True) + Q_Qps = Eqp.lift_x(ZZ(Q.x()), all=True) for Q_Qp in Q_Qps: - if F(Q_Qp.xy()[1]) == Q.xy()[1]: + if F(Q_Qp.y()) == Q.y(): break pP = p * P_Qp diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py index 78736209ee5..309603a1aa5 100644 --- a/src/sage/schemes/elliptic_curves/height.py +++ b/src/sage/schemes/elliptic_curves/height.py @@ -1177,7 +1177,7 @@ def psi(self, xi, v): 3.51086196882538 sage: L(P) / L.real_period() 0.867385122699931 - sage: xP = v(P.xy()[0]) + sage: xP = v(P.x()) sage: H = E.height_function() sage: H.psi(xP, v) 0.867385122699931 diff --git a/src/sage/schemes/elliptic_curves/hom_sum.py b/src/sage/schemes/elliptic_curves/hom_sum.py index 1bfc1c438f5..ab10ec0d530 100644 --- a/src/sage/schemes/elliptic_curves/hom_sum.py +++ b/src/sage/schemes/elliptic_curves/hom_sum.py @@ -306,7 +306,7 @@ def to_isogeny_chain(self): from sage.groups.generic import multiples from sage.misc.misc_c import prod x = polygen(Kl.base_ring()) - poly = prod(x - T.xy()[0] for T in multiples(Kl, l//2, Kl)) + poly = prod(x - T.x() for T in multiples(Kl, l//2, Kl)) poly = poly.change_ring(self.base_ring()) psi = phi.codomain().isogeny(poly) @@ -339,7 +339,7 @@ def _degree_bounds(self): sage: (phi + psi)._degree_bounds() (24, 68) sage: (phi + psi).degree() - 61 + 31 sage: (phi - phi)._degree_bounds() (0, 12) sage: (phi - phi).degree() diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index fbe4887dbae..33bccacd137 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -260,7 +260,7 @@ class FastEllipticPolynomial: Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with n = 19, P = (4 : 35 : 1) sage: hP(7) 19 - sage: prod(7 - (i*P).xy()[0] for i in range(1,P.order(),2)) + sage: prod(7 - (i*P).x() for i in range(1,P.order(),2)) 19 Passing `Q` changes the index set:: @@ -269,7 +269,7 @@ class FastEllipticPolynomial: sage: hPQ = FastEllipticPolynomial(E, P.order(), P, Q) sage: hPQ(7) 58 - sage: prod(7 - (Q+i*P).xy()[0] for i in range(P.order())) + sage: prod(7 - (Q+i*P).x() for i in range(P.order())) 58 The call syntax has an optional keyword argument ``derivative``, which @@ -279,7 +279,7 @@ class FastEllipticPolynomial: sage: hP(7, derivative=True) (19, 15) sage: R. = E.base_field()[] - sage: HP = prod(Z - (i*P).xy()[0] for i in range(1,P.order(),2)) + sage: HP = prod(Z - (i*P).x() for i in range(1,P.order(),2)) sage: HP Z^9 + 16*Z^8 + 57*Z^7 + 6*Z^6 + 45*Z^5 + 31*Z^4 + 46*Z^3 + 10*Z^2 + 28*Z + 41 sage: HP(7) @@ -292,7 +292,7 @@ class FastEllipticPolynomial: sage: hPQ(7, derivative=True) (58, 62) sage: R. = E.base_field()[] - sage: HPQ = prod(Z - (Q+i*P).xy()[0] for i in range(P.order())) + sage: HPQ = prod(Z - (Q+i*P).x() for i in range(P.order())) sage: HPQ Z^19 + 53*Z^18 + 67*Z^17 + 39*Z^16 + 56*Z^15 + 32*Z^14 + 44*Z^13 + 6*Z^12 + 27*Z^11 + 29*Z^10 + 38*Z^9 + 48*Z^8 + 38*Z^7 + 43*Z^6 + 21*Z^5 + 25*Z^4 + 33*Z^3 + 49*Z^2 + 60*Z sage: HPQ(7) @@ -342,9 +342,9 @@ def __init__(self, E, n, P, Q=None): ) I, J, K = IJK - xI = (R.xy()[0] for R in _points_range(I, P, Q)) - xJ = [R.xy()[0] for R in _points_range(J, P )] - xK = (R.xy()[0] for R in _points_range(K, P, Q)) + xI = (R.x() for R in _points_range(I, P, Q)) + xJ = [R.x() for R in _points_range(J, P )] + xK = (R.x() for R in _points_range(K, P, Q)) self.hItree = ProductTree(Z - xi for xi in xI) @@ -766,7 +766,7 @@ def _raw_eval(self, x, y=None): sage: phi._raw_codomain Elliptic Curve defined by y^2 = x^3 + ... over Finite Field of size 65537 sage: Q = E(42, 15860) - sage: phi._raw_eval(Q.xy()[0]) + sage: phi._raw_eval(Q.x()) 11958 sage: phi._raw_eval(*Q.xy()) (11958, 42770) @@ -794,8 +794,8 @@ def _raw_eval(self, x, y=None): h0, h0d = self._h0(x, derivative=True) h1, h1d = self._h1(x, derivative=True) -# assert h0 == prod(x - ( i*self._P).xy()[0] for i in range(1,self._P.order(),2)) -# assert h1 == prod(x - (self._Q+i*self._P).xy()[0] for i in range( self._P.order() )) +# assert h0 == prod(x - ( i*self._P).x() for i in range(1,self._P.order(),2)) +# assert h1 == prod(x - (self._Q+i*self._P).x() for i in range( self._P.order() )) if not h0: return () @@ -805,8 +805,8 @@ def _raw_eval(self, x, y=None): if y is None: return xx -# assert h0d == sum(prod(x - ( i*self._P).xy()[0] for i in range(1,self._P.order(),2) if i!=j) for j in range(1,self._P.order(),2)) -# assert h1d == sum(prod(x - (self._Q+i*self._P).xy()[0] for i in range( self._P.order() ) if i!=j) for j in range( self._P.order() )) +# assert h0d == sum(prod(x - ( i*self._P).x() for i in range(1,self._P.order(),2) if i!=j) for j in range(1,self._P.order(),2)) +# assert h1d == sum(prod(x - (self._Q+i*self._P).x() for i in range( self._P.order() ) if i!=j) for j in range( self._P.order() )) yy = y * (h1d - 2 * h1 / h0 * h0d) / h0**2 @@ -1026,7 +1026,7 @@ def kernel_polynomial(self): x^15 + 21562*x^14 + 8571*x^13 + 20029*x^12 + 1775*x^11 + 60402*x^10 + 17481*x^9 + 46543*x^8 + 46519*x^7 + 18590*x^6 + 36554*x^5 + 36499*x^4 + 48857*x^3 + 3066*x^2 + 23264*x + 53937 sage: h == E.isogeny(K).kernel_polynomial() True - sage: h(K.xy()[0]) + sage: h(K.x()) 0 TESTS:: diff --git a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py index ac68ba3d601..01b0a28c4f6 100644 --- a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py +++ b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py @@ -2446,12 +2446,16 @@ def isogenies_prime_degree_general(E, l, minimal_models=True): OUTPUT: - A list of all separable isogenies of degree `l` with domain ``E``. + A list of all separable isogenies of degree `l` with domain ``E`` + (up to post-isomorphism). ALGORITHM: This algorithm factors the ``l``-division polynomial, then - combines its factors to obtain kernels. See [KT2013]_, Chapter 3. + combines its factors to obtain kernels. + Originally this was done using [KT2013]_, Chapter 3, but nowadays + the recombination step is instead delegated to + :meth:`~sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.kernel_polynomial_from_divisor`. .. NOTE:: @@ -2474,12 +2478,12 @@ def isogenies_prime_degree_general(E, l, minimal_models=True): [Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2 over Finite Field in a of size 3^12 - to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2*x + to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + x + 2 over Finite Field in a of size 3^12, Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2 over Finite Field in a of size 3^12 - to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + x + 2 + to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2*x over Finite Field in a of size 3^12] sage: E = EllipticCurve('50a1') sage: isogenies_prime_degree_general(E, 3) @@ -2585,8 +2589,8 @@ def isogenies_prime_degree_general(E, l, minimal_models=True): sage: E = EllipticCurve(K,[0,0,0,1,0]) # needs sage.rings.number_field sage: [phi.codomain().ainvs() # long time # needs sage.rings.number_field ....: for phi in E.isogenies_prime_degree(37)] - [(0, 0, 0, -840*i + 1081, 0), - (0, 0, 0, 840*i + 1081, 0)] + [(0, 0, 0, 840*i + 1081, 0), + (0, 0, 0, -840*i + 1081, 0)] """ if not l.is_prime(): raise ValueError("%s is not prime." % l) @@ -2597,70 +2601,21 @@ def isogenies_prime_degree_general(E, l, minimal_models=True): psi_l = E.division_polynomial(l) - # Every kernel polynomial is a product of irreducible factors of - # the division polynomial of the same degree, where this degree is - # a divisor of (l-1)/2, so we keep only such factors: - - l2 = (l - 1) // 2 - factors = [h for h, _ in psi_l.factor()] - factors_by_degree = {d: [f for f in factors if f.degree() == d] - for d in l2.divisors()} + factors = [h for h,_ in psi_l.factor() if h.degree().divides(l//2)] ker = [] # will store all kernel polynomials found - # If for some d dividing (l-1)/2 there are exactly (l-1)/2d - # divisors of degree d, then their product is a kernel poly, which - # we add to the list and remove the factors used. + while factors: + h = factors.pop() + try: + k = E.kernel_polynomial_from_divisor(h, l, check=False) + except ValueError: + continue + assert k.degree() == l//2 and k.divides(psi_l) + ker.append(k) + factors = [h for h in factors if not h.divides(k)] - from sage.misc.misc_c import prod - for d in list(factors_by_degree): - if d * len(factors_by_degree[d]) == l2: - ker.append(prod(factors_by_degree.pop(d))) - - # Exit now if all factors have been used already: - - if all(not factors for factors in factors_by_degree.values()): - return [E.isogeny(k) for k in ker] - - # In general we look for products of factors of the same degree d - # which can be kernel polynomials - - a = _least_semi_primitive(l) - m = E.multiplication_by_m(a, x_only=True) - m_num = m.numerator() - m_den = m.denominator() - R = psi_l.parent() - - # This function permutes the factors of a given degree, replacing - # the factor with roots alpha with the one whose roots are - # m(alpha), where m(x) is the rational function giving the - # multiplication-by-a map on the X-coordinates. Here, a is a - # generator for (Z/lZ)^* / <-1> (a so-called semi-primitive root). - def mult(g): - # Find f such that f(m) = 0 mod g - S = R.quotient_ring(g) - Sm = S(m_num) / S(m_den) - return Sm.charpoly('x') - - # kernel polynomials are the products of factors of degree d in - # one orbit under mult, provided that the orbit has length - # (l-1)/2d. Otherwise the orbit will be longer. - for d in factors_by_degree: - factors = factors_by_degree[d] - while factors: - # Compute an orbit under mult: - f0 = factors.pop(0) - orbit = [f0] - f = mult(f0) - while f != f0: - orbit.append(f) - factors.remove(f) - f = mult(f) - # Check orbit length: - if d*len(orbit) == l2: - ker.append(prod(orbit)) - - return [E.isogeny(k) for k in ker] + return [E.isogeny(k, check=False) for k in ker] def isogenies_prime_degree(E, l, minimal_models=True): @@ -2698,12 +2653,12 @@ def isogenies_prime_degree(E, l, minimal_models=True): [Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2 over Finite Field in a of size 3^12 - to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2*x + to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + x + 2 over Finite Field in a of size 3^12, Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2 over Finite Field in a of size 3^12 - to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + x + 2 + to Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2*x over Finite Field in a of size 3^12] sage: E = EllipticCurve('50a1') sage: isogenies_prime_degree(E, 3) @@ -2804,7 +2759,7 @@ def isogenies_prime_degree(E, l, minimal_models=True): over Number Field in a with defining polynomial x^2 + 11 with a = 3.316624790355400?*I to Elliptic Curve defined by - y^2 = x^3 + x^2 + (30800*a+123963)*x + (3931312*a-21805005) + y^2 = x^3 + x^2 + (-30800*a+123963)*x + (-3931312*a-21805005) over Number Field in a with defining polynomial x^2 + 11 with a = 3.316624790355400?*I, Isogeny of degree 37 @@ -2812,7 +2767,7 @@ def isogenies_prime_degree(E, l, minimal_models=True): over Number Field in a with defining polynomial x^2 + 11 with a = 3.316624790355400?*I to Elliptic Curve defined by - y^2 = x^3 + x^2 + (-30800*a+123963)*x + (-3931312*a-21805005) + y^2 = x^3 + x^2 + (30800*a+123963)*x + (3931312*a-21805005) over Number Field in a with defining polynomial x^2 + 11 with a = 3.316624790355400?*I] """ diff --git a/src/sage/tests/gap_packages.py b/src/sage/tests/gap_packages.py deleted file mode 100644 index e6ba35a7856..00000000000 --- a/src/sage/tests/gap_packages.py +++ /dev/null @@ -1,141 +0,0 @@ -# sage.doctest: needs sage.libs.gap -""" -Test the optional GAP packages - -TESTS:: - - sage: from sage.tests.gap_packages import all_installed_packages, test_packages - sage: pkgs = all_installed_packages(ignore_dot_gap=True) - sage: test_packages(pkgs, only_failures=True) # optional - gap_packages - Status Package GAP Output - +--------+---------+------------+ - - sage: test_packages(['primgrp', 'smallgrp']) - Status Package GAP Output - ├────────┼──────────┼────────────┤ - primgrp true - smallgrp true -""" - -import os - -from sage.libs.gap.libgap import libgap - - -def test_packages(packages, only_failures=False): - """ - Return list of all installed packages. - - INPUT: - - - ``packages`` -- a list/tuple/iterable of strings. The names of - GAP packages to try to import. - - - ``only_failures`` -- boolean, default ``False``. Whether to only - include failures in the table. - - OUTPUT: - - A table of the installed packages and whether they load - successfully. - - EXAMPLES:: - - sage: from sage.tests.gap_packages import all_installed_packages, test_packages - sage: test_packages(['GAPDoc']) - Status Package GAP Output - ├────────┼─────────┼────────────┤ - GAPDoc true - - All packages, including user-installed ones:: - - sage: pkgs = all_installed_packages() - sage: test_packages(pkgs) # random output - Status Package GAP Output - +---------+------------+------------+ - Alnuth true - GAPDoc true - sonata true - tomlib true - toric true - """ - rows = [['Status', 'Package', 'GAP Output']] - for pkgdir in packages: - # to allow weird suffixes e.g. 'qpa-version' - pkg = pkgdir.split('-')[0] - orig_warning_level = libgap.InfoLevel(libgap.InfoWarning) - # Silence warnings about missing optional packages that might occur - # when loading packages; they're not important for the purposes of this - # test code - libgap.SetInfoLevel(libgap.InfoWarning, 0) - try: - output = libgap.LoadPackage(pkg) - finally: - # Restore the original warning level - libgap.SetInfoLevel(libgap.InfoWarning, orig_warning_level) - - ok = bool(output) - status = '' if ok else 'Failure' - if ok and only_failures: - continue - rows.append([status, pkg, str(output)]) - from sage.misc.table import table - return table(rows, header_row=True) - - -def all_installed_packages(ignore_dot_gap=False, gap=None): - """ - Return list of all installed packages. - - INPUT: - - - ``ignore_dot_gap`` -- Boolean (default: ``False``). Whether to - ignore the `.gap/` directory (usually in the user home - directory) when searching for packages. - - - ``gap`` -- The GAP interface to use (default: ``libgap``); can - be either ``libgap`` or a pexpect ``Gap`` instance. - - OUTPUT: - - Tuple of strings in alphabetic order. - - EXAMPLES:: - - sage: from sage.tests.gap_packages import all_installed_packages - sage: [p.lower() for p in all_installed_packages()] - [...'gapdoc'...] - sage: all_installed_packages(ignore_dot_gap=True) == all_installed_packages(gap=gap, ignore_dot_gap=True) - True - """ - if gap is None: - gap = libgap - - if gap == libgap: - paths = [str(p) for p in gap.eval('GAPInfo.RootPaths')] - else: - paths = [str(p) for p in gap('GAPInfo.RootPaths')] - - # When GAP_ROOT_PATHS begins or ends with a semicolon (to append - # or prepend to the default list), the list of "gap" root paths - # will sometimes contain duplicates while the list for libgap will - # not. I don't know why this is: the appending/prepending does - # work as intended, even for libgap, so the issue is not that - # appending/prepending don't work at all for libgap. For lack of a - # better idea, we deduplicate here to avoid listing the same - # packages twice for the non-lib "gap" interface. - paths = set(paths) - - packages = [] - for path in paths: - if ignore_dot_gap and path.endswith('/.gap/'): - continue - pkg_dir = os.path.join(path, 'pkg') - if not os.path.exists(pkg_dir): - continue - for subdir in os.listdir(pkg_dir): - if not os.path.isdir(os.path.join(pkg_dir, subdir)): - continue - packages.append(subdir.rstrip('-.0123456789')) - packages.sort() - return tuple(packages)